clearotron 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +64 -37
- package/.nvmrc +1 -1
- package/CONTRIBUTING.md +8 -4
- package/INSTALL.md +36 -14
- package/README.md +18 -22
- package/THIRD-PARTY-NOTICES.md +1 -1
- package/bin/clearotron.mjs +22 -0
- package/bin/example.mjs +10 -5
- package/bin/onboard.mjs +370 -28
- package/bin/start.mjs +220 -12
- package/build-info.json +2 -2
- package/demo/README.md +1 -1
- package/demo/full-country-search/run/_driver/framework.json +1 -1
- package/demo/global-preliminary-search/run/_driver/framework.json +1 -1
- package/demo/knockout-search/run/_driver/framework.json +1 -1
- package/demo/multi-country-focus-search/run/_driver/framework.json +1 -1
- package/docs/E2E.md +5 -5
- package/docs/GLOSSARY.md +85 -0
- package/docs/PORTAL.md +2 -2
- package/docs/README.md +6 -4
- package/docs/SECURITY.md +17 -0
- package/docs/architecture/01-product-overview.md +21 -9
- package/docs/architecture/04-configuration-reference.md +2 -0
- package/docs/architecture/05-config-governance.md +19 -0
- package/docs/architecture/05-customer-profiles.md +2 -2
- package/docs/configuration.md +4 -3
- package/docs/decisions/0006-what-the-public-repository-carries.md +30 -5
- package/driver/CHANGELOG.md +90 -0
- package/driver/README.md +25 -6
- package/driver/common-law-receipts.mjs +1 -1
- package/driver/config-staleness.mjs +1 -1
- package/driver/connotation-search.mjs +1 -1
- package/driver/contract-audit.mjs +5 -1
- package/driver/contract-e3-baseline.json +11 -11
- package/driver/dev-portal.mjs +2 -2
- package/driver/doubt-selection.mjs +1 -1
- package/driver/drainer-identity.mjs +1 -1
- package/driver/driver.config.mjs +1 -1
- package/driver/effort-model.mjs +2 -2
- package/driver/engine/cli-version.mjs +103 -0
- package/driver/engine/probe.mjs +45 -5
- package/driver/enqueue-schema.mjs +8 -8
- package/driver/feedback-store.mjs +1 -1
- package/driver/findings-model.mjs +52 -2
- package/driver/flag-snapshot.mjs +20 -0
- package/driver/framework.mjs +1 -1
- package/driver/gateway.mjs +20 -3
- package/driver/outbox-backoff.mjs +1 -1
- package/driver/package.json +3 -3
- package/driver/party-facts.mjs +2 -2
- package/driver/phase0.mjs +1 -1
- package/driver/pipeline.mjs +211 -106
- package/driver/plain-register.mjs +134 -0
- package/driver/portal-access.mjs +1 -1
- package/driver/portal-config-view.mjs +70 -2
- package/driver/portal-families.mjs +1 -1
- package/driver/portal-local-auth.mjs +5 -1
- package/driver/portal-report.mjs +1 -1
- package/driver/portal-service.mjs +79 -9
- package/driver/portal-upstream.mjs +2 -2
- package/driver/predelivery-lint.mjs +151 -2
- package/driver/profile-page.html +10 -2
- package/driver/profile-service.mjs +4 -4
- package/driver/profiles/README.md +3 -3
- package/driver/profiles.mjs +144 -12
- package/driver/publish/index.mjs +2 -2
- package/driver/publish/knockout.mjs +7 -1
- package/driver/publish/render-knockout.mjs +453 -118
- package/driver/publish/render.mjs +111 -16
- package/driver/reference-strip-signatures.mjs +20 -0
- package/driver/search-policy.mjs +3 -3
- package/driver/skills/blind-frame/SKILL.md +1 -1
- package/driver/skills/knockout-assess/SKILL.md +84 -0
- package/driver/skills/matter-frame/SKILL.md +6 -6
- package/driver/skills/matter-frame/watchlist-reference.md +2 -2
- package/driver/skills/prelim-common-law/SKILL.md +1 -1
- package/driver/skills/prelim-search/SKILL.md +1 -1
- package/driver/skills/prelim-search/report-prose.md +82 -0
- package/driver/skills/prelim-search/risk-framework-demo.manifest.json +1 -1
- package/driver/skills/prelim-search/risk-framework-triage.manifest.json +1 -1
- package/driver/skills/prelim-search/risk-framework.manifest.json +1 -1
- package/driver/skills/prelim-search/synthesis-rules.md +1 -1
- package/driver/skills/prelim-variants/SKILL.md +3 -3
- package/driver/stage-context.mjs +13 -0
- package/driver/stages.mjs +54 -7
- package/driver/suite-census.json +220 -58
- package/driver/systemd/clearotron-worker.service +3 -3
- package/driver/tokens.mjs +1 -1
- package/driver/unit-inventory.mjs +214 -27
- package/driver/verify.mjs +1 -1
- package/driver/whatif-memo-run.mjs +26 -4
- package/driver/whatif-memo.mjs +19 -1
- package/examples/README.md +2 -2
- package/examples/grants.example.json +6 -7
- package/mcp-server/CHANGELOG.md +4 -0
- package/mcp-server/lib/scrub.mjs +1 -1
- package/mcp-server/lib/whatif.mjs +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/packs/ops/EXAMPLES.md +2 -2
- package/mcp-server/server.mjs +1 -1
- package/package.json +31 -19
- package/portal-ui/dist/assets/{index-Ct3iQtZ2.css → index-1ziUJX1E.css} +43 -2
- package/portal-ui/dist/assets/{index-BUGgiYtF.js → index-KFAHMgdT.js} +289 -73
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/_shared/term-shape.mjs +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/uspto-local/src/index-store.js +2 -2
- package/scripts/added-reference-check.mjs +45 -89
- package/scripts/citation-line-check.mjs +121 -2
- package/scripts/e2e.mjs +2 -2
- package/scripts/env-classify.mjs +67 -3
- package/scripts/generated-files-are-current.mjs +125 -0
- package/scripts/live-surface-check.mjs +38 -1
- package/scripts/mint-names-in-force.mjs +4 -2
- package/scripts/mint-public-residue.mjs +74 -0
- package/scripts/mint-reference-strip-backlog.mjs +12 -1
- package/scripts/mint-suite-census.mjs +5 -2
- package/scripts/nightly-notice.mjs +0 -2
- package/scripts/preinstall-node-check.mjs +24 -0
- package/scripts/render-check.mjs +13 -2
- package/scripts/report-frame-check.mjs +8 -1
- package/scripts/report-screenshot.mjs +6 -3
- package/scripts/revisit-render-check.mjs +6 -3
- package/scripts/score.mjs +1 -1
- package/scripts/strip-tracker-citations.mjs +38 -7
- package/scripts/test-full.mjs +226 -0
- package/scripts/test-run.mjs +16 -0
- package/shared/README.md +2 -1
- package/shared/anon-overlay.mjs +3 -3
- package/shared/browser-temp-root.mjs +142 -0
- package/shared/checkout-move.mjs +1 -1
- package/shared/client-door.mjs +4 -3
- package/shared/customer-store.mjs +1 -1
- package/shared/identifier-scan.mjs +1 -1
- package/shared/install-auth.mjs +35 -0
- package/shared/invocation.mjs +33 -1
- package/shared/listen.mjs +55 -3
- package/shared/names-in-force.mjs +6 -1
- package/shared/node-floor.mjs +97 -0
- package/shared/os-advice.mjs +91 -0
- package/shared/reap-on-exit.mjs +42 -0
- package/shared/reference-guard-classes.mjs +351 -0
- package/shared/staff-domain.mjs +154 -0
- package/shared/store-in-repo.mjs +23 -2
- package/shared/suite-census.mjs +31 -3
- package/shared/withheld-paths-access.mjs +36 -18
- package/skills/clearotron-ops/SKILL.md +1 -1
- package/driver/recipes/README.md +0 -49
- package/driver/skills/prelim-search/risk-framework-aurora.manifest.json +0 -19
- package/driver/skills/prelim-search/risk-framework-aurora.md +0 -77
- package/driver/skills/prelim-search/risk-framework-zephyr.manifest.json +0 -14
- package/driver/skills/prelim-search/risk-framework-zephyr.md +0 -33
- package/driver/skills/prelim-search/worked-examples-aurora.md +0 -10
- package/driver/skills/prelim-search/worked-examples-zephyr.md +0 -10
- package/scripts/ai-page-render-check.mjs +0 -532
- package/scripts/clearances-render-check.mjs +0 -480
- package/scripts/composer-render-check.mjs +0 -1027
- package/scripts/home-render-check.mjs +0 -526
- package/scripts/portal-lifecycle-check.mjs +0 -639
|
@@ -416,7 +416,7 @@ const useEvidence = (m) => [USE_EVIDENCE_LABEL[m?._status], USE_SOURCE_LABEL[m?.
|
|
|
416
416
|
// EXACT EQUALITY, exactly as EVIDENCE_LABEL maps `_status`. The sentinel itself does not move: archived
|
|
417
417
|
// runs carry the old value forever and a fourth spelling of it would have to be accepted everywhere.
|
|
418
418
|
const USE_CHECK_NO_RESULT = 'perplexity_research — no result';
|
|
419
|
-
const USE_CHECK_NO_RESULT_CITE = '
|
|
419
|
+
const USE_CHECK_NO_RESULT_CITE = 'Nothing found in the marketplaces searched.';
|
|
420
420
|
const USE_CHECK_NO_RESULT_SHORT = 'marketplace search — no result found';
|
|
421
421
|
// — MATCHED ON NORMALISED PUNCTUATION, NOT ONE SPELLING. The constant itself does not
|
|
422
422
|
// move (archived runs carry it forever, the validators name it), but the SEAT emitted a hyphen where
|
|
@@ -456,8 +456,8 @@ function meter(name, entry) {
|
|
|
456
456
|
// (emitted by the report-card stage, parsed into card.meta.ord) and the driver already orders +
|
|
457
457
|
// provenance-checks cards by it — so match on THAT exact, injective key. The old fuzzy owner/mark
|
|
458
458
|
// containment collapsed distinct findings onto one card (`.find()` first-match): two findings sharing
|
|
459
|
-
// a mark, or whose mark is a substring of another's ("
|
|
460
|
-
// rendering one card's prose under every finding (the
|
|
459
|
+
// a mark, or whose mark is a substring of another's ("nova" ⊂ "novadex"), all matched the first card,
|
|
460
|
+
// rendering one card's prose under every finding (the "02 On-field conflicts" duplication). The
|
|
461
461
|
// fuzzy match is kept ONLY as a fallback for legacy artifacts whose cards predate the `- ord:` line.
|
|
462
462
|
function matchCard(finding, cards) {
|
|
463
463
|
const list = cards || [];
|
|
@@ -852,7 +852,7 @@ function plainScopeNote(text) {
|
|
|
852
852
|
if (!t) return '';
|
|
853
853
|
return stripTelemetry(t).trim(); // trim: an all-telemetry note leaves only newlines, and '' is falsy
|
|
854
854
|
}
|
|
855
|
-
function scopeSection(ranBucket, coverage, coverageJudgment, methodologyText, contextNotes, fm = {}, hasRecordSet = false, hasCards = false) {
|
|
855
|
+
function scopeSection(ranBucket, coverage, coverageJudgment, methodologyText, contextNotes, fm = {}, hasRecordSet = false, hasCards = false, hasIndexEntry = false) {
|
|
856
856
|
const parts = [];
|
|
857
857
|
// B3 (spec 2026-07-30 §4) — record provenance, stated ONCE, here, instead of a hedge stamped on
|
|
858
858
|
// every card. This is the single home of what "fetched", "register-index entry" and "inferred"
|
|
@@ -868,7 +868,11 @@ function scopeSection(ranBucket, coverage, coverageJudgment, methodologyText, co
|
|
|
868
868
|
// conditions: a copy is a thing that drifts, and over-including costs one explanatory paragraph in a
|
|
869
869
|
// collapsed section while under-including costs a reader an unexplained label. The fetched-records
|
|
870
870
|
// sentence stays conditional on hasRecordSet, so a run WITH a record set is byte-identical to B3.
|
|
871
|
-
|
|
871
|
+
// THREE SENTENCES EXPLAINING ONE WORD, and the middle one printed on every report whether or not the
|
|
872
|
+
// page had a register-index entry on it — a definition of a label the reader could not see. It renders
|
|
873
|
+
// now only where such an entry does, and the remaining two say what "inferred" means in the words a
|
|
874
|
+
// reader would use for it rather than in the renderer's.
|
|
875
|
+
if (hasRecordSet || hasCards) parts.push(`<p class="scoperead" style="margin:0 0 4px;font-weight:600">Record provenance</p><p class="provnote" style="margin:0 0 6px;font-size:13px">${hasRecordSet ? 'Registration numbers on the cards were read from the register records. ' : ''}${hasIndexEntry ? 'A registration shown as a register-index entry was seen in the register index; its full record was not pulled. ' : ''}“Inferred” beside an owner’s likelihood to object means we judged it from what the owner sells and holds; we had no enforcement history to read.</p>`);
|
|
872
876
|
// — this is the one part of §4 that does NOT fold. Same markup, same heading, same marker; it is
|
|
873
877
|
// emitted beside the <details> instead of inside it, wrapped in the panel the only-you section already
|
|
874
878
|
// uses so it reads as a region of the page rather than a stray heading.
|
|
@@ -896,7 +900,11 @@ function scopeSection(ranBucket, coverage, coverageJudgment, methodologyText, co
|
|
|
896
900
|
+ `<p class="covnone" style="margin:0 0 6px;font-size:13px">No coverage record was produced for this run. `
|
|
897
901
|
+ `This section normally lists what each search covered and what is still open; its absence here is a gap `
|
|
898
902
|
+ `in the record, not a finding that nothing is open. Ask us before relying on it.</p>`);
|
|
899
|
-
|
|
903
|
+
// THE INTERNAL COVERAGE READ IS NOT RENDERED. It concatenated the engine's own search-unit names into
|
|
904
|
+
// about a thousand characters of prose — and on the measured run it ended mid-word, because it is a
|
|
905
|
+
// machine's working note and nothing was reading it as a sentence. Every fact in it is already in the
|
|
906
|
+
// coverage cells directly above, in plain words. It stays in the run's artifacts and in the workbook,
|
|
907
|
+
// where the reader is someone who wants it.
|
|
900
908
|
const meth = plainScopeNote(methodologyText);
|
|
901
909
|
if (meth) parts.push(`<p class="scoperead" style="margin:10px 0 4px;font-weight:600">How this search was run</p><div class="methnote" style="font-size:13px">${renderProse(meth)}</div>`);
|
|
902
910
|
const cn = contextNotesBlock(contextNotes);
|
|
@@ -1230,11 +1238,16 @@ function fullDetail(f, card, recordsByUri = new Map()) {
|
|
|
1230
1238
|
// ("Download full audit (Excel)"), because that is the string the reader hunts for on
|
|
1231
1239
|
// the page. NOT an inline .xlsx link: portal-report.mjs strips those, correctly — the
|
|
1232
1240
|
// portal REPLACES them with its own download control (portal-ui Result.tsx).
|
|
1233
|
-
// placeholder — a register UI exists and we do not know its per-record address.
|
|
1234
|
-
//
|
|
1241
|
+
// placeholder — a register UI exists and we do not know its per-record address. It CARRIES NO NOTE:
|
|
1242
|
+
// the number stands on its own, because "(placeholder)" beside twenty-four
|
|
1243
|
+
// registrations reads to a client as a broken report rather than as a missing link.
|
|
1244
|
+
// THE PLACEHOLDER NOTE IS GONE. It printed " — no record link available yet (placeholder)" beside
|
|
1245
|
+
// every registration a register UI has no per-record address for — twenty-four times on the measured
|
|
1246
|
+
// page — and a client reads "placeholder" as a broken report. The number is the fact; when there is a
|
|
1247
|
+
// link the number IS the link, and when there is not, the number still stands on its own. The
|
|
1248
|
+
// workbook note stays: it tells a reader where the full record actually is.
|
|
1235
1249
|
const NO_LINK_NOTE = {
|
|
1236
1250
|
workbook: ' — full record in the audit workbook (“Download full audit (Excel)”)',
|
|
1237
|
-
placeholder: ' — no record link available yet (placeholder)',
|
|
1238
1251
|
};
|
|
1239
1252
|
const regUri = (u, fb) => {
|
|
1240
1253
|
const h = regHref(u);
|
|
@@ -1350,7 +1363,11 @@ function fullDetail(f, card, recordsByUri = new Map()) {
|
|
|
1350
1363
|
// D7 — the code-owned "searched, nothing found" sentinel becomes client words HERE, by exact
|
|
1351
1364
|
// equality against the one constant. Any other value is a source string and rides through untouched.
|
|
1352
1365
|
const useSrc = isUseCheckNoResult(f.use_check?.source) ? USE_CHECK_NO_RESULT_CITE : f.use_check?.source;
|
|
1353
|
-
|
|
1366
|
+
// NO EVIDENCE TAG ON AN EMPTY RESULT. The line read "Use checked. Marketplace search run — no result
|
|
1367
|
+
// found. Evidence: inferred", and "inferred" beside "no result" reads as a contradiction: it qualifies
|
|
1368
|
+
// how a FINDING was established, and there is no finding here. Nothing was found, and that is the
|
|
1369
|
+
// whole of what the line has to say.
|
|
1370
|
+
const useChk = cite(useSrc, 'Use checked.', isUseCheckNoResult(f.use_check?.source) ? null : useStatus);
|
|
1354
1371
|
const ownR = cite(f.own_rights?.source, 'Own-portfolio sweep.', EVIDENCE_LABEL[f.own_rights?._status]);
|
|
1355
1372
|
const proseFull = cardBlock(card, /^full detail/i);
|
|
1356
1373
|
const proseFullShown = proseFull; // one report: the full prose; portal-report strips serve-time chrome, never analysis
|
|
@@ -1627,7 +1644,67 @@ const COV_STATE = {
|
|
|
1627
1644
|
'not-searched': { cls: 'todo', ic: '→', word: 'Not run this run' },
|
|
1628
1645
|
note: { cls: 'info', ic: 'i', word: 'Note' },
|
|
1629
1646
|
};
|
|
1647
|
+
/**
|
|
1648
|
+
* ONE ROW PER GAP, where the driver's follow-up row and the model's own row are the same search.
|
|
1649
|
+
*
|
|
1650
|
+
* A run deferred the English word DOLPHIN and the page said so twice: once as the model wrote it — "the
|
|
1651
|
+
* English word DOLPHIN as a dedicated exact search · Open item" — and once as the driver composes it
|
|
1652
|
+
* from the envelope, "Follow-up / dolphin · Open item: dolphin — not completed this run — the search for
|
|
1653
|
+
* it was planned and never reached the register…". The run's own reviewer flagged the duplicate and it
|
|
1654
|
+
* shipped anyway, because the second row is composed HERE and the reviewer reads what the model wrote.
|
|
1655
|
+
*
|
|
1656
|
+
* RENDER-SIDE ONLY. Both rows stay in the record and in the workbook; this decides what the page draws.
|
|
1657
|
+
* Dropping the driver's row from `coverage[]` would change what the run recorded, and this issue is
|
|
1658
|
+
* presentation.
|
|
1659
|
+
*
|
|
1660
|
+
* IT ERRS TOWARD KEEPING BOTH. A surplus row is today's behaviour; a wrongly-suppressed one hides a gap
|
|
1661
|
+
* from the reader, which is the failure worth avoiding. So the driver's row goes only when another row
|
|
1662
|
+
* carries EVERY significant word of the directive it names — a near-match keeps both.
|
|
1663
|
+
*/
|
|
1664
|
+
const FOLLOW_UP_PREFIX = 'Follow-up / ';
|
|
1665
|
+
const COV_STOPWORDS = new Set(['the', 'a', 'an', 'as', 'for', 'of', 'in', 'on', 'and', 'or', 'to', 'is',
|
|
1666
|
+
'was', 'it', 'its', 'this', 'that', 'with', 'by', 'at', 'be', 'been', 'run', 'search', 'searched']);
|
|
1667
|
+
const covWords = (t) => new Set(String(t || '').toLowerCase().match(/[a-z0-9]+/g)?.filter((w) => !COV_STOPWORDS.has(w)) ?? []);
|
|
1668
|
+
|
|
1669
|
+
function dedupeFollowUps(coverage) {
|
|
1670
|
+
const composed = (c) => String(c?.area || '').startsWith(FOLLOW_UP_PREFIX);
|
|
1671
|
+
const written = coverage.filter((c) => !composed(c));
|
|
1672
|
+
if (!written.length) return coverage;
|
|
1673
|
+
return coverage.filter((c) => {
|
|
1674
|
+
if (!composed(c)) return true;
|
|
1675
|
+
// The directive is the note's opening clause — the same string the area was clipped from, unclipped.
|
|
1676
|
+
const directive = covWords(String(c.note || '').split('—')[0]);
|
|
1677
|
+
if (!directive.size) return true;
|
|
1678
|
+
// A COMPLETED SEARCH NEVER STANDS IN FOR AN UNCOMPLETED ONE. This is the discriminator the first
|
|
1679
|
+
// version lacked, and it hid a real gap from a client on a delivered page.
|
|
1680
|
+
//
|
|
1681
|
+
// The composed row exists to say a planned search never ran. Suppressing it requires another row
|
|
1682
|
+
// saying THE SAME THING about the same search — so the row that suppresses must itself be open. A
|
|
1683
|
+
// row reporting a search that RAN is reporting the opposite, and the two are not interchangeable
|
|
1684
|
+
// however many words they share.
|
|
1685
|
+
//
|
|
1686
|
+
// Found on a real run, driven on the shipped renderer: a completed saturation probe into
|
|
1687
|
+
// third-party dolphin-word rights suppressed the uncompleted exact-word register search for DOLPHIN,
|
|
1688
|
+
// because both areas contain "dolphin". The client then read a coverage section that mentioned the
|
|
1689
|
+
// word and disclosed nothing left undone, and the suppressed row was the page's only disclosure of
|
|
1690
|
+
// it. That is exactly the failure this function's header calls the one worth avoiding, and the
|
|
1691
|
+
// header was right while the code was not.
|
|
1692
|
+
//
|
|
1693
|
+
// WHAT THIS STILL CANNOT DO, said rather than implied: it is word containment, so two genuinely
|
|
1694
|
+
// different OPEN searches sharing every word of a short directive would still collapse to one. The
|
|
1695
|
+
// discriminator a reader would want is which search a row names, and the rows carry no identity to
|
|
1696
|
+
// join on — that is why the issue's own wording is "name the same search" rather than a rule. The
|
|
1697
|
+
// narrowing here is the strongest one the data supports, and it errs toward keeping both.
|
|
1698
|
+
return !written.some((w) => {
|
|
1699
|
+
if (COV_STATE[w?.state]?.cls === 'ok') return false; // a searched-and-clean row reports the opposite
|
|
1700
|
+
const theirs = covWords(w.area);
|
|
1701
|
+
return [...directive].every((word) => theirs.has(word));
|
|
1702
|
+
});
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1630
1706
|
function coverageGrid(coverage) {
|
|
1707
|
+
coverage = dedupeFollowUps(coverage);
|
|
1631
1708
|
if (!coverage.length) return '';
|
|
1632
1709
|
const cell = (c) => {
|
|
1633
1710
|
const s = COV_STATE[c.state] || COV_STATE.note;
|
|
@@ -1753,7 +1830,7 @@ document.addEventListener('keydown',function(e){if(e.key==='Escape'){var pop=doc
|
|
|
1753
1830
|
function markAssessmentBlock(ma) {
|
|
1754
1831
|
if (ma == null) return '';
|
|
1755
1832
|
const structured = typeof ma.distinctiveness === 'object' || typeof ma.connotation === 'object';
|
|
1756
|
-
const SEC = `<div class="sec"><span class="num">✦</span><h2>The mark itself</h2><span class="note">
|
|
1833
|
+
const SEC = `<div class="sec"><span class="num">✦</span><h2>The mark itself</h2><span class="note">how strong the name is on its own</span></div>`;
|
|
1757
1834
|
if (!structured) {
|
|
1758
1835
|
const dist = String(ma?.distinctiveness ?? '').trim(), conn = String(ma?.connotation ?? '').trim();
|
|
1759
1836
|
if (!dist && !conn) return '';
|
|
@@ -2130,7 +2207,7 @@ export function renderHtml(parsed, findings = [], coverage = [], opts = {}) {
|
|
|
2130
2207
|
const clNotice = (clNoticeText && CASE_LAW_BY_ORD.size)
|
|
2131
2208
|
? `<div class="panel" style="padding:12px 16px;margin:0 0 12px"><p style="margin:0 0 4px;font-weight:700;font-size:13px">Session-wide notice</p><div style="font-size:13px">${renderProse(clNoticeText)}</div></div>`
|
|
2132
2209
|
: '';
|
|
2133
|
-
const CL_SEC = (n) => `<div class="sec" id="common-law"><span class="num">${n}</span><h2>Common-law & marketplace</h2><span class="note">
|
|
2210
|
+
const CL_SEC = (n) => `<div class="sec" id="common-law"><span class="num">${n}</span><h2>Common-law & marketplace</h2><span class="note">who is using similar names, registered or not</span></div>
|
|
2134
2211
|
${clNotice}${clBody}`;
|
|
2135
2212
|
const hasCL = Boolean(clBody || clNotice);
|
|
2136
2213
|
let findingsSections, covNum;
|
|
@@ -2269,7 +2346,7 @@ ${opts.nav || ''}
|
|
|
2269
2346
|
${ruledOutSection(ruledOut, recordsByUri)}
|
|
2270
2347
|
|
|
2271
2348
|
<!-- doc-52 §3 WHAT ONLY YOU CAN CLOSE — forward decisions, plain English, after the findings. -->
|
|
2272
|
-
${buckets.you ? `<div class="sec" id="only-you"><span class="num">✔</span><h2>What only you can close</h2><span class="note">
|
|
2349
|
+
${buckets.you ? `<div class="sec" id="only-you"><span class="num">✔</span><h2>What only you can close</h2><span class="note">decisions that need you</span></div>
|
|
2273
2350
|
<div class="panel actions"><div class="actgrp act-you">${renderProse(buckets.you.body)
|
|
2274
2351
|
.replace(/\[Time-critical\]\s*/gi, '<span class="src cl" style="margin-right:6px">Time-critical</span> ')
|
|
2275
2352
|
.replace(/\[Open question\]\s*/gi, '<span class="src" style="margin-right:6px">Open question</span> ')
|
|
@@ -2287,14 +2364,32 @@ ${opts.nav || ''}
|
|
|
2287
2364
|
|
|
2288
2365
|
<!-- doc-52 §4 SCOPE & WHAT WE DIDN'T SEARCH — one collapsible section, last; replaces "Checks we ran"
|
|
2289
2366
|
+ "Methodology" + the coverage grid. Nothing here leads. -->
|
|
2290
|
-
${scopeSection(buckets.ran, coverage, opts.coverageJudgment, secs['Methodology'], DISPOSITION_MODE ? [] : contextNotes, fm, recordsByUri.size > 0, findings.length > 0
|
|
2367
|
+
${scopeSection(buckets.ran, coverage, opts.coverageJudgment, secs['Methodology'], DISPOSITION_MODE ? [] : contextNotes, fm, recordsByUri.size > 0, findings.length > 0,
|
|
2368
|
+
// WHETHER A REGISTER-INDEX ENTRY IS ACTUALLY ON THIS PAGE, mirroring the registration render's own
|
|
2369
|
+
// second disjunct rather than restating it loosely: a cited registration with no fetched body, which
|
|
2370
|
+
// is the state that draws the "(register-index entry)" label. The provenance paragraph explains that
|
|
2371
|
+
// label, so it renders where the label can and stays off every page where it cannot.
|
|
2372
|
+
findings.some((f) => (f?.owner?.registrations ?? []).some((r) => r?.uri
|
|
2373
|
+
&& (recordsByUri.size > 0 || !(r.status || r.filed || r.expiry || (r.classes && r.classes.length))))))}
|
|
2291
2374
|
|
|
2292
2375
|
${askAiHtml}
|
|
2293
2376
|
|
|
2294
2377
|
<footer>
|
|
2295
2378
|
<span>${productName ? `${esc(productName)}. ` : ''}${FRAMEWORK
|
|
2296
|
-
|
|
2297
|
-
|
|
2379
|
+
// TWO SENTENCES, and that count is the ruled shape rather than a consequence of trimming.
|
|
2380
|
+
//
|
|
2381
|
+
// Two things left. The band note — "the framework in force's own vocabulary, one word per
|
|
2382
|
+
// finding on every surface" — is a note about how the renderer works, printed on every report a
|
|
2383
|
+
// client receives; the framework's NAME is on the "Rated under" line below, once, which is
|
|
2384
|
+
// where a reader who wants it will look.
|
|
2385
|
+
//
|
|
2386
|
+
// AND "Working draft for legal review.", which is a separate decision and is recorded as one.
|
|
2387
|
+
// A delivered clearance is not a draft, and a document that calls itself one on every page is
|
|
2388
|
+
// describing its own status inaccurately to the person paying for it. Raised in review because
|
|
2389
|
+
// the first version of this comment argued only the band note and left the reader to infer that
|
|
2390
|
+
// the status line had gone along for the ride.
|
|
2391
|
+
? `Risk bands: <span class="mono">${esc(FRAMEWORK.bands.map(b => b.label).join(' / '))}</span>. Purple notes are for the reviewing lawyer and are removed on export.`
|
|
2392
|
+
: 'Risk bands: <span class="mono">LOW / MANAGEABLE / MEDIUM / HIGH / VERY HIGH</span>. Purple notes are for the reviewing lawyer and are removed on export.'}<br>Matter ${esc(fm.matter || '')}${fm.run ? ` · ${esc(fm.run)}` : ''}.${fm.rated_under ? `<br>Rated under: <span class="mono">${esc(fm.rated_under)}</span>.` : ''}${fm.run_under_project ? `<br>Run under project: <span class="mono">${esc(fm.run_under_project)}</span>.` : ''}</span>
|
|
2298
2393
|
${logoLockup({ mark: 16 })}
|
|
2299
2394
|
</footer>
|
|
2300
2395
|
</div>
|
|
@@ -30,6 +30,26 @@ export const SIGNATURES = [
|
|
|
30
30
|
// the strip's residue always leaves whitespace after the hyphen.
|
|
31
31
|
re: /\bpre-\s+[a-z]/,
|
|
32
32
|
},
|
|
33
|
+
{
|
|
34
|
+
name: "a parenthesis opening on a comma",
|
|
35
|
+
// THE THIRD RESIDUE, and the floor could not see it for two years because the first two are
|
|
36
|
+
// WORD-level — a bare possessive, a dangling `pre-` — and this one is PUNCTUATION. It looks like
|
|
37
|
+
// ordinary code to a reader scanning for broken English, which is exactly why a census built from
|
|
38
|
+
// the shapes somebody had noticed reads like a census of the whole problem.
|
|
39
|
+
//
|
|
40
|
+
// Where the removed reference was the entire content of a parenthesis, nothing is left but `(,)`.
|
|
41
|
+
// Where it opened one, the sentence now begins on a comma: `(, 2026-08-21)`. Two ran off the end of
|
|
42
|
+
// their line.
|
|
43
|
+
//
|
|
44
|
+
// THE LOOKAHEAD IS THE WHOLE PRECISION AND IT IS DERIVED, NOT GUESSED. `(,` also occurs in code —
|
|
45
|
+
// inside a character class (`[(,=:…]`, `[;{\s(,]`) and in an alternation (`(,|$)`) — where those two
|
|
46
|
+
// characters are syntax rather than damage. Every code instance is followed by a regex metacharacter
|
|
47
|
+
// and no residue instance is, so the exclusion is `|`, `]` and `=` rather than a list of files that
|
|
48
|
+
// would go stale. Measured against the tree as it stood before the repair: 123 matches, which is the
|
|
49
|
+
// residue count the finding reported, and 0 after it. A signature that reproduces the number it was
|
|
50
|
+
// derived from is one that is reading the same thing.
|
|
51
|
+
re: /\(\s*,(?![|\]=])/,
|
|
52
|
+
},
|
|
33
53
|
];
|
|
34
54
|
|
|
35
55
|
// THE RULE'S OWN DEFINITION IS THE ONLY EXEMPTION, and it is named rather than pattern-matched.
|
package/driver/search-policy.mjs
CHANGED
|
@@ -207,7 +207,7 @@ export const PRODUCT_POLICIES = {
|
|
|
207
207
|
// 199 of those same 420 carry one. The driver now reads this row and can cut on that tier
|
|
208
208
|
// (doubt-selection.mjs).
|
|
209
209
|
//
|
|
210
|
-
//, owner-ruled 2026-08-23, product by product (reached this branch
|
|
210
|
+
//, owner-ruled 2026-08-23, product by product (reached this branch on
|
|
211
211
|
// that thread, not as a comment from the owner's own hand — the box has one `gh` identity).
|
|
212
212
|
// It is the P2 column of that table's `placement-inquiry trace` row — the row whose typed key is
|
|
213
213
|
// the placement tier. This stage's OWN row reads `bands 1+2 / band 1` on FINDING CLASS, which is
|
|
@@ -877,7 +877,7 @@ export const RECIPE_KEYS = [
|
|
|
877
877
|
* The saved-search SCOPE block — where the machinery points, as against `base`/`components`, which
|
|
878
878
|
* choose which machinery runs.
|
|
879
879
|
*
|
|
880
|
-
* This is what makes a saved search a saved SEARCH rather than a saved depth. "
|
|
880
|
+
* This is what makes a saved search a saved SEARCH rather than a saved depth. "Drinks knockouts — US
|
|
881
881
|
* focus" is a label over exactly this: base `knockout`, scope `{jurisdictions:["US"]}`. Without it a
|
|
882
882
|
* saved search could only ever restate a level that is already one click away, which is why the recipe
|
|
883
883
|
* store existed for weeks with nothing worth putting in it.
|
|
@@ -1066,7 +1066,7 @@ let recipeCache = null;
|
|
|
1066
1066
|
* THE STORE IS NAMED, NEVER GUESSED. `CLEAROTRON_RECIPES_DIR` unset means this deployment has no saved
|
|
1067
1067
|
* searches — not that it should use the ones bundled with the source.
|
|
1068
1068
|
*
|
|
1069
|
-
* `driver/recipes/` ships synthetic demos for two
|
|
1069
|
+
* `driver/recipes/` ships synthetic demos for two test accounts so the dev
|
|
1070
1070
|
* cockpit and the tests have something to render. It used to be the fallback when the env var was
|
|
1071
1071
|
* unset, which is a foot-gun that only fires in production: the real deployment does not set the
|
|
1072
1072
|
* variable, so switching saved searches on there would have surfaced invented customers inside the
|
|
@@ -27,7 +27,7 @@ Decompose the mark into its element(s) and name the **dominant element** (the sp
|
|
|
27
27
|
|
|
28
28
|
- **Drop** characters: shorten the element (VELTRIN → VELTRI). A dropped letter is the commonest missed-cluster cause — the shorter root is its own crowded field.
|
|
29
29
|
- **Add** characters / **composite**: the element living inside a larger mark (DELPHI Diagnostics, Osler Delphi, Delphic). A composite that shares your dominant element is on the board.
|
|
30
|
-
- **Phonetic / homophone**: sound-alikes (
|
|
30
|
+
- **Phonetic / homophone**: sound-alikes (PHAROLIS / FAROLIS / PHAROLLIS). Carry the `ph`/`f` pair in particular: it sounds identical, files under a different letter, and survives no letter-distance measure — which is the whole reason this class is separate from the two above.
|
|
31
31
|
- **Neighbour**: a one-keystroke real-word or famous-mark neighbour (CHROME on a NOVAPULSE clearance). A famous neighbour is carried for diligence even when off-field.
|
|
32
32
|
|
|
33
33
|
For each, give the value, the direction, and one line of rationale.
|
|
@@ -119,6 +119,90 @@ Measured tone only — no "extremely difficult", "most dangerous", "massive", "e
|
|
|
119
119
|
colour communicates urgency. No quantitative claims from research ("major streaming hit", never
|
|
120
120
|
"294M streams"; "significant market presence", never revenue figures).
|
|
121
121
|
|
|
122
|
+
## Plain language — the two-register rule
|
|
123
|
+
|
|
124
|
+
The report goes to a lawyer who layers advice on top, and that lawyer's client reads the same page. The
|
|
125
|
+
band, the summary, the basis line and the one-liners are the whole product for the second reader. They
|
|
126
|
+
were the hardest lines on the page.
|
|
127
|
+
|
|
128
|
+
**Everything a reader sees before opening a fold is written for the client's business owner.** Ordinary
|
|
129
|
+
nouns, one idea per sentence, no sentence over 25 words, the conclusion first. On this product that is:
|
|
130
|
+
`chunkSummary`, `basis`, `factors`, `counterFactors`, `mitigation`, every finding's `net`, every
|
|
131
|
+
`registerReads[].read`, the `purpleNotes`, and `standardCaveats`.
|
|
132
|
+
|
|
133
|
+
**Inside a fold the lawyer's vocabulary is allowed** where a plain word would lose precision — a
|
|
134
|
+
finding's `basis`, the provenance, the long `assessment`. Even there, a term is explained the first time
|
|
135
|
+
it appears on the page.
|
|
136
|
+
|
|
137
|
+
The rule is the test, not the list. Ask of every noun: *does the reader already own this word?* These
|
|
138
|
+
are worked examples of the one failure, not its boundary:
|
|
139
|
+
|
|
140
|
+
| Instead of | Write |
|
|
141
|
+
|---|---|
|
|
142
|
+
| proprietor | owner |
|
|
143
|
+
| subsisting | live |
|
|
144
|
+
| senior | earlier, or came first |
|
|
145
|
+
| specification | goods list |
|
|
146
|
+
| DELPH-formative | names built on DELPH- |
|
|
147
|
+
| prevail | win |
|
|
148
|
+
| citable prior rights | earlier marks the office can raise against you |
|
|
149
|
+
| vulnerable to a non-use attack | could be cancelled for not being used |
|
|
150
|
+
| on the record as it stands | on what we found |
|
|
151
|
+
| the marks-and-goods comparison | same name, same goods |
|
|
152
|
+
| the confusion comparison meets on every limb | same name, same goods, same shops |
|
|
153
|
+
| belt-and-braces classes | extra classes |
|
|
154
|
+
| dispatch, instructed | the request, what was asked |
|
|
155
|
+
| lane | class, or channel — whichever you mean |
|
|
156
|
+
| chunk | never; say the batch, or these names |
|
|
157
|
+
|
|
158
|
+
The target is the level of these, each the standard for its line:
|
|
159
|
+
|
|
160
|
+
> **Basis.** "ORBIT is already the name of two satellite-tracking apps on the same app stores, and of an
|
|
161
|
+
> established satellite-communications company. Any of them would likely win a dispute over this name
|
|
162
|
+
> for this software. The word is a weak mark for these goods, which is why this is High and not Very
|
|
163
|
+
> High."
|
|
164
|
+
|
|
165
|
+
> **A finding's `net`.** "Same name, same goods, and their filing came first in every territory we
|
|
166
|
+
> searched. We see no argument against it."
|
|
167
|
+
|
|
168
|
+
> **The batch opener.** "One name screened: ORBIT, rated High."
|
|
169
|
+
|
|
170
|
+
**Shortening by dropping the reason is not the fix.** The "why" stays, in plain words. A visible
|
|
171
|
+
line that is short because it no longer says why is worse than the long one it replaced, and it
|
|
172
|
+
passes every length rule on this page while telling the reader less.
|
|
173
|
+
|
|
174
|
+
**Say it once.** A caveat, a limit or a finding belongs in one place. Four `standardCaveats` that each
|
|
175
|
+
restate "this is not a clearance" are one caveat and three repetitions — and the report's scope block
|
|
176
|
+
already carries that sentence in the renderer's own words, so a caveat repeating it is dropped before
|
|
177
|
+
the reader sees it. Write the one thing the scope block does not say, or write none.
|
|
178
|
+
|
|
179
|
+
**A note is verb-first, one line, and about one of two things.** Write each `purpleNotes` entry as an
|
|
180
|
+
instruction to the reviewing lawyer, opening on the verb. Every note is about the REQUEST — what was
|
|
181
|
+
asked, and whether it holds together — or about the NAME. The report prints the first kind at the top,
|
|
182
|
+
above the conflicts, and the second under that name's cards, so a note that mixes the two lands in one
|
|
183
|
+
place and reads oddly in it. Write two notes.
|
|
184
|
+
|
|
185
|
+
A note about the request NAMES the request in the note — "the request", or "what we were asked". That is
|
|
186
|
+
not a formality: the report sorts the two kinds by what the note talks about, so a request note that
|
|
187
|
+
never mentions the request is filed under the name and prints in the wrong place.
|
|
188
|
+
|
|
189
|
+
> "Check the request. The client is described as a beverages and supplements business, but we were asked
|
|
190
|
+
> to screen satellite-tracking software in Class 9. We screened the software. If beverages is the real
|
|
191
|
+
> business, this screen looked at the wrong market."
|
|
192
|
+
|
|
193
|
+
> "Ask the client whether it already uses ORBIT. The request does not say, and the client's own earlier
|
|
194
|
+
> use would change the picture."
|
|
195
|
+
|
|
196
|
+
A note about the name opens on what the lawyer should do about the name:
|
|
197
|
+
|
|
198
|
+
> "Pull EG Tech's full goods list at clearance. It is the record most likely to change the picture in
|
|
199
|
+
> either direction."
|
|
200
|
+
|
|
201
|
+
> "Search Classes 42 and 38 in their own right. Both carry live ORBIT filings, not spillover from
|
|
202
|
+
> Class 9."
|
|
203
|
+
|
|
204
|
+
A note that restates a finding already on the page is not a note. Cut it.
|
|
205
|
+
|
|
122
206
|
## The per-mark opening read (`assessment`)
|
|
123
207
|
|
|
124
208
|
Each mark's report is delivered on its own, and `assessment` is the first thing its reader meets —
|
|
@@ -75,11 +75,11 @@ You do not write a file. Hand the frame back by calling `record_matter_frame`; t
|
|
|
75
75
|
|
|
76
76
|
### Scope jurisdictions — search wide, cite narrow (instructed scope honored; worldwide / brand-signalled scope leans wide + discloses)
|
|
77
77
|
|
|
78
|
-
Two scopes live here, and the doctrine is **narrow at citation, never silently at search**: the **search scope** (where the register sweep looks) must be wide enough to catch the conflict; the **citation scope** (what the report flags and ranks) is where rights legally matter. The failure to avoid is the
|
|
78
|
+
Two scopes live here, and the doctrine is **narrow at citation, never silently at search**: the **search scope** (where the register sweep looks) must be wide enough to catch the conflict; the **citation scope** (what the report flags and ranks) is where rights legally matter. The failure to avoid is the pinned-scope miss — *silently* pinning a globally-signalling brand to one country, so the real EU/UK/US conflict is never even searched. Derive both from THIS matter; a customer profile selects the **risk framework** only — it never sets territory.
|
|
79
79
|
|
|
80
80
|
- **Instructed scope is honored (narrow or wide).** When the client names territories — a list, or "X only" — that is the scope for BOTH search and citation; do not override a client who genuinely wants a narrow scope (a sweep of a country with no instructed interest and no reach is wasted). The profile's `defaultJurisdictions`, when present, is the *customer's own standing instruction* — treat it like an instructed set.
|
|
81
|
-
- **Worldwide, or the brand's own signals point broader → the SEARCH widens (it never silently narrows).** When the instruction is worldwide, OR the brand signals broader use — a global gTLD (a `.io`/`.com` launch), an investor / expansion announcement, stated target markets, a genuinely global customer base — the **search set includes the major markets (US / EU / UK / CN / JP) plus the signalled markets**. Decide which to actually cite and rank at **selection** (where rights matter), and **disclose** any material market you could not fully cover (a `coverage-limited` row — never a silent clean). The one move forbidden: quietly pinning an unpinned, globally-signalling brand to a single country (the
|
|
82
|
-
- **Genuinely unpinned, no global signal → reason the likely scope, LABEL it inferred, and DISCLOSE it.** Read the product / customer base / channels and scope to where the brand plausibly operates; never silently default to the requester's home country (that is the
|
|
81
|
+
- **Worldwide, or the brand's own signals point broader → the SEARCH widens (it never silently narrows).** When the instruction is worldwide, OR the brand signals broader use — a global gTLD (a `.io`/`.com` launch), an investor / expansion announcement, stated target markets, a genuinely global customer base — the **search set includes the major markets (US / EU / UK / CN / JP) plus the signalled markets**. Decide which to actually cite and rank at **selection** (where rights matter), and **disclose** any material market you could not fully cover (a `coverage-limited` row — never a silent clean). The one move forbidden: quietly pinning an unpinned, globally-signalling brand to a single country (the pinned-scope miss). **The cost guard stays:** an abstract *"the sector tends to be global"* is **not, by itself, the widener** — the widener is a CONCRETE signal (a worldwide instruction, or the brand's own global footprint), so a deliberately local matter stays local.
|
|
82
|
+
- **Genuinely unpinned, no global signal → reason the likely scope, LABEL it inferred, and DISCLOSE it.** Read the product / customer base / channels and scope to where the brand plausibly operates; never silently default to the requester's home country (that is the pinned-scope mechanism). Where reach is plausibly broad, lean wide; where clearly local, scope local — either way set `Scope basis: inferred` and record a one-line `Scope assumption:` so a narrowing is visible and the coverage gate can clamp + disclose it.
|
|
83
83
|
- **In scope by reach.** A territory also counts when a right is **legally effective in an instructed territory by any route** — filed there; an international (Madrid) registration **designating** it; a regional/supranational right covering it (an EUTM covers every EU member; an instructed EU member is covered by an EUTM); a treaty / priority effect. Tag such a territory `in-scope-by-reach` and name the route. Reason the route from the right — never assume it.
|
|
84
84
|
- **Excluded.** Territories you considered and set aside — neither instructed, nor brand-signalled, nor a major on a worldwide matter — name each, the reason, and a **reopen trigger** (a right or signal pointing there surfaces). A hit effective ONLY outside the scope set is out of *citation* scope (drop it from findings); but a market the brand actually signals belongs in the SEARCH set, not here.
|
|
85
85
|
- **Primary vs only.** If the instruction names territories of "primary" / "first" interest WITHOUT "only", treat the named ones as the citation core but **let the search lean wide** to the majors / brand-signalled markets (the "primary" wording signals more may matter), and **record the assumption** — a one-line `Scope assumption: <text>`. (A single clarifying question is acceptable instead; the recorded default never blocks the run.)
|
|
@@ -88,7 +88,7 @@ Two scopes live here, and the doctrine is **narrow at citation, never silently a
|
|
|
88
88
|
|
|
89
89
|
*Worked illustrations:*
|
|
90
90
|
*— Instructed "Switzerland and the EU, only" → search CH + the EUTM/EU layer (+ any Madrid designation reaching them); majors beyond (CN/JP/US/UK) are out of scope — an honored narrow instruction (`Scope basis: instructed`).*
|
|
91
|
-
*— A brand on a `.io` domain with an investor deck naming a US/EU launch and no explicit territory list → lean wide: search US/EU/UK/CN/JP + the named launch markets; cite where rights matter; disclose any gap (`Scope basis: inferred`). This is the
|
|
91
|
+
*— A brand on a `.io` domain with an investor deck naming a US/EU launch and no explicit territory list → lean wide: search US/EU/UK/CN/JP + the named launch markets; cite where rights matter; disclose any gap (`Scope basis: inferred`). This is the pinned-scope fix — do not pin it to CH.*
|
|
92
92
|
*— A local artisan, national-only product, no global signal → scope the home market, `Scope basis: inferred`, `Scope assumption:` recorded — narrow but disclosed, never silent.*
|
|
93
93
|
|
|
94
94
|
### Off-field sectors
|
|
@@ -111,7 +111,7 @@ Two scopes live here, and the doctrine is **narrow at citation, never silently a
|
|
|
111
111
|
### Sector-convergence flags
|
|
112
112
|
- Sectors off-field today where commercial trajectories may bring them adjacent
|
|
113
113
|
- Brief reason
|
|
114
|
-
- Example: "
|
|
114
|
+
- Example: "Foxglade Interactive's broader AI / cloud AI narrative could theoretically bring vector-DBs adjacent to game-dev tooling, but Foxglade dev kit is specifically game-tooling not LLM-tooling; convergence path exists but weak"
|
|
115
115
|
|
|
116
116
|
### Machine lines the task message dictates
|
|
117
117
|
|
|
@@ -132,7 +132,7 @@ driver's fixed query shapes:
|
|
|
132
132
|
|
|
133
133
|
## Reasoning posture
|
|
134
134
|
|
|
135
|
-
- When in doubt about an **off-field / adjacency** decision, INCLUDE rather than exclude — matter-frame should not silently filter a *field*; downstream review (`placement-inquiry`) can deprioritise later with reasoning. **Jurisdiction scope follows its own rule (see *Scope jurisdictions*): search wide, cite narrow.** An instructed-narrow scope is honored; a worldwide or brand-signalled scope widens the SEARCH to the majors + signalled markets; an inferred scope is labelled and disclosed. Do not silently pin a globally-signalling brand to one country (the
|
|
135
|
+
- When in doubt about an **off-field / adjacency** decision, INCLUDE rather than exclude — matter-frame should not silently filter a *field*; downstream review (`placement-inquiry`) can deprioritise later with reasoning. **Jurisdiction scope follows its own rule (see *Scope jurisdictions*): search wide, cite narrow.** An instructed-narrow scope is honored; a worldwide or brand-signalled scope widens the SEARCH to the majors + signalled markets; an inferred scope is labelled and disclosed. Do not silently pin a globally-signalling brand to one country (the pinned-scope miss), and do not widen a deliberately-local matter on abstract "the sector is global" reasoning (a wasted sweep).
|
|
136
136
|
- Be concrete about WHY for each item. A senior lawyer reading matter-context.md should be able to disagree with specific items and see exactly where your reasoning broke — not just see a conclusion.
|
|
137
137
|
- If you find yourself rating something as in-lane that a senior lawyer would obviously drop, surface the tension rather than smoothing it.
|
|
138
138
|
|
|
@@ -47,6 +47,6 @@ watchlists come from in any case, and is why this file was never authority.
|
|
|
47
47
|
|
|
48
48
|
## Cross-sector
|
|
49
49
|
|
|
50
|
-
- **
|
|
51
|
-
on
|
|
50
|
+
- **Foxglade Interactive** — `major_brand_owners`. Broad FOXGLADE / NEBULA / VANGUARD families;
|
|
51
|
+
on a Foxglade Interactive-adjacent matter watch the relevant family token and name
|
|
52
52
|
ecosystem partners with their partnership context rather than as bare conflicts.
|
|
@@ -173,7 +173,7 @@ For game-title rows, the `developer_of_record` and `publisher_of_record` columns
|
|
|
173
173
|
| Finding | Source / Platform | URL | Notes |
|
|
174
174
|
|---|---|---|---|
|
|
175
175
|
| Sony "Pulse Elevate" portfolio | Sony products | https://... | Sony uses "Elevate" in audio products; flagged for prelim-register cross-check |
|
|
176
|
-
|
|
|
176
|
+
| Foxglade "Borealis" console "Raise Your Play" tagline (prior usage) | Foxglade Interactive marketing | https://... | Client's own prior use — note as supportive |
|
|
177
177
|
|
|
178
178
|
### PR / reputational risk
|
|
179
179
|
|
|
@@ -259,7 +259,7 @@ The driver runs the delivery stage on **every** completed run — the search is
|
|
|
259
259
|
|
|
260
260
|
**Methodology lives in the Excel Methodology tab, NOT in the email body.** Do not include paragraph-length descriptions of search approach, sub-skills invoked, variants generated, etc. in the client-facing email. That bloat is what the render-layer split fixes.
|
|
261
261
|
|
|
262
|
-
**The email is a COVER NOTE; the HTML report is the single master document (wp50).** The house default for every customer: the email carries only the headline risk tier, the report link, and any surviving scope/verdict/handling flags plus reviewer's open questions — no findings body, no client-voice prose, no hand-holding preamble. Client-voice findings live in the **HTML report** (the one surface the client reads). The last exception —
|
|
262
|
+
**The email is a COVER NOTE; the HTML report is the single master document (wp50).** The house default for every customer: the email carries only the headline risk tier, the report link, and any surviving scope/verdict/handling flags plus reviewer's open questions — no findings body, no client-voice prose, no hand-holding preamble. Client-voice findings live in the **HTML report** (the one surface the client reads). The last exception — a customer's review-table overlay (`delivery.email:'table'`) — is DELETED as of 2026-07-28, along with the knob that asked for it: a second full findings surface in the mail was the seam that let per-conflict tiers diverge from the report, and on the knockout lane it shipped internal purple notes over the wire. A lawyer who needs a bespoke, forwardable client mail asks the assistant to draft one from the run's `report-data.json` — formatting is judgement, and it belongs where judgement lives, not in a profile enum. The operative delivery spec is [delivery-contract.md](delivery-contract.md) + the driver's `report-synthesis` stage. (The `client-summary` stage, which re-voiced the findings into a separate client-facing document, is RETIRED as of 2026-08-01 — the one report is the one surface.)
|
|
263
263
|
|
|
264
264
|
Structure (content shared across templates):
|
|
265
265
|
|
|
@@ -19,6 +19,88 @@ writes lives in that stage's own file — the finding sentence and the grouped r
|
|
|
19
19
|
[synthesis-rules.md](synthesis-rules.md), the card and section shape in
|
|
20
20
|
[delivery-contract.md](delivery-contract.md). What is below is general and is stated only here.
|
|
21
21
|
|
|
22
|
+
## Two registers, and the reader of the first one is not a lawyer
|
|
23
|
+
|
|
24
|
+
**The reader is a lawyer who layers advice on top — and that lawyer's client reads the same page.** The
|
|
25
|
+
band, the summary, the basis line and the one-liners are the whole product for the second reader, and
|
|
26
|
+
they were the hardest text on it: single sentences of seventy-odd words in the vocabulary of the
|
|
27
|
+
profession.
|
|
28
|
+
|
|
29
|
+
So the page has two registers, and which one a line is written in is decided by whether the reader has
|
|
30
|
+
to open something to see it.
|
|
31
|
+
|
|
32
|
+
**DEFAULT-VISIBLE TEXT CARRIES NO LEGAL OR ENGINE VOCABULARY.** Default-visible means anything a reader
|
|
33
|
+
meets before opening a fold: the summary and the opening line, the basis for the rating, the points for
|
|
34
|
+
and against and what would soften them, the one sentence under each name, the four answers, the
|
|
35
|
+
reviewer's notes, the caveats, the coverage rows and the lines saying what needs a decision. Ordinary
|
|
36
|
+
nouns. One idea per sentence. **No visible sentence over 25 words.** The conclusion first.
|
|
37
|
+
|
|
38
|
+
Which stored fields those are is not prose's business and is not listed here — `DEFAULT_VISIBLE_FIELDS`
|
|
39
|
+
in `driver/plain-register.mjs` names them for both products, in one place, so a field renamed once does
|
|
40
|
+
not leave a doctrine file quietly describing a shape that no longer exists.
|
|
41
|
+
|
|
42
|
+
**INSIDE A FOLD THE PROFESSION'S VOCABULARY IS ALLOWED**, where a plain word would lose precision: the
|
|
43
|
+
card's detail paragraph, provenance, the drill bullets. Even there a term is glossed at first use on the
|
|
44
|
+
page. Nothing is removed from the drill and the workbook is unchanged.
|
|
45
|
+
|
|
46
|
+
### The swaps, as worked examples rather than a banned list
|
|
47
|
+
|
|
48
|
+
A list of forbidden words is not the mechanism and must never become one. These are what the plain form
|
|
49
|
+
sounds like; the instruction is always to rewrite the sentence, never to substitute the word and leave
|
|
50
|
+
the rest of a lawyer's sentence standing around it.
|
|
51
|
+
|
|
52
|
+
| written for a lawyer | what the reader needs |
|
|
53
|
+
|---|---|
|
|
54
|
+
| proprietor | owner |
|
|
55
|
+
| subsisting | live |
|
|
56
|
+
| senior | earlier, or came first |
|
|
57
|
+
| specification | goods list |
|
|
58
|
+
| DELPH-formative | names built on DELPH- |
|
|
59
|
+
| prevail | win |
|
|
60
|
+
| citable prior rights | earlier marks the office can raise against you |
|
|
61
|
+
| vulnerable to a non-use attack | could be cancelled for not being used |
|
|
62
|
+
| on the record as it stands | on what we found |
|
|
63
|
+
| the marks-and-goods comparison | same name, same goods |
|
|
64
|
+
| the confusion comparison meets on every limb | same name, same goods, same shops |
|
|
65
|
+
| belt-and-braces classes | extra classes |
|
|
66
|
+
| dispatch, instructed | the request, what was asked |
|
|
67
|
+
| lane | class, or channel — whichever is meant |
|
|
68
|
+
| chunk | never; it is an engine word |
|
|
69
|
+
|
|
70
|
+
### The standard, in full sentences
|
|
71
|
+
|
|
72
|
+
A basis line, rewritten: *"ORBIT is already the name of two satellite-tracking apps on the same app
|
|
73
|
+
stores, and of an established satellite-communications company. Any of them would likely win a dispute
|
|
74
|
+
over this name for this software. The word is a weak mark for these goods, which is why this is High and
|
|
75
|
+
not Very High."*
|
|
76
|
+
|
|
77
|
+
An answer, rewritten: *"Blocked. An identical earlier mark for the same goods stops registration in
|
|
78
|
+
Switzerland, the EU and the US. Below that, earlier DELPH- marks in EU class 5 and US class 42 can be
|
|
79
|
+
raised against the application."*
|
|
80
|
+
|
|
81
|
+
A card's detail — allowed the fold's vocabulary, still written plainly: *"Same name, same goods, and
|
|
82
|
+
their Swiss filing came first in every territory we searched. We see no argument against it."*
|
|
83
|
+
|
|
84
|
+
A note, rewritten: *"Ask the client whether it already uses ORBIT. Its own earlier use would change the
|
|
85
|
+
picture and is not reflected here."*
|
|
86
|
+
|
|
87
|
+
**Shortening by dropping the reason is not the fix.** The "why" stays, in plain words. A visible line
|
|
88
|
+
that is short because it no longer says why is worse than the long one it replaced.
|
|
89
|
+
|
|
90
|
+
### What reads this, and what does not
|
|
91
|
+
|
|
92
|
+
**No delivery gate fails on any of it, and no report is ever marked for it.** The reviewing stage that
|
|
93
|
+
already rejects engine vocabulary applies this test to the default-visible fields and hands back a
|
|
94
|
+
rewrite, in the same pass and the same voice it uses for everything else. A hit is a sentence rewritten.
|
|
95
|
+
It is never a disclosure to the client, never a run failure, and it moves no band, no evidence and
|
|
96
|
+
nothing that is searched — this is presentation.
|
|
97
|
+
|
|
98
|
+
The reviewer is helped by `driver/plain-register.mjs`, which offers the plain form beside the term it
|
|
99
|
+
found and never decides anything. **It cannot flag the mark the run is clearing**: half of these words
|
|
100
|
+
are ordinary English and several — PREVAIL, SENIOR, SPECIFICATION — are perfectly good trademarks, so
|
|
101
|
+
every term the run is about is removed from the text before it is read. A check that could not tell a
|
|
102
|
+
mark from the profession's vocabulary would put its noise on the one report where it matters most.
|
|
103
|
+
|
|
22
104
|
## Fact · assessment · prescription
|
|
23
105
|
|
|
24
106
|
Three different things, and only two of them belong in a report.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"framework_key": "demo",
|
|
4
4
|
"title": "Demo Brand Owner risk framework",
|
|
5
|
-
"source_deck": "Demo Brand Owner risk framework,
|
|
5
|
+
"source_deck": "Demo Brand Owner risk framework, authored in-house for the demonstration account",
|
|
6
6
|
"entity_label": "Demo Brand Owner",
|
|
7
7
|
"bands": [
|
|
8
8
|
{ "label": "Very High", "tone": "severe" },
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"framework_key": "house-triage",
|
|
4
4
|
"title": "House knockout triage ladder (Stage 0)",
|
|
5
|
-
"source_deck": "
|
|
5
|
+
"source_deck": "Cordillera house triage scale for knockout screening, authored in-house; five rating tiers",
|
|
6
6
|
"entity_label": "the client",
|
|
7
7
|
"bands": [
|
|
8
8
|
{ "label": "Very High", "tone": "severe" },
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"framework_key": "house-default",
|
|
4
4
|
"title": "Generic default risk framework",
|
|
5
|
-
"source_deck": "
|
|
5
|
+
"source_deck": "Cordillera house risk framework, authored in-house and reviewed by counsel; 2026-08-31 revision",
|
|
6
6
|
"entity_label": "the company",
|
|
7
7
|
"bands": [
|
|
8
8
|
{ "label": "Very High", "tone": "severe" },
|
|
@@ -14,7 +14,7 @@ Across the synthesis, two postures are load-bearing:
|
|
|
14
14
|
|
|
15
15
|
3. **Optics is annotation, not a rating.** Partner-relationship, channel-conflict, PR, and reputational concerns — however prominently `matter-context` or the manifest raise them — are surfaced as client-facing annotations (the separate PR / reputational section below; a "relationship-level heads-up"). PR is a separate category, **never rated on the framework's bands**, and an existing business relationship is an *either-way* practical factor, not a band multiplier. The rating answers what the framework in force asks — anchored in *if this party sued us, would they win?*, set by the consumer-confusion read (mark-as-whole × G&S). Never let optics/PR/partner/size move the band; annotate beside it.
|
|
16
16
|
|
|
17
|
-
4. **The framework in force rates the matter (doc 50).** Every run reads exactly one risk framework — **the customer's own framework if the profile has one on file, else the house default. Nothing in between.** That framework is the customer's own legal judgment written down: reason each conflict *with* it — its band definitions (Legal position × Practical position × Potential outcomes), or its matrix where it states one — and state the rating as **its band word, verbatim**. It is a reasoning authority, never a lookup table: no numeric thresholds, no score-to-band mapping, no vocabulary borrowed from another framework. **The band follows the words** — state the likelihood in plain words first (*"the prior owner is likely to win, though credible defences exist"*), then give the band those words require under the framework's own definitions; the prose read and the band are one judgment and may not disagree. Where the framework states **ceilings or matrix mappings, honour them exactly as written** — they are that framework's own anti-escalation mechanism, and no practical or optics factor lifts a rating past what its stated method yields. A conflict the client **clearly wins with no material risk is not a rated conflict at all** (most frameworks' lowest band still describes *real* residual risk — nuisance claims, weak strategic complaints, registration obstacles — never clear wins): surface it unrated as commercial awareness if worth knowing, else omit it. Voice the client side as the framework's **entity** names it (*"the company"*, *"
|
|
17
|
+
4. **The framework in force rates the matter (doc 50).** Every run reads exactly one risk framework — **the customer's own framework if the profile has one on file, else the house default. Nothing in between.** That framework is the customer's own legal judgment written down: reason each conflict *with* it — its band definitions (Legal position × Practical position × Potential outcomes), or its matrix where it states one — and state the rating as **its band word, verbatim**. It is a reasoning authority, never a lookup table: no numeric thresholds, no score-to-band mapping, no vocabulary borrowed from another framework. **The band follows the words** — state the likelihood in plain words first (*"the prior owner is likely to win, though credible defences exist"*), then give the band those words require under the framework's own definitions; the prose read and the band are one judgment and may not disagree. Where the framework states **ceilings or matrix mappings, honour them exactly as written** — they are that framework's own anti-escalation mechanism, and no practical or optics factor lifts a rating past what its stated method yields. A conflict the client **clearly wins with no material risk is not a rated conflict at all** (most frameworks' lowest band still describes *real* residual risk — nuisance claims, weak strategic complaints, registration obstacles — never clear wins): surface it unrated as commercial awareness if worth knowing, else omit it. Voice the client side as the framework's **entity** names it (*"the company"*, *"Coastline/Nimbus/Kestrel"*, *"Foxglade Interactive"*). The three-question discipline stays: **legal risk** (*would they win?* — the confusion read net of merits defences), **practical risk** (*would they act?* — enforcement reality), and **impact** (*if they did, what follows?*). The framework's band definitions integrate the first two — reason both, in that order, through its own terms. **Impact stays client-surfaced, never rating-moving**: name the consequences for the client to weigh (we do not conclude acceptability), beside the band, exactly as before.
|
|
18
18
|
|
|
19
19
|
## Contents
|
|
20
20
|
|