clearotron 0.2.1 → 0.2.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.
Files changed (676) hide show
  1. package/.env.example +82 -1
  2. package/.nvmrc +1 -1
  3. package/CONTRIBUTING.md +3 -3
  4. package/INSTALL.md +64 -58
  5. package/README.md +11 -16
  6. package/THIRD-PARTY-NOTICES.md +1 -1
  7. package/bin/brandowner.mjs +1 -1
  8. package/bin/clearotron.mjs +26 -4
  9. package/bin/connect.mjs +2 -2
  10. package/bin/example.mjs +31 -11
  11. package/bin/grant.mjs +3 -3
  12. package/bin/key.mjs +1 -1
  13. package/bin/onboard.mjs +252 -136
  14. package/bin/start.mjs +176 -33
  15. package/bin/stop.mjs +65 -3
  16. package/bin/update.mjs +7 -7
  17. package/bin/uspto-sync.mjs +1 -1
  18. package/build-info.json +2 -2
  19. package/demo/full-country-search/run/_driver/framework.json +1 -1
  20. package/demo/global-preliminary-search/run/_driver/framework.json +1 -1
  21. package/demo/knockout-search/run/_driver/framework.json +1 -1
  22. package/demo/multi-country-focus-search/run/_driver/framework.json +1 -1
  23. package/docs/DELIVERY.md +6 -2
  24. package/docs/E2E.md +5 -5
  25. package/docs/PORTAL.md +2 -2
  26. package/docs/README.md +5 -4
  27. package/docs/SECURITY.md +17 -0
  28. package/docs/architecture/01-product-overview.md +2 -2
  29. package/docs/architecture/02-architecture.md +2 -2
  30. package/docs/architecture/04-configuration-reference.md +8 -3
  31. package/docs/architecture/05-config-governance.md +52 -5
  32. package/docs/architecture/05-customer-profiles.md +2 -2
  33. package/docs/architecture/06-operations-runbook.md +13 -1
  34. package/docs/architecture/09-security-and-data.md +1 -1
  35. package/docs/configuration.md +4 -3
  36. package/docs/decisions/0004-documentation-structure.md +1 -1
  37. package/driver/CHANGELOG.md +121 -1
  38. package/driver/README.md +2 -2
  39. package/driver/ask-ledger.mjs +3 -3
  40. package/driver/authority-trees.mjs +1 -1
  41. package/driver/call-capture.mjs +1 -1
  42. package/driver/card-frame.mjs +1 -1
  43. package/driver/case-law-ledger.mjs +1 -1
  44. package/driver/claim-liveness.mjs +1 -1
  45. package/driver/common-law-receipts.mjs +2 -2
  46. package/driver/commonlaw-carry.mjs +1 -1
  47. package/driver/config-inventory.mjs +1 -1
  48. package/driver/config-staleness.mjs +1 -1
  49. package/driver/connotation-search.mjs +12 -12
  50. package/driver/contract-audit.mjs +5 -5
  51. package/driver/contract-dictation-registry.mjs +4 -4
  52. package/driver/contract-dictation.mjs +2 -2
  53. package/driver/contract-e3-backlog.mjs +6 -6
  54. package/driver/contract-vocabulary.mjs +3 -3
  55. package/driver/corrections-feedforward.mjs +1 -1
  56. package/driver/coverage-form-io.mjs +2 -2
  57. package/driver/coverage-form.mjs +4 -4
  58. package/driver/coverage-ledger.mjs +3 -3
  59. package/driver/coverage-union.mjs +1 -1
  60. package/driver/declination-call.mjs +35 -3
  61. package/driver/declination-duty.mjs +1 -1
  62. package/driver/deliver-trigger.sh +3 -3
  63. package/driver/deploy-live-run-guard.mjs +6 -2
  64. package/driver/dev-portal.mjs +3 -3
  65. package/driver/disposition-call.mjs +4 -4
  66. package/driver/disposition-tool.mjs +4 -4
  67. package/driver/disposition-union.mjs +2 -2
  68. package/driver/doc-constants.mjs +1 -1
  69. package/driver/door-gates.mjs +2 -2
  70. package/driver/doubt-selection.mjs +2 -2
  71. package/driver/drainer-identity.mjs +1 -1
  72. package/driver/driver.config.mjs +64 -12
  73. package/driver/e2e-rounds.mjs +2 -2
  74. package/driver/effort-model.mjs +2 -2
  75. package/driver/engine/anthropic-agent.mjs +24 -24
  76. package/driver/engine/auth.mjs +1 -1
  77. package/driver/engine/cli-version.mjs +103 -0
  78. package/driver/engine/common.mjs +1 -1
  79. package/driver/engine/deny-authority-write.mjs +1 -1
  80. package/driver/engine/jx-turn.mjs +1 -1
  81. package/driver/engine/mcp/clarivate-server.mjs +1 -1
  82. package/driver/engine/mcp/corsearch-server.mjs +1 -1
  83. package/driver/engine/mcp/declination-server.mjs +1 -1
  84. package/driver/engine/mcp/dispositions-server.mjs +1 -1
  85. package/driver/engine/mcp/euipo-server.mjs +1 -1
  86. package/driver/engine/mcp/fetch-server.mjs +1 -1
  87. package/driver/engine/mcp/free-tier-server.mjs +2 -2
  88. package/driver/engine/mcp/gather-config.mjs +11 -11
  89. package/driver/engine/mcp/perplexity-server.mjs +1 -1
  90. package/driver/engine/mcp/recording-server.mjs +8 -4
  91. package/driver/engine/mcp/signa-server.mjs +1 -1
  92. package/driver/engine/mcp/uspto-local-server.mjs +1 -1
  93. package/driver/engine/openai-agent.mjs +2 -2
  94. package/driver/engine-build.mjs +2 -2
  95. package/driver/enqueue-schema.mjs +10 -10
  96. package/driver/enqueue.mjs +1 -1
  97. package/driver/envelope-settle.mjs +1 -1
  98. package/driver/evidence-claim-invariant.mjs +2 -2
  99. package/driver/feedback-store.mjs +1 -1
  100. package/driver/findings-model.mjs +60 -10
  101. package/driver/flag-snapshot.mjs +20 -0
  102. package/driver/floor-duty.mjs +1 -1
  103. package/driver/form-neighbourhood.mjs +1 -1
  104. package/driver/framework.mjs +2 -2
  105. package/driver/gateway.mjs +45 -23
  106. package/driver/grounds-grammar.mjs +1 -1
  107. package/driver/hit-list.mjs +1 -1
  108. package/driver/jurisdiction-systems.mjs +1 -1
  109. package/driver/jx-lanes.mjs +1 -1
  110. package/driver/jx-units.mjs +1 -1
  111. package/driver/jx.mjs +3 -3
  112. package/driver/knockout-assess-record.mjs +5 -1
  113. package/driver/narrative-refutation-record.mjs +3 -3
  114. package/driver/numeric-setting.mjs +1 -1
  115. package/driver/owner-use-check.mjs +184 -0
  116. package/driver/package.json +4 -4
  117. package/driver/party-facts.mjs +2 -2
  118. package/driver/phase0.mjs +2 -2
  119. package/driver/pipeline-knockout.mjs +52 -4
  120. package/driver/pipeline.mjs +180 -67
  121. package/driver/placement-diff.mjs +2 -2
  122. package/driver/placement-form.mjs +4 -4
  123. package/driver/placement-union.mjs +1 -1
  124. package/driver/plain-register.mjs +120 -0
  125. package/driver/portal-access.mjs +1 -1
  126. package/driver/portal-acks.mjs +1 -1
  127. package/driver/portal-config-view.mjs +42 -3
  128. package/driver/portal-families.mjs +1 -1
  129. package/driver/portal-local-auth.mjs +1 -1
  130. package/driver/portal-report.mjs +7 -7
  131. package/driver/portal-service.mjs +41 -22
  132. package/driver/portal-upstream.mjs +2 -2
  133. package/driver/predelivery-lint.mjs +149 -11
  134. package/driver/prelim-variants-record.mjs +2 -2
  135. package/driver/profile-page.html +11 -3
  136. package/driver/profile-selection.mjs +1 -1
  137. package/driver/profile-service.mjs +6 -6
  138. package/driver/profiles/README.md +3 -3
  139. package/driver/profiles.mjs +145 -13
  140. package/driver/progress.mjs +6 -6
  141. package/driver/provider-usage.mjs +1 -1
  142. package/driver/publish/demo-marking.mjs +1 -1
  143. package/driver/publish/index.mjs +49 -8
  144. package/driver/publish/knockout.mjs +42 -14
  145. package/driver/publish/parse.mjs +4 -4
  146. package/driver/publish/publish-inputs.mjs +2 -2
  147. package/driver/publish/register-presence.mjs +1 -1
  148. package/driver/publish/render-knockout.mjs +646 -144
  149. package/driver/publish/render.mjs +2 -2
  150. package/driver/publish/report-data.mjs +13 -3
  151. package/driver/queue-watch-probe.mjs +1 -1
  152. package/driver/queue-watch-verdict.mjs +1 -1
  153. package/driver/recall-reconciliation.mjs +1 -1
  154. package/driver/recipe-service.mjs +1 -1
  155. package/driver/record-carry.mjs +5 -5
  156. package/driver/recording-agreement.mjs +3 -3
  157. package/driver/reference-score.mjs +7 -7
  158. package/driver/reference-strip-signatures.mjs +20 -0
  159. package/driver/refusal-bound.mjs +3 -3
  160. package/driver/register-availability.mjs +2 -2
  161. package/driver/register-count.mjs +1 -1
  162. package/driver/register-grant-vocabulary.mjs +2 -2
  163. package/driver/register-plan.mjs +8 -8
  164. package/driver/register-records.mjs +2 -2
  165. package/driver/registry-fidelity.mjs +3 -3
  166. package/driver/remedy-accounting.mjs +1 -1
  167. package/driver/repair-composers.mjs +2 -2
  168. package/driver/repair-digest.mjs +3 -3
  169. package/driver/repairs.mjs +6 -6
  170. package/driver/replay-archive.mjs +2 -0
  171. package/driver/report-card-record.mjs +2 -2
  172. package/driver/result-noun-fields.mjs +10 -3
  173. package/driver/run-economics.mjs +2 -2
  174. package/driver/run-integrity.mjs +1 -1
  175. package/driver/runner.mjs +22 -22
  176. package/driver/scope-rules.mjs +1 -1
  177. package/driver/search-policy.mjs +5 -5
  178. package/driver/skeptic-search.mjs +1 -1
  179. package/driver/skills/blind-frame/SKILL.md +2 -2
  180. package/driver/skills/case-law-citation/SKILL.md +2 -2
  181. package/driver/skills/knockout-assess/SKILL.md +94 -4
  182. package/driver/skills/matter-frame/SKILL.md +6 -6
  183. package/driver/skills/matter-frame/watchlist-reference.md +2 -2
  184. package/driver/skills/prelim-common-law/SKILL.md +1 -1
  185. package/driver/skills/prelim-search/SKILL.md +3 -3
  186. package/driver/skills/prelim-search/report-prose.md +82 -0
  187. package/driver/skills/prelim-search/risk-framework-demo.manifest.json +1 -1
  188. package/driver/skills/prelim-search/risk-framework-triage.manifest.json +1 -1
  189. package/driver/skills/prelim-search/risk-framework.manifest.json +1 -1
  190. package/driver/skills/prelim-search/synthesis-rules.md +1 -1
  191. package/driver/skills/prelim-search/template-formatting.md +1 -1
  192. package/driver/skills/prelim-search/templates/email/generic.md +1 -1
  193. package/driver/skills/prelim-search/worked-examples.md +1 -1
  194. package/driver/skills/prelim-variants/SKILL.md +5 -5
  195. package/driver/stage-context.mjs +1 -1
  196. package/driver/stages-knockout.mjs +55 -4
  197. package/driver/stages.mjs +101 -44
  198. package/driver/stray-artifacts.mjs +2 -2
  199. package/driver/suite-census.json +275 -65
  200. package/driver/synthesis-record.mjs +1 -1
  201. package/driver/systemd/clearotron-client-mcp.service +1 -1
  202. package/driver/systemd/clearotron-portal.service +1 -1
  203. package/driver/systemd/render-units.mjs +1 -1
  204. package/driver/tokens.mjs +1 -1
  205. package/driver/tool-calls.mjs +1 -1
  206. package/driver/turnaround-bands.mjs +1 -1
  207. package/driver/unit-file-drift.mjs +1 -1
  208. package/driver/unit-inventory.mjs +297 -80
  209. package/driver/unit-state-verdict.mjs +20 -12
  210. package/driver/verify-knockout.mjs +10 -2
  211. package/driver/verify.mjs +13 -13
  212. package/driver/whatif-memo-run.mjs +72 -9
  213. package/driver/whatif-memo.mjs +19 -1
  214. package/driver/whatif-queue.mjs +1 -1
  215. package/driver/whatif-worker.mjs +1 -1
  216. package/examples/README.md +2 -2
  217. package/examples/grants.example.json +6 -7
  218. package/mcp-server/CHANGELOG.md +6 -0
  219. package/mcp-server/README.md +3 -3
  220. package/mcp-server/http-server.mjs +44 -1
  221. package/mcp-server/key-socket.mjs +184 -0
  222. package/mcp-server/lib/audit-view.mjs +69 -2
  223. package/mcp-server/lib/brief.mjs +15 -0
  224. package/mcp-server/lib/driver.mjs +7 -1
  225. package/mcp-server/lib/events.mjs +1 -1
  226. package/mcp-server/lib/http-handler.mjs +1 -1
  227. package/mcp-server/lib/knockout.mjs +435 -0
  228. package/mcp-server/lib/ops.mjs +4 -4
  229. package/mcp-server/lib/options.mjs +1 -1
  230. package/mcp-server/lib/plan.mjs +1 -1
  231. package/mcp-server/lib/runs.mjs +1 -1
  232. package/mcp-server/lib/scrub.mjs +2 -2
  233. package/mcp-server/lib/whatif.mjs +5 -5
  234. package/mcp-server/mint-token.mjs +1 -1
  235. package/mcp-server/package.json +2 -2
  236. package/mcp-server/packs/ops/EXAMPLES.md +2 -2
  237. package/mcp-server/server.mjs +77 -12
  238. package/package.json +30 -12
  239. package/portal-ui/dist/assets/{index-Ct3iQtZ2.css → index-1ziUJX1E.css} +43 -2
  240. package/portal-ui/dist/assets/{index-BUGgiYtF.js → index-CEYvXdqH.js} +226 -49
  241. package/portal-ui/dist/index.html +2 -2
  242. package/portal-ui/package.json +1 -1
  243. package/providers/_shared/enumerate.mjs +1 -1
  244. package/providers/_shared/ledger-path.mjs +3 -3
  245. package/providers/_shared/ledger.mjs +1 -1
  246. package/providers/_shared/result-shape.mjs +3 -3
  247. package/providers/_shared/term-shape.mjs +2 -2
  248. package/providers/corsearch/src/core.js +1 -1
  249. package/providers/jx-subclass/BUILDING.md +1 -1
  250. package/providers/oauth-mcp-bridge/CHANGELOG.md +6 -0
  251. package/providers/oauth-mcp-bridge/package.json +1 -1
  252. package/providers/oauth-mcp-bridge/stdin-guard.mjs +2 -2
  253. package/providers/uspto-local/src/index-store.js +2 -2
  254. package/scripts/README.md +1 -1
  255. package/scripts/added-reference-check.mjs +29 -1
  256. package/scripts/authority-boundary-probe.mjs +1 -1
  257. package/scripts/backfill-started-at.mjs +1 -1
  258. package/scripts/citation-line-check.mjs +124 -5
  259. package/scripts/compare.mjs +2 -2
  260. package/scripts/contract-dictation-scan.mjs +2 -2
  261. package/scripts/dead-names.mjs +1 -1
  262. package/scripts/drain-preflight.mjs +5 -5
  263. package/scripts/e2e-unread-terminals.mjs +1 -1
  264. package/scripts/e2e.mjs +8 -8
  265. package/scripts/env-audit.mjs +7 -7
  266. package/scripts/env-classify.mjs +206 -18
  267. package/scripts/freeze-example-run.mjs +3 -3
  268. package/scripts/head-arrived-gated.mjs +1 -1
  269. package/scripts/headless-page.mjs +51 -2
  270. package/scripts/live-surface-check.mjs +129 -19
  271. package/scripts/main-health.mjs +1 -1
  272. package/scripts/merge-presence-check.mjs +3 -3
  273. package/scripts/mint-suite-census.mjs +6 -6
  274. package/scripts/nightly-notice.mjs +1 -3
  275. package/scripts/preinstall-node-check.mjs +24 -0
  276. package/scripts/reconcile-runs.mjs +1 -1
  277. package/scripts/release-await-cut.mjs +6 -4
  278. package/scripts/release-version-pr-checks.mjs +1 -1
  279. package/scripts/release-version.mjs +2 -2
  280. package/scripts/render-check.mjs +66 -7
  281. package/scripts/report-frame-check.mjs +1 -1
  282. package/scripts/report-screenshot.mjs +1 -1
  283. package/scripts/revisit-render-check.mjs +1 -1
  284. package/scripts/score.mjs +4 -4
  285. package/scripts/strip-tracker-citations.mjs +139 -0
  286. package/scripts/test-run.mjs +19 -3
  287. package/scripts/third-party-notices.mjs +3 -3
  288. package/scripts/travelling-predicates.mjs +3 -3
  289. package/scripts/unexecuted-asserts.mjs +2 -2
  290. package/scripts/verify-publishable.mjs +2 -2
  291. package/scripts/write-up-form-census.mjs +1 -1
  292. package/shared/access-audience.mjs +1 -1
  293. package/shared/anon-overlay.mjs +3 -3
  294. package/shared/brand.mjs +13 -13
  295. package/shared/citation-scan.mjs +1 -1
  296. package/shared/client-door.mjs +7 -7
  297. package/shared/customer-store.mjs +1 -1
  298. package/shared/driver-dir.mjs +1 -1
  299. package/shared/env-aliases.mjs +0 -48
  300. package/shared/env-local.mjs +1 -1
  301. package/shared/identifier-scan.mjs +3 -3
  302. package/shared/invocation.mjs +29 -1
  303. package/shared/names-in-force.mjs +4 -0
  304. package/shared/node-floor.mjs +97 -0
  305. package/shared/product-owned-names.mjs +1 -1
  306. package/shared/register-selection.mjs +1 -1
  307. package/shared/scope.mjs +5 -5
  308. package/shared/server-units.mjs +4 -4
  309. package/shared/staff-domain.mjs +154 -0
  310. package/shared/store-in-repo.mjs +25 -4
  311. package/shared/suite-census.mjs +4 -4
  312. package/shared/tty-style.mjs +1 -1
  313. package/shared/uspto-index-size.mjs +1 -1
  314. package/shared/vacuous-pass.mjs +1 -1
  315. package/skills/clearotron-account/SKILL.md +42 -13
  316. package/skills/clearotron-ops/SKILL.md +1 -1
  317. package/driver/recipes/README.md +0 -49
  318. package/driver/skills/prelim-search/risk-framework-aurora.manifest.json +0 -19
  319. package/driver/skills/prelim-search/risk-framework-aurora.md +0 -77
  320. package/driver/skills/prelim-search/risk-framework-zephyr.manifest.json +0 -14
  321. package/driver/skills/prelim-search/risk-framework-zephyr.md +0 -33
  322. package/driver/skills/prelim-search/worked-examples-aurora.md +0 -10
  323. package/driver/skills/prelim-search/worked-examples-zephyr.md +0 -10
  324. package/driver/systemd/prelim-driver.path +0 -69
  325. package/driver/systemd/prelim-driver.service +0 -86
  326. package/driver/systemd/prelim-driver.timer +0 -14
  327. package/driver/systemd/prelim-outbox.path +0 -27
  328. package/driver/systemd/prelim-outbox.service +0 -37
  329. package/driver/systemd/prelim-outbox.timer +0 -21
  330. package/node_modules/exceljs/README_zh.md +0 -2878
  331. package/node_modules/exceljs/dist/LICENSE +0 -22
  332. package/node_modules/exceljs/dist/es5/csv/csv.js +0 -182
  333. package/node_modules/exceljs/dist/es5/csv/csv.js.map +0 -1
  334. package/node_modules/exceljs/dist/es5/csv/line-buffer.js +0 -68
  335. package/node_modules/exceljs/dist/es5/csv/line-buffer.js.map +0 -1
  336. package/node_modules/exceljs/dist/es5/csv/stream-converter.js +0 -110
  337. package/node_modules/exceljs/dist/es5/csv/stream-converter.js.map +0 -1
  338. package/node_modules/exceljs/dist/es5/doc/anchor.js +0 -73
  339. package/node_modules/exceljs/dist/es5/doc/anchor.js.map +0 -1
  340. package/node_modules/exceljs/dist/es5/doc/cell.js +0 -961
  341. package/node_modules/exceljs/dist/es5/doc/cell.js.map +0 -1
  342. package/node_modules/exceljs/dist/es5/doc/column.js +0 -274
  343. package/node_modules/exceljs/dist/es5/doc/column.js.map +0 -1
  344. package/node_modules/exceljs/dist/es5/doc/data-validations.js +0 -18
  345. package/node_modules/exceljs/dist/es5/doc/data-validations.js.map +0 -1
  346. package/node_modules/exceljs/dist/es5/doc/defined-names.js +0 -173
  347. package/node_modules/exceljs/dist/es5/doc/defined-names.js.map +0 -1
  348. package/node_modules/exceljs/dist/es5/doc/enums.js +0 -49
  349. package/node_modules/exceljs/dist/es5/doc/enums.js.map +0 -1
  350. package/node_modules/exceljs/dist/es5/doc/image.js +0 -69
  351. package/node_modules/exceljs/dist/es5/doc/image.js.map +0 -1
  352. package/node_modules/exceljs/dist/es5/doc/modelcontainer.js +0 -16
  353. package/node_modules/exceljs/dist/es5/doc/modelcontainer.js.map +0 -1
  354. package/node_modules/exceljs/dist/es5/doc/note.js +0 -64
  355. package/node_modules/exceljs/dist/es5/doc/note.js.map +0 -1
  356. package/node_modules/exceljs/dist/es5/doc/range.js +0 -229
  357. package/node_modules/exceljs/dist/es5/doc/range.js.map +0 -1
  358. package/node_modules/exceljs/dist/es5/doc/row.js +0 -382
  359. package/node_modules/exceljs/dist/es5/doc/row.js.map +0 -1
  360. package/node_modules/exceljs/dist/es5/doc/table.js +0 -443
  361. package/node_modules/exceljs/dist/es5/doc/table.js.map +0 -1
  362. package/node_modules/exceljs/dist/es5/doc/workbook.js +0 -202
  363. package/node_modules/exceljs/dist/es5/doc/workbook.js.map +0 -1
  364. package/node_modules/exceljs/dist/es5/doc/worksheet.js +0 -900
  365. package/node_modules/exceljs/dist/es5/doc/worksheet.js.map +0 -1
  366. package/node_modules/exceljs/dist/es5/exceljs.bare.js +0 -14
  367. package/node_modules/exceljs/dist/es5/exceljs.bare.js.map +0 -1
  368. package/node_modules/exceljs/dist/es5/exceljs.browser.js +0 -36
  369. package/node_modules/exceljs/dist/es5/exceljs.browser.js.map +0 -1
  370. package/node_modules/exceljs/dist/es5/exceljs.nodejs.js +0 -15
  371. package/node_modules/exceljs/dist/es5/exceljs.nodejs.js.map +0 -1
  372. package/node_modules/exceljs/dist/es5/index.js +0 -15
  373. package/node_modules/exceljs/dist/es5/stream/xlsx/hyperlink-reader.js +0 -90
  374. package/node_modules/exceljs/dist/es5/stream/xlsx/hyperlink-reader.js.map +0 -1
  375. package/node_modules/exceljs/dist/es5/stream/xlsx/sheet-comments-writer.js +0 -94
  376. package/node_modules/exceljs/dist/es5/stream/xlsx/sheet-comments-writer.js.map +0 -1
  377. package/node_modules/exceljs/dist/es5/stream/xlsx/sheet-rels-writer.js +0 -94
  378. package/node_modules/exceljs/dist/es5/stream/xlsx/sheet-rels-writer.js.map +0 -1
  379. package/node_modules/exceljs/dist/es5/stream/xlsx/workbook-reader.js +0 -362
  380. package/node_modules/exceljs/dist/es5/stream/xlsx/workbook-reader.js.map +0 -1
  381. package/node_modules/exceljs/dist/es5/stream/xlsx/workbook-writer.js +0 -347
  382. package/node_modules/exceljs/dist/es5/stream/xlsx/workbook-writer.js.map +0 -1
  383. package/node_modules/exceljs/dist/es5/stream/xlsx/worksheet-reader.js +0 -392
  384. package/node_modules/exceljs/dist/es5/stream/xlsx/worksheet-reader.js.map +0 -1
  385. package/node_modules/exceljs/dist/es5/stream/xlsx/worksheet-writer.js +0 -652
  386. package/node_modules/exceljs/dist/es5/stream/xlsx/worksheet-writer.js.map +0 -1
  387. package/node_modules/exceljs/dist/es5/utils/auto-drain.js +0 -18
  388. package/node_modules/exceljs/dist/es5/utils/auto-drain.js.map +0 -1
  389. package/node_modules/exceljs/dist/es5/utils/browser-buffer-decode.js +0 -15
  390. package/node_modules/exceljs/dist/es5/utils/browser-buffer-decode.js.map +0 -1
  391. package/node_modules/exceljs/dist/es5/utils/browser-buffer-encode.js +0 -18
  392. package/node_modules/exceljs/dist/es5/utils/browser-buffer-encode.js.map +0 -1
  393. package/node_modules/exceljs/dist/es5/utils/cell-matrix.js +0 -149
  394. package/node_modules/exceljs/dist/es5/utils/cell-matrix.js.map +0 -1
  395. package/node_modules/exceljs/dist/es5/utils/col-cache.js +0 -258
  396. package/node_modules/exceljs/dist/es5/utils/col-cache.js.map +0 -1
  397. package/node_modules/exceljs/dist/es5/utils/copy-style.js +0 -43
  398. package/node_modules/exceljs/dist/es5/utils/copy-style.js.map +0 -1
  399. package/node_modules/exceljs/dist/es5/utils/encryptor.js +0 -58
  400. package/node_modules/exceljs/dist/es5/utils/encryptor.js.map +0 -1
  401. package/node_modules/exceljs/dist/es5/utils/iterate-stream.js +0 -46
  402. package/node_modules/exceljs/dist/es5/utils/iterate-stream.js.map +0 -1
  403. package/node_modules/exceljs/dist/es5/utils/parse-sax.js +0 -47
  404. package/node_modules/exceljs/dist/es5/utils/parse-sax.js.map +0 -1
  405. package/node_modules/exceljs/dist/es5/utils/shared-formula.js +0 -42
  406. package/node_modules/exceljs/dist/es5/utils/shared-formula.js.map +0 -1
  407. package/node_modules/exceljs/dist/es5/utils/shared-strings.js +0 -32
  408. package/node_modules/exceljs/dist/es5/utils/shared-strings.js.map +0 -1
  409. package/node_modules/exceljs/dist/es5/utils/stream-base64.js +0 -66
  410. package/node_modules/exceljs/dist/es5/utils/stream-base64.js.map +0 -1
  411. package/node_modules/exceljs/dist/es5/utils/stream-buf.js +0 -343
  412. package/node_modules/exceljs/dist/es5/utils/stream-buf.js.map +0 -1
  413. package/node_modules/exceljs/dist/es5/utils/string-buf.js +0 -73
  414. package/node_modules/exceljs/dist/es5/utils/string-buf.js.map +0 -1
  415. package/node_modules/exceljs/dist/es5/utils/string-builder.js +0 -32
  416. package/node_modules/exceljs/dist/es5/utils/string-builder.js.map +0 -1
  417. package/node_modules/exceljs/dist/es5/utils/stuttered-pipe.js +0 -61
  418. package/node_modules/exceljs/dist/es5/utils/stuttered-pipe.js.map +0 -1
  419. package/node_modules/exceljs/dist/es5/utils/typed-stack.js +0 -23
  420. package/node_modules/exceljs/dist/es5/utils/typed-stack.js.map +0 -1
  421. package/node_modules/exceljs/dist/es5/utils/under-dash.js +0 -168
  422. package/node_modules/exceljs/dist/es5/utils/under-dash.js.map +0 -1
  423. package/node_modules/exceljs/dist/es5/utils/utils.js +0 -171
  424. package/node_modules/exceljs/dist/es5/utils/utils.js.map +0 -1
  425. package/node_modules/exceljs/dist/es5/utils/xml-stream.js +0 -151
  426. package/node_modules/exceljs/dist/es5/utils/xml-stream.js.map +0 -1
  427. package/node_modules/exceljs/dist/es5/utils/zip-stream.js +0 -79
  428. package/node_modules/exceljs/dist/es5/utils/zip-stream.js.map +0 -1
  429. package/node_modules/exceljs/dist/es5/xlsx/defaultnumformats.js +0 -230
  430. package/node_modules/exceljs/dist/es5/xlsx/defaultnumformats.js.map +0 -1
  431. package/node_modules/exceljs/dist/es5/xlsx/rel-type.js +0 -18
  432. package/node_modules/exceljs/dist/es5/xlsx/rel-type.js.map +0 -1
  433. package/node_modules/exceljs/dist/es5/xlsx/xform/base-xform.js +0 -139
  434. package/node_modules/exceljs/dist/es5/xlsx/xform/base-xform.js.map +0 -1
  435. package/node_modules/exceljs/dist/es5/xlsx/xform/book/defined-name-xform.js +0 -85
  436. package/node_modules/exceljs/dist/es5/xlsx/xform/book/defined-name-xform.js.map +0 -1
  437. package/node_modules/exceljs/dist/es5/xlsx/xform/book/sheet-xform.js +0 -32
  438. package/node_modules/exceljs/dist/es5/xlsx/xform/book/sheet-xform.js.map +0 -1
  439. package/node_modules/exceljs/dist/es5/xlsx/xform/book/workbook-calc-properties-xform.js +0 -24
  440. package/node_modules/exceljs/dist/es5/xlsx/xform/book/workbook-calc-properties-xform.js.map +0 -1
  441. package/node_modules/exceljs/dist/es5/xlsx/xform/book/workbook-properties-xform.js +0 -27
  442. package/node_modules/exceljs/dist/es5/xlsx/xform/book/workbook-properties-xform.js.map +0 -1
  443. package/node_modules/exceljs/dist/es5/xlsx/xform/book/workbook-view-xform.js +0 -51
  444. package/node_modules/exceljs/dist/es5/xlsx/xform/book/workbook-view-xform.js.map +0 -1
  445. package/node_modules/exceljs/dist/es5/xlsx/xform/book/workbook-xform.js +0 -234
  446. package/node_modules/exceljs/dist/es5/xlsx/xform/book/workbook-xform.js.map +0 -1
  447. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/comment-xform.js +0 -103
  448. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/comment-xform.js.map +0 -1
  449. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/comments-xform.js +0 -76
  450. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/comments-xform.js.map +0 -1
  451. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/style/vml-position-xform.js +0 -35
  452. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/style/vml-position-xform.js.map +0 -1
  453. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/style/vml-protection-xform.js +0 -32
  454. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/style/vml-protection-xform.js.map +0 -1
  455. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/vml-anchor-xform.js +0 -53
  456. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/vml-anchor-xform.js.map +0 -1
  457. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/vml-client-data-xform.js +0 -97
  458. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/vml-client-data-xform.js.map +0 -1
  459. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/vml-notes-xform.js +0 -107
  460. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/vml-notes-xform.js.map +0 -1
  461. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/vml-shape-xform.js +0 -92
  462. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/vml-shape-xform.js.map +0 -1
  463. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/vml-textbox-xform.js +0 -61
  464. package/node_modules/exceljs/dist/es5/xlsx/xform/comment/vml-textbox-xform.js.map +0 -1
  465. package/node_modules/exceljs/dist/es5/xlsx/xform/composite-xform.js +0 -51
  466. package/node_modules/exceljs/dist/es5/xlsx/xform/composite-xform.js.map +0 -1
  467. package/node_modules/exceljs/dist/es5/xlsx/xform/core/app-heading-pairs-xform.js +0 -30
  468. package/node_modules/exceljs/dist/es5/xlsx/xform/core/app-heading-pairs-xform.js.map +0 -1
  469. package/node_modules/exceljs/dist/es5/xlsx/xform/core/app-titles-of-parts-xform.js +0 -27
  470. package/node_modules/exceljs/dist/es5/xlsx/xform/core/app-titles-of-parts-xform.js.map +0 -1
  471. package/node_modules/exceljs/dist/es5/xlsx/xform/core/app-xform.js +0 -93
  472. package/node_modules/exceljs/dist/es5/xlsx/xform/core/app-xform.js.map +0 -1
  473. package/node_modules/exceljs/dist/es5/xlsx/xform/core/content-types-xform.js +0 -112
  474. package/node_modules/exceljs/dist/es5/xlsx/xform/core/content-types-xform.js.map +0 -1
  475. package/node_modules/exceljs/dist/es5/xlsx/xform/core/core-xform.js +0 -158
  476. package/node_modules/exceljs/dist/es5/xlsx/xform/core/core-xform.js.map +0 -1
  477. package/node_modules/exceljs/dist/es5/xlsx/xform/core/relationship-xform.js +0 -23
  478. package/node_modules/exceljs/dist/es5/xlsx/xform/core/relationship-xform.js.map +0 -1
  479. package/node_modules/exceljs/dist/es5/xlsx/xform/core/relationships-xform.js +0 -65
  480. package/node_modules/exceljs/dist/es5/xlsx/xform/core/relationships-xform.js.map +0 -1
  481. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/base-cell-anchor-xform.js +0 -47
  482. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/base-cell-anchor-xform.js.map +0 -1
  483. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/blip-fill-xform.js +0 -61
  484. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/blip-fill-xform.js.map +0 -1
  485. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/blip-xform.js +0 -40
  486. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/blip-xform.js.map +0 -1
  487. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/c-nv-pic-pr-xform.js +0 -35
  488. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/c-nv-pic-pr-xform.js.map +0 -1
  489. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/c-nv-pr-xform.js +0 -62
  490. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/c-nv-pr-xform.js.map +0 -1
  491. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/cell-position-xform.js +0 -82
  492. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/cell-position-xform.js.map +0 -1
  493. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/drawing-xform.js +0 -98
  494. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/drawing-xform.js.map +0 -1
  495. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/ext-lst-xform.js +0 -40
  496. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/ext-lst-xform.js.map +0 -1
  497. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/ext-xform.js +0 -38
  498. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/ext-xform.js.map +0 -1
  499. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/hlink-click-xform.js +0 -38
  500. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/hlink-click-xform.js.map +0 -1
  501. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/nv-pic-pr-xform.js +0 -59
  502. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/nv-pic-pr-xform.js.map +0 -1
  503. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/one-cell-anchor-xform.js +0 -63
  504. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/one-cell-anchor-xform.js.map +0 -1
  505. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/pic-xform.js +0 -67
  506. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/pic-xform.js.map +0 -1
  507. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/sp-pr.js +0 -30
  508. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/sp-pr.js.map +0 -1
  509. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/two-cell-anchor-xform.js +0 -62
  510. package/node_modules/exceljs/dist/es5/xlsx/xform/drawing/two-cell-anchor-xform.js.map +0 -1
  511. package/node_modules/exceljs/dist/es5/xlsx/xform/list-xform.js +0 -91
  512. package/node_modules/exceljs/dist/es5/xlsx/xform/list-xform.js.map +0 -1
  513. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/auto-filter-xform.js +0 -40
  514. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/auto-filter-xform.js.map +0 -1
  515. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cell-xform.js +0 -451
  516. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cell-xform.js.map +0 -1
  517. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/cf-rule-xform.js +0 -275
  518. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/cf-rule-xform.js.map +0 -1
  519. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/cfvo-xform.js +0 -25
  520. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/cfvo-xform.js.map +0 -1
  521. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/color-scale-xform.js +0 -38
  522. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/color-scale-xform.js.map +0 -1
  523. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/conditional-formatting-xform.js +0 -45
  524. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/conditional-formatting-xform.js.map +0 -1
  525. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/conditional-formattings-xform.js +0 -77
  526. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/conditional-formattings-xform.js.map +0 -1
  527. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/databar-xform.js +0 -42
  528. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/databar-xform.js.map +0 -1
  529. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/ext-lst-ref-xform.js +0 -71
  530. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/ext-lst-ref-xform.js.map +0 -1
  531. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/formula-xform.js +0 -22
  532. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/formula-xform.js.map +0 -1
  533. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/icon-set-xform.js +0 -43
  534. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf/icon-set-xform.js.map +0 -1
  535. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/cf-icon-ext-xform.js +0 -28
  536. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/cf-icon-ext-xform.js.map +0 -1
  537. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/cf-rule-ext-xform.js +0 -88
  538. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/cf-rule-ext-xform.js.map +0 -1
  539. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/cfvo-ext-xform.js +0 -38
  540. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/cfvo-ext-xform.js.map +0 -1
  541. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/conditional-formatting-ext-xform.js +0 -52
  542. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/conditional-formatting-ext-xform.js.map +0 -1
  543. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/conditional-formattings-ext-xform.js +0 -43
  544. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/conditional-formattings-ext-xform.js.map +0 -1
  545. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/databar-ext-xform.js +0 -75
  546. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/databar-ext-xform.js.map +0 -1
  547. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/f-ext-xform.js +0 -22
  548. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/f-ext-xform.js.map +0 -1
  549. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/icon-set-ext-xform.js +0 -66
  550. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/icon-set-ext-xform.js.map +0 -1
  551. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/sqref-ext-xform.js +0 -22
  552. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/cf-ext/sqref-ext-xform.js.map +0 -1
  553. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/col-xform.js +0 -78
  554. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/col-xform.js.map +0 -1
  555. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/data-validations-xform.js +0 -247
  556. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/data-validations-xform.js.map +0 -1
  557. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/dimension-xform.js +0 -28
  558. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/dimension-xform.js.map +0 -1
  559. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/drawing-xform.js +0 -32
  560. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/drawing-xform.js.map +0 -1
  561. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/ext-lst-xform.js +0 -69
  562. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/ext-lst-xform.js.map +0 -1
  563. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/header-footer-xform.js +0 -126
  564. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/header-footer-xform.js.map +0 -1
  565. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/hyperlink-xform.js +0 -50
  566. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/hyperlink-xform.js.map +0 -1
  567. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/merge-cell-xform.js +0 -26
  568. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/merge-cell-xform.js.map +0 -1
  569. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/merges.js +0 -52
  570. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/merges.js.map +0 -1
  571. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/outline-properties-xform.js +0 -35
  572. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/outline-properties-xform.js.map +0 -1
  573. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/page-breaks-xform.js +0 -24
  574. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/page-breaks-xform.js.map +0 -1
  575. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/page-margins-xform.js +0 -46
  576. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/page-margins-xform.js.map +0 -1
  577. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/page-setup-properties-xform.js +0 -32
  578. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/page-setup-properties-xform.js.map +0 -1
  579. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/page-setup-xform.js +0 -99
  580. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/page-setup-xform.js.map +0 -1
  581. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/picture-xform.js +0 -32
  582. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/picture-xform.js.map +0 -1
  583. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/print-options-xform.js +0 -45
  584. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/print-options-xform.js.map +0 -1
  585. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/row-breaks-xform.js +0 -37
  586. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/row-breaks-xform.js.map +0 -1
  587. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/row-xform.js +0 -128
  588. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/row-xform.js.map +0 -1
  589. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/sheet-format-properties-xform.js +0 -51
  590. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/sheet-format-properties-xform.js.map +0 -1
  591. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/sheet-properties-xform.js +0 -83
  592. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/sheet-properties-xform.js.map +0 -1
  593. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/sheet-protection-xform.js +0 -84
  594. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/sheet-protection-xform.js.map +0 -1
  595. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/sheet-view-xform.js +0 -190
  596. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/sheet-view-xform.js.map +0 -1
  597. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/table-part-xform.js +0 -32
  598. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/table-part-xform.js.map +0 -1
  599. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/worksheet-xform.js +0 -513
  600. package/node_modules/exceljs/dist/es5/xlsx/xform/sheet/worksheet-xform.js.map +0 -1
  601. package/node_modules/exceljs/dist/es5/xlsx/xform/simple/boolean-xform.js +0 -27
  602. package/node_modules/exceljs/dist/es5/xlsx/xform/simple/boolean-xform.js.map +0 -1
  603. package/node_modules/exceljs/dist/es5/xlsx/xform/simple/date-xform.js +0 -58
  604. package/node_modules/exceljs/dist/es5/xlsx/xform/simple/date-xform.js.map +0 -1
  605. package/node_modules/exceljs/dist/es5/xlsx/xform/simple/float-xform.js +0 -47
  606. package/node_modules/exceljs/dist/es5/xlsx/xform/simple/float-xform.js.map +0 -1
  607. package/node_modules/exceljs/dist/es5/xlsx/xform/simple/integer-xform.js +0 -53
  608. package/node_modules/exceljs/dist/es5/xlsx/xform/simple/integer-xform.js.map +0 -1
  609. package/node_modules/exceljs/dist/es5/xlsx/xform/simple/string-xform.js +0 -47
  610. package/node_modules/exceljs/dist/es5/xlsx/xform/simple/string-xform.js.map +0 -1
  611. package/node_modules/exceljs/dist/es5/xlsx/xform/static-xform.js +0 -61
  612. package/node_modules/exceljs/dist/es5/xlsx/xform/static-xform.js.map +0 -1
  613. package/node_modules/exceljs/dist/es5/xlsx/xform/strings/phonetic-text-xform.js +0 -98
  614. package/node_modules/exceljs/dist/es5/xlsx/xform/strings/phonetic-text-xform.js.map +0 -1
  615. package/node_modules/exceljs/dist/es5/xlsx/xform/strings/rich-text-xform.js +0 -92
  616. package/node_modules/exceljs/dist/es5/xlsx/xform/strings/rich-text-xform.js.map +0 -1
  617. package/node_modules/exceljs/dist/es5/xlsx/xform/strings/shared-string-xform.js +0 -99
  618. package/node_modules/exceljs/dist/es5/xlsx/xform/strings/shared-string-xform.js.map +0 -1
  619. package/node_modules/exceljs/dist/es5/xlsx/xform/strings/shared-strings-xform.js +0 -114
  620. package/node_modules/exceljs/dist/es5/xlsx/xform/strings/shared-strings-xform.js.map +0 -1
  621. package/node_modules/exceljs/dist/es5/xlsx/xform/strings/text-xform.js +0 -39
  622. package/node_modules/exceljs/dist/es5/xlsx/xform/strings/text-xform.js.map +0 -1
  623. package/node_modules/exceljs/dist/es5/xlsx/xform/style/alignment-xform.js +0 -139
  624. package/node_modules/exceljs/dist/es5/xlsx/xform/style/alignment-xform.js.map +0 -1
  625. package/node_modules/exceljs/dist/es5/xlsx/xform/style/border-xform.js +0 -185
  626. package/node_modules/exceljs/dist/es5/xlsx/xform/style/border-xform.js.map +0 -1
  627. package/node_modules/exceljs/dist/es5/xlsx/xform/style/color-xform.js +0 -66
  628. package/node_modules/exceljs/dist/es5/xlsx/xform/style/color-xform.js.map +0 -1
  629. package/node_modules/exceljs/dist/es5/xlsx/xform/style/dxf-xform.js +0 -106
  630. package/node_modules/exceljs/dist/es5/xlsx/xform/style/dxf-xform.js.map +0 -1
  631. package/node_modules/exceljs/dist/es5/xlsx/xform/style/fill-xform.js +0 -308
  632. package/node_modules/exceljs/dist/es5/xlsx/xform/style/fill-xform.js.map +0 -1
  633. package/node_modules/exceljs/dist/es5/xlsx/xform/style/font-xform.js +0 -175
  634. package/node_modules/exceljs/dist/es5/xlsx/xform/style/font-xform.js.map +0 -1
  635. package/node_modules/exceljs/dist/es5/xlsx/xform/style/numfmt-xform.js +0 -59
  636. package/node_modules/exceljs/dist/es5/xlsx/xform/style/numfmt-xform.js.map +0 -1
  637. package/node_modules/exceljs/dist/es5/xlsx/xform/style/protection-xform.js +0 -53
  638. package/node_modules/exceljs/dist/es5/xlsx/xform/style/protection-xform.js.map +0 -1
  639. package/node_modules/exceljs/dist/es5/xlsx/xform/style/style-xform.js +0 -118
  640. package/node_modules/exceljs/dist/es5/xlsx/xform/style/style-xform.js.map +0 -1
  641. package/node_modules/exceljs/dist/es5/xlsx/xform/style/styles-xform.js +0 -612
  642. package/node_modules/exceljs/dist/es5/xlsx/xform/style/styles-xform.js.map +0 -1
  643. package/node_modules/exceljs/dist/es5/xlsx/xform/style/underline-xform.js +0 -46
  644. package/node_modules/exceljs/dist/es5/xlsx/xform/style/underline-xform.js.map +0 -1
  645. package/node_modules/exceljs/dist/es5/xlsx/xform/table/auto-filter-xform.js +0 -75
  646. package/node_modules/exceljs/dist/es5/xlsx/xform/table/auto-filter-xform.js.map +0 -1
  647. package/node_modules/exceljs/dist/es5/xlsx/xform/table/custom-filter-xform.js +0 -30
  648. package/node_modules/exceljs/dist/es5/xlsx/xform/table/custom-filter-xform.js.map +0 -1
  649. package/node_modules/exceljs/dist/es5/xlsx/xform/table/filter-column-xform.js +0 -89
  650. package/node_modules/exceljs/dist/es5/xlsx/xform/table/filter-column-xform.js.map +0 -1
  651. package/node_modules/exceljs/dist/es5/xlsx/xform/table/filter-xform.js +0 -28
  652. package/node_modules/exceljs/dist/es5/xlsx/xform/table/filter-xform.js.map +0 -1
  653. package/node_modules/exceljs/dist/es5/xlsx/xform/table/table-column-xform.js +0 -42
  654. package/node_modules/exceljs/dist/es5/xlsx/xform/table/table-column-xform.js.map +0 -1
  655. package/node_modules/exceljs/dist/es5/xlsx/xform/table/table-style-info-xform.js +0 -40
  656. package/node_modules/exceljs/dist/es5/xlsx/xform/table/table-style-info-xform.js.map +0 -1
  657. package/node_modules/exceljs/dist/es5/xlsx/xform/table/table-xform.js +0 -123
  658. package/node_modules/exceljs/dist/es5/xlsx/xform/table/table-xform.js.map +0 -1
  659. package/node_modules/exceljs/dist/es5/xlsx/xlsx.js +0 -697
  660. package/node_modules/exceljs/dist/es5/xlsx/xlsx.js.map +0 -1
  661. package/node_modules/exceljs/dist/es5/xlsx/xml/theme1.js +0 -5
  662. package/node_modules/exceljs/dist/es5/xlsx/xml/theme1.js.map +0 -1
  663. package/node_modules/exceljs/dist/exceljs.bare.js +0 -55253
  664. package/node_modules/exceljs/dist/exceljs.bare.js.map +0 -761
  665. package/node_modules/exceljs/dist/exceljs.bare.min.js +0 -45
  666. package/node_modules/exceljs/dist/exceljs.bare.min.js.map +0 -1
  667. package/node_modules/exceljs/dist/exceljs.js +0 -59900
  668. package/node_modules/exceljs/dist/exceljs.js.map +0 -1083
  669. package/node_modules/exceljs/dist/exceljs.min.js +0 -45
  670. package/node_modules/exceljs/dist/exceljs.min.js.map +0 -1
  671. package/scripts/ai-page-render-check.mjs +0 -532
  672. package/scripts/clearances-render-check.mjs +0 -480
  673. package/scripts/composer-render-check.mjs +0 -1027
  674. package/scripts/deploy-test.sh +0 -309
  675. package/scripts/home-render-check.mjs +0 -526
  676. package/scripts/portal-lifecycle-check.mjs +0 -639
@@ -21,10 +21,20 @@
21
21
  // ~60 lines of duplicated document shell, and it is the right price: the alternative is exporting the
22
22
  // shell out of the frozen file, which is a bigger break than the duplication saves.
23
23
  //
24
- // ONE report. There is no client variant and no internal variant. What used
25
- // to render purplethe staff notes and the model's register estimate is not stripped here, it is
26
- // simply NOT PART OF THE REPORT; it lives in the audit workbook, which is where internal working material
27
- // belongs. That removes the whole class of "the wrong link got sent".
24
+ // ONE report. There is no client variant and no internal variant, and that is
25
+ // still the ruleit is what removes the whole class of "the wrong link got sent".
26
+ //
27
+ // WHAT CHANGED, 2026-09-07 (owner ruling, tracker issue 274): the one report now CARRIES the reviewer's
28
+ // notes rather than routing them to the audit workbook alone. The earlier reading of "one report" was
29
+ // that internal working material is simply not part of it; the ruling is that there is one report and the
30
+ // person who ran Clearotron reads it, so holding material back "just adds confusion for where data is
31
+ // lost". Six notes written for the reviewing lawyer on a delivered run reached the spreadsheet and
32
+ // nothing else, which is the measurement behind the ruling.
33
+ //
34
+ // So: the notes render, LABELLED, in report.css's existing `.internal` purple convention (see
35
+ // reviewerNotesBlock) — never merged into the client-voiced body. The workbook keeps them as well; this
36
+ // added a surface and moved none. The model's `registerEstimate` is NOT covered by the ruling and stays
37
+ // off the page.
28
38
  import { readFileSync } from 'node:fs';
29
39
  import { join, dirname } from 'node:path';
30
40
  import { fileURLToPath } from 'node:url';
@@ -35,7 +45,7 @@ import {
35
45
  import { SUMMARY_BLOCK_LINE, parseSummaryBlocks } from '../../shared/summary-blocks.mjs';
36
46
  import { COUNT_BASIS, COUNT_PREDICATES, countsForMark, countLine, variantFormsLine } from '../register-count.mjs';
37
47
  import { RECORD_BASIS, recordsForMark, recordsLine } from '../register-records.mjs';
38
- import { knockoutFindingViews } from '../findings-model.mjs';
48
+ import { knockoutFindingViews, splitKnockoutNotes } from '../findings-model.mjs';
39
49
  import { demoBannerHtml } from './render.mjs'; // — the SAME banner the clearance template renders, not a second wording
40
50
  // — the two facts the register card is allowed to read off a raw record, and NEITHER is minted
41
51
  // here. `makeClassifyStatus` and `isAllClass` are the screening lane's own, already shipped, already
@@ -118,7 +128,7 @@ function bandStop(framework, band) {
118
128
 
119
129
  const DEGRADED_NOTE = 'Automated research for this name was unavailable — manual verification is recommended before relying on this rating.';
120
130
 
121
- // tracker issue 760 — THE QUALIFIER, IN WORDS. It rendered as a bare grey token under the band chip — `low`, no
131
+ // THE QUALIFIER, IN WORDS. It rendered as a bare grey token under the band chip — `low`, no
122
132
  // label, no border, nothing on the page saying what it qualified. A reader met one word of a private
123
133
  // vocabulary and had to guess whether it described the risk, the confidence or the search.
124
134
  //
@@ -176,6 +186,14 @@ const KO_CSS = `
176
186
  .ko-findev{margin:0;font-size:12.5px;line-height:1.6;word-break:break-word}
177
187
  .ko-count{font-family:var(--mono);font-size:13px;color:var(--ink);margin:0}
178
188
  .ko-degraded{font-size:13px;color:var(--med-tx,#82550A);margin:8px 0 0;font-style:italic}
189
+ /* The reviewer-notes legend. NOT .ko-legend — that name is taken by the framework attribution row
190
+ above, and reusing it would restyle the caption. It names the purple convention report.css already
191
+ draws for .internal, so the colour is stated once and never re-specified. */
192
+ .ko-refnote{margin:0;padding:11px 24px 0;font-size:12px;color:#6a2b6e;font-style:italic}
193
+ /* The notes sit inside a mark's column, so the shared .internal block's bullets keep the column's
194
+ own list indent and do not fight the paragraph above them. */
195
+ .ko-row .internal .ko-bul{margin:0 0 4px}
196
+ .ko-row .internal .ko-bul:last-child{margin-bottom:0}
179
197
  .ko-counts table{width:100%;border-collapse:collapse;font-size:14px}
180
198
  .ko-counts th{text-align:left;font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--faint);
181
199
  font-weight:700;padding:14px 24px 8px;border-bottom:1px solid var(--line)}
@@ -202,6 +220,28 @@ const KO_CSS = `
202
220
  saved to PDF would drop the narrative this block exists to preserve. The beforeprint handler below
203
221
  opens them; this hides the now-pointless toggle in print. */
204
222
  @media print{.ko-full summary{display:none}}
223
+ /* THE LABELS THE READER ACTUALLY READS. ko-lbl above is 9.5px uppercase grey — the owner's
224
+ "what holds what?" was asked of a label in that style, so a new word in the same style is the
225
+ same defect with different letters. These are body size and body colour, and they carry the
226
+ band's own word ("Why High"), which is what makes them answerable without a legend. */
227
+ .ko-lbl2{display:block;font-size:14px;font-weight:700;color:var(--ink);margin:0 0 5px;letter-spacing:0}
228
+ /* The card's fold. ko-full rides with it so openAll() and the beforeprint handler reach it —
229
+ what folds on screen still prints, which is the rule this template already states for the
230
+ narrative fold. ko-why carries only the spacing a card needs inside its own body. */
231
+ .ko-why{margin:6px 0 5px}
232
+ .ko-why summary{font-size:12px;text-transform:none;letter-spacing:0;color:var(--rose,var(--faint));font-weight:600}
233
+ /* "About this request" — what was asked, and any flag on the asking. It sits in the hero because a
234
+ mis-scoped request makes every number below it answer the wrong question. */
235
+ .ko-req{margin:14px 0 0;padding:13px 16px;border:1px solid var(--line);border-radius:3px}
236
+ .ko-req .ko-lbl2{margin-bottom:6px}
237
+ .ko-req p{margin:0 0 6px;font-size:13.5px;color:var(--slate);line-height:1.6}
238
+ .ko-req p:last-child{margin-bottom:0}
239
+ .ko-reqflag{border-left:3px solid var(--rose,var(--line));padding-left:11px;margin-top:9px}
240
+ /* THE PURPLE NOTES COME OFF THE EXPORT, exactly as the clearance page removes its internal notes
241
+ ("Internal (review-only) notes are removed on export"). The knockout's export is window.print()
242
+ via exportPDF(), so print is the whole export path and this rule is the whole strip. Without it
243
+ a knockout PDF forwarded to a client carries the reviewing lawyer's notes. */
244
+ @media print{.internal{display:none !important}}
205
245
  .ko-reg{margin:11px 0 0;padding:8px 0 0;border-top:1px solid var(--line);
206
246
  font-family:var(--mono);font-size:12px;color:var(--faint);line-height:1.5}
207
247
  .ko-filings{padding:14px 0 2px;border-bottom:1px solid var(--line)}
@@ -248,13 +288,13 @@ function glanceSection(marks, framework, registerCounts) {
248
288
  </div>
249
289
  </div>`;
250
290
  }).join('');
251
- // tracker issue 761 — THE FRAMEWORK IS NAMED BESIDE THE CHIPS IT EXPLAINS. Every `.ko-band` chip below speaks one
291
+ // THE FRAMEWORK IS NAMED BESIDE THE CHIPS IT EXPLAINS. Every `.ko-band` chip below speaks one
252
292
  // word out of a framework's vocabulary, and that framework was named exactly once on this page: in the
253
293
  // footer, under everything it governs. This is ATTRIBUTION AND NOTHING ELSE — the same "Rated under
254
294
  // <name>" the footer prints, moved to where the words it licenses are actually read.
255
295
  //
256
296
  // NO LADDER HERE. Spelling the band scale out beside the chips would be new furniture on a client
257
- // deliverable, and tracker issue 765 rules that a rung buys depth, never furniture. If the knockout reader needs the
297
+ // deliverable, and a rung buys depth, never furniture. If the knockout reader needs the
258
298
  // scale, that is its own decision on its own merits, not a rider on this one.
259
299
  //
260
300
  // No framework ⇒ NOTHING. A run this render was given no manifest for gets no attribution invented for
@@ -279,8 +319,111 @@ function glanceSection(marks, framework, registerCounts) {
279
319
  // all-classes figure alongside a class-scoped one would be a second count — one more provider call per
280
320
  // mark per predicate, billable on Corsearch — and that is a spend the owner has not ruled on. Raised as
281
321
  // a follow-up rather than assumed here (tracker issue 717).
322
+ // ── TERRITORIES IN WORDS, NEVER CODES (tracker issue 331 C) ─────────────────────────────────────────
323
+ //
324
+ // The line read "territories: EM, US, WO". On a worldwide run the same line printed every register code
325
+ // the provider offers — roughly two hundred, AD through ZZ, internal groupings among them — which the
326
+ // owner called a meaningless list, correctly: a reader cannot tell anything from two hundred codes and
327
+ // cannot tell much from three.
328
+ //
329
+ // TWO CODES ARE REGISTERS RATHER THAN COUNTRIES and neither is an ISO region, so neither can be resolved
330
+ // by a country table: EM is the EU Intellectual Property Office and WO is WIPO's international register.
331
+ // They are named here because they are the two a reader meets constantly.
332
+ //
333
+ // A CODE THIS TABLE CANNOT NAME IS DROPPED, NOT PRINTED. The provider's internal groupings (XA, XG, XS,
334
+ // XW, ZZ and their like) name no register a reader could look up, and printing one is the defect this
335
+ // change exists to remove. Dropping them is safe because the COUNT of registers is stated separately and
336
+ // is taken before any naming — so a reader is never told about fewer registers than were counted.
337
+ // TWO FORMS PER REGISTER, because English needs both: "Counted in the United States" takes the article
338
+ // and "United States application" refuses it. One map with one form produced "the United States
339
+ // application (pending)" on a card, which is why they are separate fields rather than a regex over one.
340
+ const REGISTER_NAMES = Object.freeze({
341
+ EM: { bare: 'European Union', phrase: 'the European Union' },
342
+ EU: { bare: 'European Union', phrase: 'the European Union' },
343
+ WO: { bare: 'international', phrase: 'the WIPO register' },
344
+ US: { bare: 'United States', phrase: 'the United States' },
345
+ GB: { bare: 'United Kingdom', phrase: 'the United Kingdom' },
346
+ UK: { bare: 'United Kingdom', phrase: 'the United Kingdom' },
347
+ CH: { bare: 'Swiss', phrase: 'Switzerland' },
348
+ });
349
+
350
+ /**
351
+ * A register code as a reader's words, or '' when this build cannot name it. NEVER returns a code.
352
+ * `bare: true` gives the attributive form for a phrase like "United States application".
353
+ */
354
+ function territoryName(code, { bare = false } = {}) {
355
+ const c = String(code ?? '').trim().toUpperCase();
356
+ if (!c) return '';
357
+ if (REGISTER_NAMES[c]) return bare ? REGISTER_NAMES[c].bare : REGISTER_NAMES[c].phrase;
358
+ // The provider's internal groupings all sit in the X* and Z* space and name no lookup-able register.
359
+ if (/^[XZ]/.test(c)) return '';
360
+ try {
361
+ const name = new Intl.DisplayNames(['en'], { type: 'region' }).of(c);
362
+ // Intl hands the CODE back when it knows no region by that name — which would print exactly the
363
+ // thing this function exists to prevent, so an unresolved code is an empty string.
364
+ return name && name.toUpperCase() !== c ? name : '';
365
+ } catch { return ''; }
366
+ }
367
+
368
+ /** "a, b and c" — the reader's list, not a join on commas. */
369
+ function listWords(items) {
370
+ const xs = (items ?? []).filter(Boolean);
371
+ if (xs.length <= 1) return xs[0] ?? '';
372
+ return `${xs.slice(0, -1).join(', ')} and ${xs[xs.length - 1]}`;
373
+ }
374
+
375
+ /**
376
+ * The counted scope, in one sentence (tracker issue 331 C). Three shapes, and the shape is chosen by
377
+ * how many registers were counted rather than by which they were:
378
+ *
379
+ * every register the provider offers -> "Counted worldwide, 190 registers, on Clarivate Compumark."
380
+ * up to six -> named in full
381
+ * more than six, not all -> the count, and where the list is
382
+ *
383
+ * The provider is named here and nowhere else on the page; 331 D takes the vendor's name off the cards.
384
+ */
385
+ const TERRITORY_NAME_CAP = 6;
386
+ function territoriesLine(registerCounts) {
387
+ const provider = registerCounts?.providerLabel ?? registerCounts?.provider ?? 'the register';
388
+ const regions = (registerCounts?.scope?.regions ?? []).filter(Boolean);
389
+ const worldwide = registerCounts?.scope?.worldwide === true || regions.length === 0;
390
+ if (worldwide) {
391
+ const n = regions.length;
392
+ return n
393
+ ? `Counted worldwide, ${n} registers, on ${provider}.`
394
+ : `Counted worldwide on ${provider}.`;
395
+ }
396
+ const named = regions.map(territoryName).filter(Boolean);
397
+ if (named.length && named.length <= TERRITORY_NAME_CAP) {
398
+ return `Counted in ${listWords(named)}, on ${provider}.`;
399
+ }
400
+ return `Counted on ${regions.length} registers, listed on the workbook's Register Counts sheet, on ${provider}.`;
401
+ }
402
+
403
+ /** What the counts do and do not say — the one line that replaces the count-basis paragraph on the page. */
404
+ const COUNTS_READER_LINE = 'Counts include live, pending and dead filings. '
405
+ + 'A count is not a conflict; the cards above say which filings matter.';
406
+
282
407
  function countsSection(marks, registerCounts) {
283
- const head = COUNT_PREDICATES.map((p) => `<th>${esc(p.label)}</th>`).join('');
408
+ // ── THE DEFINITION MOVES INTO THE COLUMN HEADER (tracker issue 331 B) ────────────────────────────
409
+ //
410
+ // The 70-word count-basis paragraph existed because three one-word headers — Identical, Containing,
411
+ // Close variations — did not say what they counted, so the definition had to go somewhere. Put it in
412
+ // the header and the paragraph has nothing left to do.
413
+ //
414
+ // ONE NAME ⇒ THE HEADER NAMES IT, because "Exactly ORBIT" needs no gloss at all. Several names share
415
+ // one table and no header can name one of them, so they keep the general form and each row's own
416
+ // forms line (already rendered, unchanged) carries that row's near-spellings.
417
+ const single = marks.length === 1 ? String(marks[0]?.name ?? '').trim() : '';
418
+ const forms = single
419
+ ? ((countsForMark(registerCounts, single)?.counts?.close?.forms ?? []).map((f) => String(f?.form ?? '').trim()).filter(Boolean))
420
+ : [];
421
+ const headLabel = (pk) => {
422
+ if (pk === 'identical') return single ? `Exactly ${single}` : 'Exactly the name';
423
+ if (pk === 'containing') return single ? `Contains ${single}` : 'Contains the name';
424
+ return forms.length ? `Near-spellings (${forms.join(', ')})` : 'Near-spellings';
425
+ };
426
+ const head = COUNT_PREDICATES.map((p) => `<th>${esc(headLabel(p.key))}</th>`).join('');
284
427
  const rows = marks.map((m) => {
285
428
  const e = countsForMark(registerCounts, m.name);
286
429
  const cells = COUNT_PREDICATES.map((p) => {
@@ -308,11 +451,13 @@ function countsSection(marks, registerCounts) {
308
451
  return `<tr><td><b>${esc(m.name)}</b><br><span class="ko-classes">${esc(scope)}</span></td>${cells}</tr>`
309
452
  + (forms ? `<tr class="ko-forms"><td colspan="${COUNT_PREDICATES.length + 1}">${esc(forms)}</td></tr>` : '');
310
453
  }).join('');
311
- const provider = registerCounts.providerLabel ?? registerCounts.provider ?? 'the register';
312
- const territories = (registerCounts.scope?.regions ?? []).join(', ') || 'worldwide';
454
+ // COUNT_BASIS IS OFF THE PAGE, NOT OUT OF THE RECORD (tracker issue 331 B). It is still written to
455
+ // report-data.json (knockoutReportData -> registerCountBasis) and still on the workbook, which is where
456
+ // 331 says it belongs; what it stops doing is printing twice on a page whose headers now say the same
457
+ // thing in three words each.
313
458
  return `<div class="panel">
314
459
  <div class="ko-counts ko-scroll"><table><thead><tr><th>Name</th>${head}</tr></thead><tbody>${rows}</tbody></table></div>
315
- <p class="ko-basis">${esc(COUNT_BASIS)}<br>Counted on <b>${esc(provider)}</b> · territories: ${esc(territories)}.</p>
460
+ <p class="ko-basis">${esc(COUNTS_READER_LINE)}<br>${esc(territoriesLine(registerCounts))}</p>
316
461
  </div>`;
317
462
  }
318
463
 
@@ -514,6 +659,29 @@ function tierAbsenceLine(registerCounts, probeRan) {
514
659
  // A NAME is deliberately not emitted here for the control to send. `ref` is right there and reads
515
660
  // nicely, but portal-ui/src/contract/reportFrame.ts refuses a mark from the document side on purpose,
516
661
  // and the composite key IS a mark name. The server reads `ref` back off disk once it has resolved.
662
+ /**
663
+ * THE CARD'S DETAIL, FOLDED (tracker issue 331 A.3).
664
+ *
665
+ * Visible on a card: the reference, the name, the band chip, the source chip, the one-sentence read and
666
+ * the evidence links. Folded: the paragraph that argues the band, and on a register card the use-check
667
+ * line and the record link with it.
668
+ *
669
+ * WHY A FOLD AND NOT A CUT. The paragraph is the reasoning, and 331 rejects "shortening by dropping the
670
+ * reasons" in as many words. It is one click away and it still prints — `ko-full` is on the element, so
671
+ * openAll() and the beforeprint handler both reach it and a saved PDF carries the whole argument.
672
+ *
673
+ * EMPTY IN, EMPTY OUT. A card with no detail renders no fold rather than an empty one, which is also the
674
+ * archived-run path: a run whose findings carry no `basis` renders exactly the card it was delivered.
675
+ */
676
+ function whyBandFold(inner) {
677
+ const body = String(inner ?? '').trim();
678
+ if (!body) return '';
679
+ return `<details class="ko-full ko-why"><summary>${esc(WHY_BAND_LABEL)}</summary>${body}</details>`;
680
+ }
681
+
682
+ /** The card fold's own word. Stated once so the renderer and its tests read the same string. */
683
+ const WHY_BAND_LABEL = 'Why this band';
684
+
517
685
  function findingBlock(v, framework, markIndex) {
518
686
  const meta = [v.type, v.owner].filter(Boolean).map((s) => esc(s)).join(' · ');
519
687
  const ev = v.evidence.map((u) => linkOrText(u)).join(' · ');
@@ -546,7 +714,7 @@ function findingBlock(v, framework, markIndex) {
546
714
  </div>
547
715
  ${meta ? `<p class="ko-findmeta">${meta}</p>` : ''}
548
716
  ${v.lead ? `<p class="ko-findnet">${inlineMd(v.lead)}</p>` : ''}
549
- ${v.detail ? `<p class="ko-findbasis">${inlineMd(v.detail)}</p>` : ''}
717
+ ${whyBandFold(v.detail ? `<p class="ko-findbasis">${inlineMd(v.detail)}</p>` : '')}
550
718
  ${ev ? `<p class="ko-findev">Evidence: ${ev}</p>` : ''}
551
719
  </div>
552
720
  </div>
@@ -637,6 +805,28 @@ const REGISTER_ORDINAL_FLOOR = 900;
637
805
  export const NOT_WEIGHED_LINE = 'Register listing — this card carries no rating of its own';
638
806
  const NOT_WEIGHED = NOT_WEIGHED_LINE;
639
807
 
808
+ /**
809
+ * The register card's basis line, which cannot be written without knowing whether the card is rated.
810
+ *
811
+ * THE TWO RULINGS MEET HERE AND THEY ALMOST CONTRADICTED EACH OTHER ON A CLIENT PAGE. tracker issue
812
+ * 1935 retired a code-owned placeholder because a rating nobody performed must not appear; tracker
813
+ * issue 274 then ruled that the rater's OWN band for a promoted filing must appear, because a filing
814
+ * the rater weighed into the verdict was rendering as though it had not been. Both hold, and they hold
815
+ * over different cards.
816
+ *
817
+ * `band` and `read` are independent fields — the band comes from `registerReads[].band` and the read
818
+ * from its `text` — so a filing the rater banded WITHOUT typing a read is reachable, and the first cut
819
+ * of this rendered the band chip beside the sentence "this card carries no rating of its own". The
820
+ * report would have been contradicting itself in one card, in the client's voice.
821
+ *
822
+ * So: the retired line belongs only to a card with no band. A banded card with no read says nothing
823
+ * rather than something false — the chip and the source tag already say what the card is.
824
+ */
825
+ function basisLine(band, read) {
826
+ if (read) return `<p class="ko-findbasis">${esc(read)}</p>`;
827
+ return band ? '' : `<p class="ko-findbasis">${esc(NOT_WEIGHED)}</p>`;
828
+ }
829
+
640
830
 
641
831
  /**
642
832
  * Which listed filings get pointed at. TWO tests, and both fail OPEN — a filing is promoted unless
@@ -654,7 +844,11 @@ const NOT_WEIGHED = NOT_WEIGHED_LINE;
654
844
  * Neither test is a judgment about the filing. Whether any of it blocks anything is lawyer work this
655
845
  * product does not do — the same line register-records.mjs rule 3 has held since the lane shipped.
656
846
  */
657
- function promotableRecords(entry, mark) {
847
+ // EXPORTED so the pipeline can ask the same question the page asks (tracker issue 276). The owner
848
+ // use-check runs on PROMOTED filings only, and "promoted" has to mean here what it means on the report —
849
+ // a second predicate would let the engine search an owner whose filing the report never shows, or skip
850
+ // one it does.
851
+ export function promotableRecords(entry, mark) {
658
852
  const scope = new Set([...(mark?.classesSearched ?? []), ...(entry?.classes ?? [])]
659
853
  .map(Number).filter(Number.isInteger));
660
854
  return (entry?.records ?? []).filter((r) => {
@@ -670,18 +864,52 @@ function promotableRecords(entry, mark) {
670
864
  /** One sentence of what the filing IS, composed from the register's own fields. No adjective that the
671
865
  * record did not supply, and every absent field says it is absent rather than rendering as a blank —
672
866
  * a blank cell and an unstated field read alike to a human and only one is honest. */
867
+ // ── THE OFFICE, NOT THE VENDOR (tracker issue 331 D) ────────────────────────────────────────────────
868
+ //
869
+ // The sentence read "A filing of the identical name on Clarivate Compumark — held by ..., status
870
+ // PENDING, classes 9, 38, 45, territory us." Clarivate Compumark is the search vendor; the filing is at
871
+ // the United States office. Naming the vendor in the position where a reader expects the register says
872
+ // the filing exists somewhere it does not, and prints a raw two-letter territory code besides.
873
+ //
874
+ // The provider is still named — once, in scope, as the data source (territoriesLine), which is what it
875
+ // is. A card states the office, the kind of right, its status, its owner and its classes.
876
+ //
877
+ // STATUS IS THE RECORD'S OWN WORD, lower-cased for the sentence and never re-classified: "pending" here
878
+ // is the register's PENDING, not this renderer's reading of it. A record that states none says so.
879
+ const REGISTRATION_WORDS = /^(?:reg|registered|registration)/i;
880
+
881
+ /** "United States application (pending)" — the office and the kind of right, from the record's own fields. */
882
+ function registerRightPhrase(r) {
883
+ const office = territoryName(r?.territory, { bare: true });
884
+ const status = String(r?.status ?? '').trim();
885
+ // A right whose status names a registration is a registration; anything else is an application. The
886
+ // record's raw word rides in the bracket either way, so the reader never has to trust the reading.
887
+ const kind = REGISTRATION_WORDS.test(status) ? 'registration' : 'application';
888
+ const where = office ? `${office} ` : '';
889
+ // The bracket exists to show the register's OWN word where that word adds something. On a record whose
890
+ // status already names the kind ("REGISTERED" beside "registration") it adds nothing but noise, so it
891
+ // is dropped — the reader has the same fact either way, in one word instead of two.
892
+ // "registration (registered)" and "application (application)" say the kind twice. Both roots are
893
+ // tested rather than stemmed, because a stemmer that turns "registered" and "registration" into one
894
+ // token also turns "pending" and "pended" into one, and "(pending)" is a fact the reader wants.
895
+ const echoes = (kind === 'registration' && REGISTRATION_WORDS.test(status))
896
+ || (kind === 'application' && /^(?:appl|filed?)/i.test(status));
897
+ const bracket = status && !echoes ? ` (${status.toLowerCase()})` : '';
898
+ return `${where}${kind}${bracket}`.trim();
899
+ }
900
+
673
901
  function registerStatement(r, provider) {
674
- const what = r.matchedBasis === 'close'
675
- ? `A filing on ${provider} found under the close variation ${r.matchedForm ?? '—'}`
676
- : `A filing of the identical name on ${provider}`;
902
+ const name = String(r?.mark ?? r?.name ?? '').trim();
677
903
  const classes = (Array.isArray(r.classes) ? r.classes : []).filter((n) => Number.isFinite(Number(n)));
678
- const bits = [
679
- r.owner ? `held by ${r.owner}` : 'proprietor not stated on the record',
680
- r.status ? `status ${r.status}` : 'status not stated on the record',
681
- classes.length ? `class${classes.length === 1 ? '' : 'es'} ${classes.join(', ')}` : 'classes not stated on the record',
682
- r.territory ? `territory ${r.territory}` : null,
683
- ].filter(Boolean);
684
- return `${what} ${bits.join(', ')}.`;
904
+ const lead = [name, registerRightPhrase(r)].filter(Boolean).join(', ');
905
+ const owner = r.owner ? `by ${r.owner}` : 'owner not stated on the record';
906
+ const cls = classes.length
907
+ ? `class${classes.length === 1 ? '' : 'es'} ${classes.join(', ')}`
908
+ : 'classes not stated on the record';
909
+ // A close-variation card says WHICH near-form matched, because the name at the head of the sentence is
910
+ // then not the name the client asked about and a reader must not have to guess which it is.
911
+ const via = r.matchedBasis === 'close' && r.matchedForm ? ` Found under the close variation ${r.matchedForm}.` : '';
912
+ return `${lead} ${owner}, ${cls}.${via}`;
685
913
  }
686
914
 
687
915
  /**
@@ -697,6 +925,72 @@ function registerStatement(r, provider) {
697
925
  * form would point a reader at a row that does not exist. Its receipt is the register record itself,
698
926
  * which the card links.
699
927
  */
928
+ /**
929
+ * WHICH OF A MARK'S REGISTER CARDS THE PAGE DRAWS (tracker issue 331 A.3).
930
+ *
931
+ * THIS FILTERS THE PAGE, NEVER THE RECORD, and that separation is the whole point. 331 rejects dropping
932
+ * anything from report-data.json or the workbook to make the page shorter, so `registerCardViews` still
933
+ * builds every promoted filing and still assigns the ordinals it always did — knockoutReportData reads
934
+ * that, unchanged. This function decides only what is DRAWN.
935
+ *
936
+ * Keeping one projection also keeps the flag path honest: portal-service resolves a flag as
937
+ * `marks[i].findings.find(f => f.ordinal === ordinal)`, so a card on the page carries the ordinal its
938
+ * own data entry has. A card the page does not draw cannot be flagged, because there is nothing to click.
939
+ *
940
+ * ONLY AN EXPLICIT LOWEST-RUNG BAND SUPPRESSES A CARD. Absence never does, and that is the whole rule —
941
+ * there is no second condition and no archived-run special case, because this one covers both.
942
+ *
943
+ * The first cut read an absent band as "lowest" and needed a clause-F escape hatch beside it: if no
944
+ * filing on the mark carried a band, keep them all. That escape hatch is what a wrong default looks
945
+ * like. It handled the run with NO bands and silently mishandled the run with SOME: `band` is optional
946
+ * per row, so a rater who bands the filings that matter and leaves the rest alone is doing what the
947
+ * shape invites — and the mark whose one typed band happened to be the bottom rung lost every card it
948
+ * had, including the filings nobody ruled on.
949
+ *
950
+ * `atLowestBand` below already argues this, one function down: a band it cannot place on the ladder
951
+ * is kept, because "the rater said something, and the safe reading of something we cannot rank is that
952
+ * it is worth pointing at". An absent band is strictly LESS information than an unrankable one, so if
953
+ * unknown keeps, absent keeps. Dropping it would be this renderer deciding a filing is immaterial on
954
+ * the rater's behalf — the same claim tracker issue 274 removed from these cards for the same reason.
955
+ *
956
+ * A run that carries no bands at all still renders every card it was delivered with, which is what
957
+ * clause F asks for; it now falls out of the rule instead of being carved around it.
958
+ */
959
+ function registerCardsOnPage(cards, mark, framework) {
960
+ return cards.filter((v) => !atLowestBand(framework, readBandFor(mark, v?.record?.recordId)));
961
+ }
962
+
963
+ /**
964
+ * DID THE RATER PUT THIS FILING ON THE LADDER'S LOWEST RUNG? (tracker issue 331 A.3)
965
+ *
966
+ * The ladder runs worst-first, so the lowest rung is the LAST entry. A filing the rater put there is one
967
+ * the rater called manageable, and 331 keeps those in the filings table rather than on a card: three
968
+ * cards whose own text says the filing does not bear materially on the rating cost the reader more than
969
+ * they told him.
970
+ *
971
+ * Stated as "is it AT the lowest rung" rather than "is it above it", because that is the only direction
972
+ * a caller may act on. Every other answer — a rung higher up, a word this build cannot place on the
973
+ * run's own ladder, no band at all — is a filing that keeps its card. An unplaceable word is not a band
974
+ * this function may quietly demote: the rater said something, and the safe reading of something we
975
+ * cannot rank is that it is worth pointing at. Absence says even less, so it keeps the card too.
976
+ */
977
+ function atLowestBand(framework, band) {
978
+ const ladder = Array.isArray(framework?.bands) ? framework.bands : [];
979
+ const word = String(band ?? '').trim().toLowerCase();
980
+ if (!word || !ladder.length) return false;
981
+ const lowest = String(ladder[ladder.length - 1]?.label ?? '').trim().toLowerCase();
982
+ return Boolean(lowest) && word === lowest;
983
+ }
984
+
985
+ /** The rater's band for one filing, off the row that names it. `null` when the rater gave none. */
986
+ function readBandFor(mark, recordId) {
987
+ const id = String(recordId ?? '').trim();
988
+ if (!id) return null;
989
+ const row = (Array.isArray(mark?.registerReads) ? mark.registerReads : [])
990
+ .find((x) => String(x?.recordId ?? '').trim() === id);
991
+ return String(row?.band ?? '').trim() || null;
992
+ }
993
+
700
994
  function registerCardViews(mark, framework, registerRecords) {
701
995
  if (!registerRecords || registerRecords.unavailable) return { cards: [], entry: null, promoted: 0 };
702
996
  const entry = recordsForMark(registerRecords, mark?.name);
@@ -728,9 +1022,13 @@ function registerCardViews(mark, framework, registerRecords) {
728
1022
  * is gone; what tells the two apart on the page is where the finding came from, which is the vocabulary
729
1023
  * the clearance reports already use for exactly this.
730
1024
  *
731
- * NO BAND CHIP AND NO `bandStop`, still, and for the reason the old note gave: a rung of the run's ladder
732
- * here would be a rating nobody performed. The rail takes --faint so the card reads off the risk ramp
733
- * rather than on it.
1025
+ * THE BAND CHIP IS DRAWN ONLY WHEN THE RATER SENT ONE, and that condition is the whole rule. The old note
1026
+ * here said "no band chip and no `bandStop`, ever: a rung of the run's ladder would be a rating nobody
1027
+ * performed" which was true for as long as nothing could carry the rater's rating of an individual
1028
+ * filing. `registerReads[].band` now can (tracker issue 274), so the objection is answered where it was
1029
+ * raised: with a band, the rating WAS performed, by the seat, in the framework's own words, and the chip
1030
+ * states it. With no band the old rule stands unchanged — --faint rail, no chip, nothing claimed — and
1031
+ * that is still what every archived run renders.
734
1032
  *
735
1033
  * THE LINE UNDER THE STATEMENT DESCRIBES THE CARD, NOT THE RATING. It used to deny
736
1034
  * that the rating turned on this filing, which this function has no way to know and which a delivered
@@ -759,28 +1057,117 @@ function sourceChips(v) {
759
1057
  return `${commonLaw ? '<span class="src cl">Common law</span>' : ''}${register ? '<span class="src reg">Register</span>' : ''}`;
760
1058
  }
761
1059
 
762
- function registerFindingBlock(v, markIndex, reads = null) {
1060
+ // ── THE REVIEWER'S NOTES, ON THE REPORT ─────────────────────────────────────────────────
1061
+ //
1062
+ // OWNER RULING, 2026-09-07: there is one report and the person who ran Clearotron reads it — keeping
1063
+ // this material off the page "just adds confusion for where data is lost". This replaces the earlier
1064
+ // split under which the notes went to the audit workbook alone (the header of this file and
1065
+ // publish/knockout.mjs carried that rule; both now say what replaced it). The workbook keeps them too:
1066
+ // this adds a surface, it does not move one.
1067
+ //
1068
+ // LABELLED, NEVER MERGED. They render in the established `.internal` purple convention report.css
1069
+ // already defines — the same one the clearance report uses for this material — so a reader can see whose
1070
+ // voice a line is in. Merging them into the client-voiced body would make the reviewer's asides read as
1071
+ // findings about the mark, which is the one way this ruling could produce a worse document.
1072
+ const REVIEWER_NOTES_LEGEND = 'Purple notes are for the reviewing lawyer. Remove them before this goes to the client.';
1073
+
1074
+ // The clearance lane's own label, copied rather than re-worded (tracker issue 276). One spelling across
1075
+ // both products is the point: a reader who has seen it on a clearance report knows what it means here.
1076
+ const USE_CHECK_LABEL = 'Use-check source:';
1077
+
1078
+ /** The owner lookup covering a filing, joined by recordId. `null` when this run owed that filing none. */
1079
+ function ownerCheckFor(ownerChecks, recordId) {
1080
+ const id = String(recordId ?? '').trim();
1081
+ if (!id) return null;
1082
+ return (Array.isArray(ownerChecks) ? ownerChecks : [])
1083
+ .find((c) => (Array.isArray(c?.recordIds) ? c.recordIds : []).some((x) => String(x).trim() === id)) ?? null;
1084
+ }
1085
+
1086
+ // The two-place sort lives in findings-model.mjs (splitKnockoutNotes) because the predelivery lint
1087
+ // needs the same answer: the page files each note, and the reviewer warns a writer whose note will file
1088
+ // the way they did not intend. One reader, so the two can never disagree.
1089
+ /**
1090
+ * "About this request" — what was asked, and any flag on the asking (tracker issue 331 A.1).
1091
+ *
1092
+ * WHAT WAS ASKED IS CODE-OWNED. It is read off the run's own frozen instructed scope, never off model
1093
+ * prose, so the line states the request the run was given rather than a paraphrase of it. An archived run
1094
+ * that carries no such sidecar renders the flags alone, and a run with neither renders nothing at all —
1095
+ * the block never appears empty and never appears with a heading over nothing.
1096
+ *
1097
+ * THE FLAGS ARE THE REVIEWER'S OWN WORDS, unedited. This surface moves them; it does not rewrite them.
1098
+ * They keep the purple convention here exactly as they wear it under the cards, which is also what keeps
1099
+ * them off the export: the strip is one rule on .internal and it reaches both places.
1100
+ */
1101
+ function aboutRequestBlock(scope, requestNotes) {
1102
+ const goods = String(scope?.goods ?? '').trim();
1103
+ const classes = (Array.isArray(scope?.classes) ? scope.classes : []).filter((c) => c || c === 0);
1104
+ const jx = (Array.isArray(scope?.jurisdictions) ? scope.jurisdictions : []).map((t) => territoryName(t)).filter(Boolean);
1105
+ const asked = [
1106
+ classes.length ? `Class${classes.length === 1 ? '' : 'es'} ${classes.join(', ')}` : '',
1107
+ goods,
1108
+ ].filter(Boolean).join(' — ');
1109
+ const where = jx.length ? `Searched in ${listWords(jx)}.` : '';
1110
+ const flags = (requestNotes ?? []).filter(Boolean);
1111
+ if (!asked && !where && !flags.length) return '';
1112
+ return `<div class="ko-req">
1113
+ <span class="ko-lbl2">About this request</span>
1114
+ ${asked ? `<p>${esc(asked)}</p>` : ''}
1115
+ ${where ? `<p>${esc(where)}</p>` : ''}
1116
+ ${flags.length ? `<div class="internal ko-reqflag">${
1117
+ flags.map((n) => `<p class="ko-bul">${inlineMd(n)}</p>`).join('')}</div>` : ''}
1118
+ </div>`;
1119
+ }
1120
+
1121
+ function reviewerNotesBlock(m) {
1122
+ const notes = splitKnockoutNotes(m).name;
1123
+ if (!notes.length) return '';
1124
+ return `<div class="internal">
1125
+ <span class="tag">For the reviewing lawyer</span>
1126
+ ${notes.map((n) => `<p class="ko-bul">${inlineMd(n)}</p>`).join('')}
1127
+ </div>`;
1128
+ }
1129
+
1130
+ function registerFindingBlock(v, markIndex, reads = null, framework = null, ownerChecks = []) {
763
1131
  const r = v.record;
764
1132
  // — THE RATER'S OWN READ OF THIS FILING, when it recorded one. `registerReads` is
765
1133
  // joined to the record store by the validator, so a row that reaches here names a filing this run
766
1134
  // actually holds. With no row the neutral line stands, and the neutral line is TRUE: it describes the
767
1135
  // card. That is why the field can be optional without the page ever asserting something false.
768
- const read = (Array.isArray(reads) ? reads : [])
769
- .map((x) => ({ id: String(x?.recordId ?? '').trim(), text: String(x?.read ?? '').trim() }))
770
- .find((x) => x.id && x.id === String(r?.recordId ?? '').trim())?.text;
1136
+ const row = (Array.isArray(reads) ? reads : [])
1137
+ .map((x) => ({ id: String(x?.recordId ?? '').trim(), text: String(x?.read ?? '').trim(), band: String(x?.band ?? '').trim() }))
1138
+ .find((x) => x.id && x.id === String(r?.recordId ?? '').trim());
1139
+ const read = row?.text;
1140
+ // The band rides the SAME row as the read and is independently optional: a read with no band prints
1141
+ // exactly as it did before this field existed. The validator has already checked the word against the
1142
+ // frozen ladder, so `bandStop` resolves a real stop rather than falling through to a default that would
1143
+ // colour an unknown word as though it were rated.
1144
+ const band = row?.band || null;
1145
+ const stop = band ? bandStop(framework, band) : null;
1146
+ // ── WHERE THE OWNER'S TRADE WAS LOOKED UP (tracker issue 276) ──────────────────────────────────
1147
+ //
1148
+ // The source is the DRIVER'S, joined by this filing's own recordId — never a URL the seat typed. That
1149
+ // is the whole reason a reader can trust it: the row exists because the driver made the call, so
1150
+ // "asserting a no-result without issuing a query" is not a thing this surface can express.
1151
+ //
1152
+ // ABSENT ON A RUN THAT OWED NO CHECK, and that silence is correct: no line at all says nothing, while
1153
+ // a line reading "no result" on a filing nobody was asked about would claim a search that never ran.
1154
+ const useCheck = ownerCheckFor(ownerChecks, r?.recordId);
1155
+ const useCheckSource = useCheck?.source ?? null;
771
1156
  const meta = [r.owner ? esc(r.owner) : 'proprietor not stated', r.territory ? esc(r.territory) : null]
772
1157
  .filter(Boolean).join(' · ');
773
1158
  const receipt = isHttpUrl(r.url) ? linkOrText(r.url) : esc(r.recordId ?? 'no record address supplied');
774
1159
  return `<div class="card ko-find" data-ko-mark="${Number(markIndex)}" data-ko-ord="${Number(v.ordinal)}">
775
1160
  <div class="top">
776
- <div class="rail" style="background:var(--faint)"></div>
1161
+ <div class="rail" style="background:var(${stop ?? '--faint'})"></div>
777
1162
  <div class="body">
778
1163
  <div class="cardhead">
779
- <span class="fnum">${esc(v.ref)}</span><span class="who">${esc(r.mark ?? 'Unnamed filing')}</span><span class="src reg">Register</span>
1164
+ <span class="fnum">${esc(v.ref)}</span><span class="who">${esc(r.mark ?? 'Unnamed filing')}</span>${
1165
+ band ? `<span class="ko-findband" style="background:var(${stop})">${esc(band)}</span>` : ''}<span class="src reg">Register</span>
780
1166
  </div>
781
1167
  <p class="ko-findmeta">${meta}</p>
782
1168
  <p class="ko-findnet">${esc(v.statement)}</p>
783
- <p class="ko-findbasis">${esc(read || NOT_WEIGHED)}</p>
1169
+ ${basisLine(band, read)}
1170
+ ${useCheck ? `<p class="ko-findev">${esc(USE_CHECK_LABEL)} ${isHttpUrl(useCheckSource) ? linkOrText(useCheckSource) : esc(useCheckSource)}</p>` : ''}
784
1171
  <p class="ko-findev">Register record: ${receipt}</p>
785
1172
  </div>
786
1173
  </div>
@@ -900,34 +1287,117 @@ function coverageClause(mark, registerCounts, probeRan) {
900
1287
  // NOTHING IS DISCARDED. Where a run carries both the typed fields and prose bullets the bullets survive
901
1288
  // under a collapsed "Full narrative" — the reader who wants the long form still has it, one click away,
902
1289
  // and no sentence the stage wrote is dropped on the floor.
903
- function readBlock(m) {
1290
+ /**
1291
+ * THE LABELS, IN THE READER'S WORDS (tracker issue 331 A.2).
1292
+ *
1293
+ * The old pair was "What holds it there" and "What would move it", 9.5px grey capitals. The owner read
1294
+ * the first one and asked "what holds what?" — so the fix is not a synonym in the same style, it is a
1295
+ * label that carries the band's own word and answers the question by itself.
1296
+ *
1297
+ * factors → "Why High" (the mark's own band)
1298
+ * counterFactors → "Why not Very High" (the rung ABOVE it on the run's own ladder)
1299
+ * mitigation → "What would lower the risk"
1300
+ *
1301
+ * The ladder runs worst-first, so the rung above is the PREVIOUS entry. At the top rung there is no
1302
+ * higher band to name and the honest label is "What keeps it here" — the same question, asked where the
1303
+ * comparative form has no answer. A band this build cannot find on the ladder gets that label too,
1304
+ * rather than a comparative naming a rung that may not exist.
1305
+ */
1306
+ function counterLabel(framework, band) {
1307
+ const ladder = Array.isArray(framework?.bands) ? framework.bands : [];
1308
+ const i = ladder.findIndex((b) => String(b?.label ?? '').trim().toLowerCase() === String(band ?? '').trim().toLowerCase());
1309
+ const up = i > 0 ? String(ladder[i - 1]?.label ?? '').trim() : '';
1310
+ return up ? `Why not ${up}` : 'What keeps it here';
1311
+ }
1312
+
1313
+ const ASSESSMENT_FOLD_LABEL = 'Read the full assessment';
1314
+
1315
+ /** The scope block's own words, as one string, so the caveat filter reads exactly what the page prints. */
1316
+ const SCOPE_BLOCK_TEXT = 'What this is. A fast screen for obvious blockers to using each name, from '
1317
+ + 'marketplace and web use plus a count of register filings. What it is not. A clearance search. We '
1318
+ + 'drew no register conclusions and give no filing advice. A name that passes here is not clear; it '
1319
+ + 'goes on to clearance. Every conflict above links to the material we found. The audit workbook holds '
1320
+ + 'every search run, every empty result and the working notes. Register data.';
1321
+
1322
+ /** Words that carry no claim, so their presence or absence says nothing about what a sentence asserts. */
1323
+ const STOPWORDS = new Set(['a', 'an', 'and', 'are', 'as', 'at', 'be', 'been', 'but', 'by', 'can', 'do',
1324
+ 'does', 'each', 'for', 'from', 'has', 'have', 'here', 'in', 'is', 'it', 'its', 'no', 'not', 'of', 'on',
1325
+ 'or', 'that', 'the', 'their', 'them', 'there', 'these', 'they', 'this', 'to', 'up', 'was', 'we', 'were',
1326
+ 'what', 'when', 'which', 'will', 'with', 'you', 'your']);
1327
+
1328
+ const contentWords = (s) => String(s ?? '').toLowerCase().replace(/[^a-z0-9\s-]/g, ' ')
1329
+ .split(/\s+/).filter((w) => w.length > 2 && !STOPWORDS.has(w));
1330
+
1331
+ /**
1332
+ * Does this line assert anything the reference text does not already assert?
1333
+ *
1334
+ * TRUE unless every content word in the line is already in the reference. An empty line has nothing to
1335
+ * say and returns false; a line with one unfamiliar word is kept. Singular/plural is folded so that
1336
+ * "conclusion" does not read as new beside "conclusions".
1337
+ */
1338
+ function saysSomethingNew(line, reference) {
1339
+ // The stem must be IDEMPOTENT on the singular, or the fold does nothing: an earlier form stripped
1340
+ // "es" and turned "gives" into "giv" while leaving "give" alone, so the two never matched and every
1341
+ // caveat looked new. Strip one trailing "s" and nothing else.
1342
+ const stem = (w) => w.replace(/ies$/, 'y').replace(/s$/, '');
1343
+ const known = new Set(contentWords(reference).map(stem));
1344
+ const words = contentWords(line);
1345
+ if (!words.length) return false;
1346
+ return words.some((w) => !known.has(stem(w)));
1347
+ }
1348
+
1349
+ function readBlock(m, framework) {
904
1350
  const factors = (m.factors ?? []).filter((s) => typeof s === 'string' && s.trim());
905
1351
  const counter = (m.counterFactors ?? []).filter((s) => typeof s === 'string' && s.trim());
906
1352
  const structured = Boolean(m.basis || factors.length || counter.length || m.mitigation);
907
1353
  const bullets = (m.bullets ?? []).map((b) => `<li>${inlineMd(b)}</li>`).join('');
908
1354
  if (!structured) return bullets ? `<ul class="ko-bul">${bullets}</ul>` : '';
1355
+ const assessment = String(m.assessment ?? '').trim();
1356
+ const band = String(m.rating ?? '').trim();
909
1357
  return [
910
1358
  m.basis ? `<p class="ko-basisline">${inlineMd(m.basis)}</p>` : '',
911
- factors.length ? `<ul class="ko-bul">${factors.map((f) => `<li>${inlineMd(f)}</li>`).join('')}</ul>` : '',
912
- counter.length ? `<div class="ko-counter"><span class="ko-lbl">What holds it there</span><ul class="ko-bul">${
1359
+ factors.length ? `<div class="ko-why-band">${band ? `<span class="ko-lbl2">Why ${esc(band)}</span>` : ''}<ul class="ko-bul">${
1360
+ factors.map((f) => `<li>${inlineMd(f)}</li>`).join('')}</ul></div>` : '',
1361
+ counter.length ? `<div class="ko-counter"><span class="ko-lbl2">${esc(counterLabel(framework, band))}</span><ul class="ko-bul">${
913
1362
  counter.map((f) => `<li>${inlineMd(f)}</li>`).join('')}</ul></div>` : '',
914
- m.mitigation ? `<div class="ko-mitig"><span class="ko-lbl">What would move it</span><p>${inlineMd(m.mitigation)}</p></div>` : '',
915
- bullets ? `<details class="ko-full"><summary>Full narrative</summary><ul class="ko-bul">${bullets}</ul></details>` : '',
1363
+ m.mitigation ? `<div class="ko-mitig"><span class="ko-lbl2">What would lower the risk</span><p>${inlineMd(m.mitigation)}</p></div>` : '',
1364
+ // ── THE ASSESSMENT REPLACES THE "FULL NARRATIVE" FOLD (tracker issue 331 A.2) ────────────────────
1365
+ //
1366
+ // The model writes both. `bullets` (five) say in other words what `factors` (four) already say
1367
+ // visible above; `assessment` is the long-form read under its own four headings, and until now the
1368
+ // page rendered it NOWHERE — validated, carried in report-data.json and the workbook, never shown.
1369
+ // The owner found the bullets fold by accident and called it the most useful block on the page; the
1370
+ // assessment is the better version of the same thing.
1371
+ //
1372
+ // `bullets` is NOT dropped from the record — knockoutReportData still carries it and the workbook
1373
+ // still writes it. What retires is one fold on one page, replaced by a longer answer to the same
1374
+ // question. 331 rejects dropping the field itself, and this does not.
1375
+ //
1376
+ // CLAUSE F. An archived run carries bullets and no assessment: it keeps the fold it was delivered
1377
+ // with, under its own old label. The fallback is the old block, never an empty new one.
1378
+ assessment
1379
+ ? `<details class="ko-full"><summary>${esc(ASSESSMENT_FOLD_LABEL)}</summary><div class="ko-assess">${mdParagraphs(assessment)}</div></details>`
1380
+ : (bullets ? `<details class="ko-full"><summary>Full narrative</summary><ul class="ko-bul">${bullets}</ul></details>` : ''),
916
1381
  ].filter(Boolean).join('');
917
1382
  }
918
1383
 
919
- function analysisSection(marks, framework, { registerCounts = null, probeRan = false, registerRecords = null } = {}) {
1384
+ function analysisSection(marks, framework, { registerCounts = null, probeRan = false, registerRecords = null, ownerChecks = [] } = {}) {
920
1385
  const cards = marks.map((m, markIndex) => {
921
1386
  const stop = bandStop(framework, m.rating);
922
- const bullets = readBlock(m);
1387
+ const bullets = readBlock(m, framework);
923
1388
  // — the promoted register filings, in the SAME list as the common-law conflicts. They render
924
1389
  // after the typed findings and that position is not a rank: a card with no band already sorts last
925
1390
  // under compareKnockoutBlockingPower, so appending them is the ladder's own rule applied to a row
926
1391
  // that has no rung, not a new one invented for the register.
927
1392
  const reg = registerCardViews(m, framework, registerRecords);
928
- const regBlocks = reg.cards.map((v) => registerFindingBlock(v, markIndex, m?.registerReads)).join('');
929
- const overflow = reg.promoted > reg.cards.length
930
- ? `<p class="ko-bul">${esc(`${reg.promoted - reg.cards.length} further filing${reg.promoted - reg.cards.length === 1 ? '' : 's'} for this name met the same test — every one of them is in the filings section below.`)}</p>`
1393
+ const drawn = registerCardsOnPage(reg.cards, m, framework);
1394
+ const regBlocks = drawn.map((v) => registerFindingBlock(v, markIndex, m?.registerReads, framework, ownerChecks)).join('');
1395
+ // The overflow counts what is NOT on the page against what met the promotion test — cards held back
1396
+ // by the cap and cards the rater put on the lowest rung alike, because from the reader's side they
1397
+ // are the same fact: further filings for this name, all of them listed below.
1398
+ const held = reg.promoted - drawn.length;
1399
+ const overflow = held > 0
1400
+ ? `<p class="ko-bul">${esc(`${held} further filing${held === 1 ? '' : 's'} for this name met the same test — every one of them is in the filings section below.`)}</p>`
931
1401
  : '';
932
1402
  // THE DEFECT THIS CLOSES. This line was `(m.findings ??).filter((f) => f?.url)`, and a
933
1403
  // typed finding has no `url` — so every conflict was dropped from the page and a mark whose findings
@@ -950,11 +1420,17 @@ function analysisSection(marks, framework, { registerCounts = null, probeRan = f
950
1420
  <div>
951
1421
  ${body || '<p class="ko-bul">No adverse signals recorded for this name on this screen.</p>'}
952
1422
  ${m.degraded ? `<p class="ko-degraded">${esc(DEGRADED_NOTE)}</p>` : ''}
1423
+ ${reviewerNotesBlock(m)}
953
1424
  <p class="ko-reg">${esc(registerLine(m, registerCounts, probeRan, registerRecords, reg.cards))}</p>
954
1425
  </div>
955
1426
  </div>`;
956
1427
  }).join('');
957
- return `<div class="panel ko-glance">${cards}</div>`;
1428
+ // The legend rides the panel and only when a note is actually on it — a standing sentence explaining a
1429
+ // colour no reader can see would be the report describing a convention it did not use.
1430
+ const anyNotes = marks.some((m) => (Array.isArray(m?.purpleNotes) ? m.purpleNotes : [])
1431
+ .some((n) => String(n ?? '').trim()));
1432
+ const legend = anyNotes ? `<p class="ko-refnote">${esc(REVIEWER_NOTES_LEGEND)}</p>` : '';
1433
+ return `<div class="panel ko-glance">${legend}${cards}</div>`;
958
1434
  }
959
1435
 
960
1436
  // The method line names EXACTLY what ran, and its wording is doctrine — with counts on the page, "register
@@ -1012,87 +1488,6 @@ function depthStrip(note) {
1012
1488
  // says what the lines under it are. It is not a filter and it rewrites no caveat.
1013
1489
  const CAVEAT_LEAD = 'This screen also carries the following limits:';
1014
1490
 
1015
- /**
1016
- * WHAT THIS RUN SEARCHED AND DID NOT, FROM THE RUN'S OWN POLICY.
1017
- *
1018
- * The line this replaces was composed from `registerCounts` — an ARTIFACT — and said "Common-law/
1019
- * marketplace screen only, register searches are addressed separately" whenever no counts were present.
1020
- * That reads the absence of a result as a statement of scope, which is the class this repo keeps paying
1021
- * for: a run INSTRUCTED to take register counts whose provider refused printed the same sentence as a
1022
- * run never instructed to take them at all. One of those is a scope statement and the other is a gap in
1023
- * the record, and a client cannot tell them apart.
1024
- *
1025
- * The clearance report composes its scope from the run's frozen components (`productCoverageNote`,
1026
- * search-policy.mjs) and this now does the same, from the same object, so the two lanes cannot describe
1027
- * the same component in different words.
1028
- *
1029
- * THE ARTIFACT IS STILL REPORTED — separately, and as what it is. Where the policy says a component ran
1030
- * and its output is missing, that is disclosed as a gap rather than folded into "we did not do it".
1031
- *
1032
- * @returns {string[]} lines, most load-bearing first. Empty when there is no policy to read, and the
1033
- * caller falls back to the artifact-derived sentence rather than printing nothing.
1034
- */
1035
- export function knockoutScopeLines(policy, { registerCounts = null, probeRan = false } = {}) {
1036
- const c = policy?.components;
1037
- if (!c) return [];
1038
- const lines = [];
1039
- lines.push('This is a screen, not a clearance search: it triages names against use we can see, '
1040
- + 'and makes no filing recommendation.');
1041
- if (c.registerProbe) {
1042
- const who = registerCounts?.providerLabel ?? registerCounts?.provider ?? null;
1043
- // THE BASIS RIDES WITH THE COMPONENT THAT NEEDS IT, in methodLine's own words rather than new ones.
1044
- // Composing this section from the policy moved methodLine off the page for every run that HAS a
1045
- // policy — which is every live run — and took COUNT_BASIS and "Counting is not searching" with it.
1046
- // That is the sentence saying a count weighs nothing and decides nothing, on the page that prints
1047
- // the counts. `runner.knockout-e2e.test.mjs` caught it and this branch called it pre-existing;
1048
- // it is not — origin/main's renderer passes that arm 3/3 in the same worktree.
1049
- lines.push(registerCounts
1050
- ? `Registers were searched for a hit-count per name${who ? ` on ${who}` : ''}, and the filings behind `
1051
- + `the narrow counts are listed below. ${COUNT_BASIS} Counting is not searching: no register `
1052
- + `conclusions, no filing advice, and a full register clearance is a separate piece of work.`
1053
- : 'Registers were searched for a hit-count per name, and no counts reached this report — '
1054
- + 'that is a gap in the record for this run, not a statement that registers were left out.');
1055
- } else if (c.registerProbe === false) {
1056
- lines.push(probeRan
1057
- ? 'Registers were not part of this search. A register count screen ran separately; full register analysis is its own piece of work.'
1058
- : 'Registers were not searched in this search.');
1059
- }
1060
- // A COMPONENT THE POLICY DOES NOT MENTION IS NOT A COMPONENT THAT WAS OFF. Same rule as the missing
1061
- // policy above, one level down: `undefined` is silence, and silence is not `false`. Reached only when
1062
- // `components` arrives without this key — every row in search-policy.mjs states all three, so the
1063
- // product table cannot produce it; `{ ...policyFor(product)?.components, ...r.components }` can, when
1064
- // the product resolves to nothing. Driven before this line existed: an empty `components` printed
1065
- // "Registers were not searched in this search" on a report that was carrying a register hit-count
1066
- // table, which is the document contradicting itself in the section that exists to state scope. Saying
1067
- // nothing is the honest answer, and it needs no new sentence in front of a client.
1068
- //
1069
- // THE TWO BELOW STAY `!== true`, and the difference is the population rather than the principle.
1070
- // `registerProbe` is true on two knockout rows (knockout-search, knockout-register), so silence about
1071
- // it is genuinely ambiguous. `commonLawGrid` and `jxLanes` are false on EVERY row this template
1072
- // renders — they are what the clearance search is — so silence there states a thing that is true of
1073
- // the whole population, and making them conditional would drop a disclosure the clearance report
1074
- // makes about itself. Symmetry here would cost a client the sentence naming the lane they did not buy.
1075
- // Stated because the clearance report states its own absence of them, in these words. A reader
1076
- // comparing two documents must not have to infer that a lane is missing from its silence.
1077
- if (c.commonLawGrid !== true) {
1078
- lines.push('The systematic common-law grid sweep is not part of this search — that is the clearance search.');
1079
- }
1080
- if (c.jxLanes !== true) {
1081
- lines.push('The per-jurisdiction native-script deep dive is not part of this search.');
1082
- }
1083
- return lines;
1084
- }
1085
-
1086
- function methodLine(registerCounts, probeRan, { citedFindings = 0, uncitedFindings = 0 } = {}) {
1087
- const receipts = (citedFindings && !uncitedFindings)
1088
- ? ` Every conflict named above cites material held in this run's own research payload for that name.`
1089
- : '';
1090
- return (registerCounts
1091
- ? `Common-law/marketplace screen, plus a register HIT-COUNT per name on ${registerCounts.providerLabel ?? registerCounts.provider ?? 'the register'}. ${COUNT_BASIS} `
1092
- + `Counting is not searching: no register conclusions, no filing advice, and a full register clearance is a separate piece of work.`
1093
- : `Common-law/marketplace screen only — register searches are addressed separately${probeRan ? ' (a register count screen ran; full register analysis follows separately)' : ''}. `
1094
- + `A knockout screen is triage, not a clearance search: no register conclusions, no filing advice.`) + receipts;
1095
- }
1096
1491
 
1097
1492
  /**
1098
1493
  * Render the knockout report.
@@ -1114,11 +1509,20 @@ function methodLine(registerCounts, probeRan, { citedFindings = 0, uncitedFindin
1114
1509
  export function renderKnockoutHtml(findings, framework, {
1115
1510
  runId, overall, issued = null, auditFile = null, probeRan = false, registerCounts = null,
1116
1511
  registerRecords = null,
1512
+ // The driver's own record of the owner lookups it ran (tracker issue 276). Defaults to [] so an
1513
+ // archived run that predates the lane renders exactly as it was delivered.
1514
+ ownerChecks = [],
1117
1515
  identity = null, matter = null, homeHref = null, chromeHref = null, depthNote = null,
1118
1516
  // — the run's FROZEN policy, so the scope section states what this run was
1119
1517
  // instructed to do rather than what its artifacts happen to show. Defaults to null, and an archived
1120
1518
  // run that carries none keeps the artifact-derived sentence it was delivered with.
1121
1519
  searchPolicy = null,
1520
+ // — the run's own frozen instructed scope (tracker issue 331 A.1): the goods, classes and
1521
+ // territories the requester asked for, in the requester's words. Defaults to null, and an archived run
1522
+ // that carries no such sidecar renders "About this request" from its request-level notes alone, or not
1523
+ // at all when it has none. Never derived from model prose — this is what was ASKED, not what was said
1524
+ // about it.
1525
+ instructedScope = null,
1122
1526
  // — the run's frozen delivery overlay. It defaults to null, which is the NO-OPINION state and
1123
1527
  // renders the same plain "Privileged & Confidential" this template always printed — so the ~15 unit
1124
1528
  // fixtures and both render-check scripts, none of which pass one, are unchanged by its arrival.
@@ -1145,7 +1549,12 @@ export function renderKnockoutHtml(findings, framework, {
1145
1549
  const allViews = marks.flatMap((m) => knockoutFindingViews(m, { manifest: framework }));
1146
1550
  const citedFindings = allViews.filter((v) => v.evidence.length).length;
1147
1551
  const uncitedFindings = allViews.length - citedFindings;
1148
- const glance = glanceSection(marks, framework, registerCounts);
1552
+ // ── A ONE-NAME PAGE HAS NO INDEX (tracker issue 331 A.6) ────────────────────────────────────────
1553
+ //
1554
+ // The glance row exists to let a reader of a BATCH find the name they care about. With one name there
1555
+ // is nothing to index, and the row printed that name, its band and its classes for the first of three
1556
+ // times before the reader reached a single sentence of reading. On a batch it stays exactly as it was.
1557
+ const glance = marks.length > 1 ? glanceSection(marks, framework, registerCounts) : '';
1149
1558
  const hasCounts = Boolean(registerCounts?.marks?.length);
1150
1559
  // The register block is a table when counts were taken and a SENTENCE when they were not — never
1151
1560
  // nothing. It renders in the place the numbers would have occupied, at the top of the section that
@@ -1154,7 +1563,24 @@ export function renderKnockoutHtml(findings, framework, {
1154
1563
  const counts = hasCounts
1155
1564
  ? countsSection(marks, registerCounts)
1156
1565
  : `<div class="panel"><p class="ko-tier">${esc(tierLine)}</p></div>`;
1157
- const analysis = analysisSection(marks, framework, { registerCounts, probeRan, registerRecords });
1566
+ // ── A CAVEAT THAT SAYS NOTHING THE SCOPE BLOCK HAS NOT SAID IS NOT RENDERED (tracker issue 331 E) ──
1567
+ //
1568
+ // The four model-written caveats on the measured run overlap the fixed text completely — 331 item 9,
1569
+ // and 333 rule 4 ("say it once") fixes it at the source for runs written under that doctrine. This is
1570
+ // the reader for the ones already written.
1571
+ //
1572
+ // THE TEST IS SUBSET, NOT SIMILARITY, and the difference is the whole safety argument. A caveat is
1573
+ // dropped only when EVERY content word in it already appears in the scope block. A caveat making any
1574
+ // new claim brings at least one new content word with it and is kept — so this cannot silently delete
1575
+ // a disclosure, which is the one failure that would matter on a client-facing page. A similarity score
1576
+ // could; that is why there isn't one.
1577
+ const newCaveats = caveats.filter((c) => saysSomethingNew(c, SCOPE_BLOCK_TEXT));
1578
+ const analysis = analysisSection(marks, framework, { registerCounts, probeRan, registerRecords, ownerChecks });
1579
+ // The request-level notes, gathered across every mark on the document and rendered ONCE at the top.
1580
+ // They are about the asking, not about a name, so a batch repeating them per mark would be the same
1581
+ // sentence three times. reviewerNotesBlock renders the rest, under that mark's own cards.
1582
+ const requestNotes = marks.flatMap((m) => splitKnockoutNotes(m).request);
1583
+ const aboutRequest = aboutRequestBlock(instructedScope, requestNotes);
1158
1584
  const provider = hasCounts ? (registerCounts.providerLabel ?? registerCounts.provider ?? 'the register') : null;
1159
1585
  // The filings appendix renders only when the run produced a listing artifact — never on its absence,
1160
1586
  // and never as an empty table. A knockout with no sidecar publishes exactly the counts-only document.
@@ -1165,7 +1591,10 @@ export function renderKnockoutHtml(findings, framework, {
1165
1591
  // promoted filing that sentence sent a reader to a row that does not exist. A false pointer on the
1166
1592
  // audit trail is worse than no pointer, and the honest fix is to narrow the claim rather than mint a
1167
1593
  // second drill-through key for a card whose receipt is the register record itself.
1168
- const registerCardCount = marks.reduce((n, m) => n + registerCardViews(m, framework, registerRecords).cards.length, 0);
1594
+ // Counted over what the page DRAWS: the sentence explains a REG reference a reader can see, so a run
1595
+ // whose register cards are all held back needs no sentence about them.
1596
+ const registerCardCount = marks.reduce((n, m) =>
1597
+ n + registerCardsOnPage(registerCardViews(m, framework, registerRecords).cards, m, framework).length, 0);
1169
1598
  const hasRecords = Boolean(registerRecords && (registerRecords.marks?.length || registerRecords.unavailable));
1170
1599
  const filings = hasRecords ? filingsSection(marks, registerRecords) : '';
1171
1600
  const onFieldSec = `<div class="sec"><span class="num">${num()}</span><h2>On-field conflicts</h2><span class="note">${hasCounts ? `register hit-counts (${esc(provider)}) and the read, per name` : 'what we found, per name'}</span></div>
@@ -1226,6 +1655,7 @@ window.addEventListener('beforeprint',o);})();</script>
1226
1655
  ${depthStrip(depthNote)}
1227
1656
  <h1 class="mark">${esc(title)}</h1>
1228
1657
  ${productContext ? `<p class="ko-classes" style="font-size:13.5px;margin:0 0 16px">${inlineMd(productContext)}</p>` : ''}
1658
+ ${aboutRequest}
1229
1659
  ${summary ? `<div class="sub">${mdParagraphs(summary)}</div>` : ''}
1230
1660
  </header>
1231
1661
 
@@ -1247,14 +1677,31 @@ window.addEventListener('beforeprint',o);})();</script>
1247
1677
  // FROM THE POLICY WHERE THERE IS ONE (tracker issue 1935). The fallback is not a tidy default: an
1248
1678
  // archived run froze no policy, and re-rendering it must not invent a scope claim about a run
1249
1679
  // nobody can now ask. It keeps the sentence it was delivered with.
1250
- const lines = knockoutScopeLines(searchPolicy, { registerCounts, probeRan });
1251
- if (!lines.length) return `<p class="ko-scope">${esc(methodLine(registerCounts, probeRan, { citedFindings, uncitedFindings }))}</p>`;
1252
- const receipts = citedFindings && !uncitedFindings
1253
- ? '<p class="ko-scope">Every conflict named above cites material held in this run\'s own research payload for that name.</p>'
1680
+ // ── THE SCOPE BLOCK, WHOLE (tracker issue 331 E) ──────────────────────────────────────────────
1681
+ //
1682
+ // The composed lines said one thing five times in 362 words: "not a clearance" three times,
1683
+ // "proceeds to clearance" twice, and the count-basis sentence a second time after the counts table
1684
+ // had already carried it. This is the owner's replacement text, and its two halves are the two
1685
+ // questions a reader of a screen actually has.
1686
+ //
1687
+ // "Every conflict above links to the material we found" STAYS CONDITIONAL and that is not a
1688
+ // stylistic carry-over: said unconditionally it is an absence claim wider than what was examined,
1689
+ // which this file refuses elsewhere in the same words. It prints only when every rendered conflict
1690
+ // cites something.
1691
+ const linked = citedFindings && !uncitedFindings
1692
+ ? ' Every conflict above links to the material we found.'
1693
+ : '';
1694
+ const source = registerCounts
1695
+ ? ` Register data: ${registerCounts.providerLabel ?? registerCounts.provider ?? 'the register'}.`
1254
1696
  : '';
1255
- return `${lines.map((l) => `<p class="ko-scope">${esc(l)}</p>`).join('')}${receipts}`;
1697
+ return `<p class="ko-scope"><b>What this is.</b> A fast screen for obvious blockers to using each `
1698
+ + `name, from marketplace and web use plus a count of register filings.</p>`
1699
+ + `<p class="ko-scope"><b>What it is not.</b> A clearance search. We drew no register conclusions `
1700
+ + `and give no filing advice. A name that passes here is not clear; it goes on to clearance.</p>`
1701
+ + `<p class="ko-scope">${linked.trim()}${linked ? ' ' : ''}The audit workbook holds every search run, `
1702
+ + `every empty result and the working notes.${source}</p>`;
1256
1703
  })()}
1257
- ${caveats.length ? `<p class="ko-scope" style="border-top:1px solid var(--line)">${CAVEAT_LEAD}<br>${caveats.map((c) => inlineMd(c)).join('<br>')}</p>` : ''}
1704
+ ${newCaveats.length ? `<p class="ko-scope" style="border-top:1px solid var(--line)">${CAVEAT_LEAD}<br>${newCaveats.map((c) => inlineMd(c)).join('<br>')}</p>` : ''}
1258
1705
  ${auditFile ? `<p class="ko-scope" style="border-top:1px solid var(--line)"><a href="${escAttr(auditFile)}">Download the audit workbook (Excel)</a> — every search run, every negative result, and the working notes behind these ratings.${
1259
1706
  citedFindings ? ` The reference beside each common-law conflict above (for example <span class="mono">${esc(firstRef(marks, framework))}</span>) is its row on the workbook's Findings sheet.${
1260
1707
  registerCardCount ? ` A <span class="mono">REG</span> reference is a register filing rather than a common-law conflict — it has no Findings row, and its receipt is the register record the card links.` : ''}` : ''}</p>` : ''}
@@ -1282,7 +1729,7 @@ window.addEventListener('beforeprint',o);})();</script>
1282
1729
  * where the working notes live, and a data file that quietly re-admitted them would reopen exactly the
1283
1730
  * leak this series closed.
1284
1731
  */
1285
- export function knockoutReportData(findings, framework, { runId, codename, overall, issued, identity, registerCounts, registerRecords = null, url, auditFile, customerKey, matter }) {
1732
+ export function knockoutReportData(findings, framework, { runId, codename, overall, issued, identity, registerCounts, registerRecords = null, ownerChecks = [], url, auditFile, customerKey, matter }) {
1286
1733
  const marks = findings?.marks ?? [];
1287
1734
  return {
1288
1735
  schema: 'report-data/1',
@@ -1324,6 +1771,39 @@ export function knockoutReportData(findings, framework, { runId, codename, overa
1324
1771
  classesDriving: m.classesDriving ?? [],
1325
1772
  degraded: Boolean(m.degraded),
1326
1773
  points: m.bullets ?? [],
1774
+ // ── THE ASSESSMENT THE RUN ALREADY WROTE (tracker issue 274) ──────────────────────────────
1775
+ //
1776
+ // Seven keys the assess stage writes into knockout-findings.json reached this file as nothing.
1777
+ // They were not withheld by a rule — no code decided against them; this projection is an explicit
1778
+ // whitelist and nobody had added them, so an assistant drafting from this file could not see the
1779
+ // reasoning behind a rating it was drafting about.
1780
+ //
1781
+ // `assessment` is the sharpest case: publish substitutes it into `batch.executiveSummary` ONLY on
1782
+ // a multi-mark batch, so on a single-mark run — the common knockout — the mark's own opening read
1783
+ // was written, validated, and then dropped by both surfaces at once.
1784
+ //
1785
+ // `purpleNotes` is the reviewer's notes, and it is here by the owner's ruling of 2026-09-07: one
1786
+ // report, read by the person who ran Clearotron, because keeping material off it "just adds
1787
+ // confusion for where data is lost". They are LABELLED on the page rather than merged into the
1788
+ // client-voiced body — see reviewerNotesBlock — so a reader can tell whose voice they are in.
1789
+ assessment: (() => { const t = String(m.assessment ?? '').trim(); return t || null; })(),
1790
+ counterFactors: m.counterFactors ?? [],
1791
+ mitigation: (() => { const t = String(m.mitigation ?? '').trim(); return t || null; })(),
1792
+ // `null`, not `false`, when the rater said nothing: a crowded field is a mitigant the reasoning
1793
+ // turns on, and an unstated one must not read as a stated "no".
1794
+ crowdedField: typeof m.crowdedField === 'boolean' ? m.crowdedField : null,
1795
+ reviewerNotes: m.purpleNotes ?? [],
1796
+ // The proof-of-search rows — the same defensibility record list_searches answers from.
1797
+ negatives: (m.negatives ?? []).map((n) => ({
1798
+ term: n?.term ?? null, source: n?.source ?? null, note: n?.note ?? null,
1799
+ })),
1800
+ // The rater's read of a filing it weighed, keyed by the record id the card prints, so a consumer
1801
+ // can join a read to its filing without matching on prose.
1802
+ registerReads: (m.registerReads ?? []).map((x) => ({
1803
+ recordId: x?.recordId ?? null,
1804
+ read: x?.read ?? null,
1805
+ band: String(x?.band ?? '').trim() || null,
1806
+ })),
1327
1807
  // — the typed finding, ranked, with the reference the report prints. The old key was
1328
1808
  // `evidence[]` and it was `(m.findings ?? []).filter((f) => f?.url)`: a typed finding has no
1329
1809
  // `url`, so this file — the one the assistant drafts client mail from — silently carried zero
@@ -1345,14 +1825,36 @@ export function knockoutReportData(findings, framework, { runId, codename, overa
1345
1825
  // A promoted filing the brief omitted would be the report and the brief disagreeing about
1346
1826
  // what the run found.
1347
1827
  //
1348
- // `band: null` and `shape: 'register'` are the load-bearing fields: a consumer can tell a
1349
- // weighed conflict from a pointed-at filing without parsing prose, and `net` carries the
1350
- // not-weighed sentence so a consumer that reads only `net` still cannot overstate it.
1351
- ...registerCardViews(m, framework, registerRecords).cards.map((v) => ({
1352
- ref: v.ref, ordinal: v.ordinal, name: v.record.mark, owner: v.record.owner, band: null,
1353
- type: 'Register filing', net: `${v.statement} It was ${NOT_WEIGHED}.`, basis: NOT_WEIGHED,
1354
- evidence: v.evidence, shape: 'register',
1355
- })),
1828
+ // `shape: 'register'` is the load-bearing field: a consumer can tell a weighed conflict from a
1829
+ // pointed-at filing without parsing prose.
1830
+ //
1831
+ // `band` AND `basis` NOW CARRY THE RATER'S READ WHEN IT SENT ONE (tracker issue 274). Before
1832
+ // this, both were constants `band: null` and the not-weighed sentence — on every filing of
1833
+ // every run, including runs where the seat had written a full read of that exact filing and the
1834
+ // HTML card was already printing it. The page and this file disagreed, and this file is the one
1835
+ // the MCP brief answers from, so the surface a reader asks "what did you make of that
1836
+ // registration?" was the surface that had dropped the answer.
1837
+ //
1838
+ // The unmatched case is unchanged and must stay that way: no row for this recordId ⇒ `band:
1839
+ // null` and the not-weighed line, which describes the card and claims nothing.
1840
+ ...registerCardViews(m, framework, registerRecords).cards.map((v) => {
1841
+ const row = (Array.isArray(m?.registerReads) ? m.registerReads : [])
1842
+ .find((x) => String(x?.recordId ?? '').trim() && String(x?.recordId ?? '').trim() === String(v.record?.recordId ?? '').trim());
1843
+ const read = String(row?.read ?? '').trim() || null;
1844
+ const band = String(row?.band ?? '').trim() || null;
1845
+ // The driver's own record of where this filing's proprietor was looked up (tracker issue
1846
+ // 276), joined by recordId. `null` where the run owed no check — the same three states the
1847
+ // card has, so a consumer can tell "searched and found nothing" from "never searched".
1848
+ const oc = ownerCheckFor(ownerChecks, v.record?.recordId);
1849
+ return {
1850
+ ref: v.ref, ordinal: v.ordinal, name: v.record.mark, owner: v.record.owner, band,
1851
+ type: 'Register filing',
1852
+ net: read ? v.statement : `${v.statement} It was ${NOT_WEIGHED}.`,
1853
+ basis: read ?? NOT_WEIGHED,
1854
+ useCheckSource: oc?.source ?? null,
1855
+ evidence: v.evidence, shape: 'register',
1856
+ };
1857
+ }),
1356
1858
  ],
1357
1859
  registerCounts: counted?.counts
1358
1860
  ? {