clearotron 0.4.0 → 0.4.1-beta.1

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 (132) hide show
  1. package/THIRD-PARTY-NOTICES.md +2 -2
  2. package/bin/README.md +1 -1
  3. package/bin/clearotron.mjs +2 -0
  4. package/bin/mcp.mjs +80 -0
  5. package/bin/start.mjs +14 -1
  6. package/build-info.json +2 -2
  7. package/demo/README.md +1 -1
  8. package/docs/SECURITY-OWASP.md +21 -13
  9. package/docs/SECURITY.md +20 -14
  10. package/docs/architecture/04-configuration-reference.md +3 -3
  11. package/docs/architecture/05-config-governance.md +1 -1
  12. package/docs/architecture/06-operations-runbook.md +5 -4
  13. package/docs/architecture/08-development-guide.md +3 -1
  14. package/docs/architecture/09-security-and-data.md +16 -11
  15. package/driver/CHANGELOG.md +29 -0
  16. package/driver/binding-layers.mjs +28 -6
  17. package/driver/citation-census.json +3 -3
  18. package/driver/common-law-coverage-status.mjs +52 -0
  19. package/driver/common-law-receipts.mjs +1 -1
  20. package/driver/compare.mjs +1 -1
  21. package/driver/connotation-search.mjs +5 -5
  22. package/driver/contract-audit.mjs +6 -6
  23. package/driver/contract-dictation-registry.mjs +1 -1
  24. package/driver/contract-e3-backlog.mjs +18 -17
  25. package/driver/contract-vocabulary.mjs +145 -132
  26. package/driver/declination-call.mjs +5 -1
  27. package/driver/degraded-parts.mjs +10 -3
  28. package/driver/dev-portal.mjs +2 -1
  29. package/driver/dispatch-record.mjs +19 -0
  30. package/driver/doubt-closure-tool.mjs +2 -2
  31. package/driver/driver.config.mjs +39 -6
  32. package/driver/engagement-receipt.mjs +1 -1
  33. package/driver/engine/CONTRACT.md +41 -1
  34. package/driver/engine/anthropic-agent.mjs +56 -4
  35. package/driver/engine/mcp/codex-config.mjs +8 -3
  36. package/driver/engine/mcp/coverage-server.mjs +1 -1
  37. package/driver/engine/mcp/gather-config.mjs +1 -1
  38. package/driver/engine/mcp/recording-server.mjs +32 -5
  39. package/driver/engine/mcp/stdio-server.mjs +45 -7
  40. package/driver/engine/mcp/supplemental.mjs +26 -1
  41. package/driver/engine/openai-agent.mjs +113 -20
  42. package/driver/engine/session-record.mjs +223 -0
  43. package/driver/enqueue-schema.mjs +2 -2
  44. package/driver/findings-model.mjs +4 -4
  45. package/driver/flag-snapshot.mjs +2 -1
  46. package/driver/form-neighbourhood.mjs +7 -7
  47. package/driver/frame-diff-model.mjs +3 -3
  48. package/driver/framework.mjs +22 -0
  49. package/driver/gateway.mjs +96 -14
  50. package/driver/jx.mjs +1 -1
  51. package/driver/knockout-review-record.mjs +55 -1
  52. package/driver/named-band.mjs +4 -0
  53. package/driver/owner-screen.mjs +7 -4
  54. package/driver/package.json +1 -1
  55. package/driver/phase0.mjs +13 -2
  56. package/driver/pipeline-knockout.mjs +15 -13
  57. package/driver/pipeline.mjs +132 -48
  58. package/driver/portal-families.mjs +3 -3
  59. package/driver/portal-service.mjs +6 -5
  60. package/driver/predelivery-lint.mjs +19 -19
  61. package/driver/products.mjs +1 -1
  62. package/driver/profiles.mjs +4 -3
  63. package/driver/publish/index.mjs +1 -1
  64. package/driver/publish/knockout.mjs +5 -4
  65. package/driver/publish/office-record-links.mjs +17 -2
  66. package/driver/publish/publish-inputs.mjs +3 -3
  67. package/driver/publish/render-knockout.mjs +17 -5
  68. package/driver/publish/render.mjs +4 -2
  69. package/driver/publish/xlsx.mjs +1 -1
  70. package/driver/record-fold.mjs +177 -0
  71. package/driver/register-count.mjs +20 -3
  72. package/driver/register-coverage.mjs +1 -1
  73. package/driver/register-plan.mjs +83 -18
  74. package/driver/register-records.mjs +7 -0
  75. package/driver/registry-fidelity.mjs +2 -2
  76. package/driver/repairs.mjs +1 -1
  77. package/driver/report-card-record.mjs +4 -4
  78. package/driver/result-noun-fields.mjs +3 -3
  79. package/driver/scope-facts.mjs +11 -3
  80. package/driver/scope-rules.mjs +2 -2
  81. package/driver/search-policy.mjs +20 -1
  82. package/driver/skills/clearance-common-law/SKILL.md +22 -22
  83. package/driver/skills/clearance-register/digest.md +3 -3
  84. package/driver/skills/clearance-register/register-recipes.md +11 -11
  85. package/driver/skills/clearance-register/status-rules.md +1 -1
  86. package/driver/skills/clearance-register/unit.md +1 -1
  87. package/driver/skills/clearance-search/firm-wide-reasoning.md +3 -2
  88. package/driver/skills/clearance-search/phase2-execution.md +6 -5
  89. package/driver/skills/clearance-search/synthesis-rules.md +4 -4
  90. package/driver/skills/clearance-search/worked-examples.md +39 -39
  91. package/driver/skills/clearance-variants/SKILL.md +16 -16
  92. package/driver/skills/frame-diff/SKILL.md +2 -2
  93. package/driver/stage-context.mjs +36 -1
  94. package/driver/stages-knockout.mjs +6 -5
  95. package/driver/stages.mjs +30 -30
  96. package/driver/suite-census.json +259 -55
  97. package/driver/synthesis-record.mjs +5 -0
  98. package/driver/unit-inventory.mjs +17 -28
  99. package/driver/verify.mjs +7 -7
  100. package/mcp-server/CHANGELOG.md +8 -0
  101. package/mcp-server/CONNECT.md +9 -3
  102. package/mcp-server/demo-mode.mjs +10 -0
  103. package/mcp-server/lib/evidence.mjs +8 -8
  104. package/mcp-server/lib/knockout.mjs +1 -1
  105. package/mcp-server/lib/runs.mjs +7 -1
  106. package/mcp-server/lib/scrub.mjs +1 -1
  107. package/mcp-server/package.json +2 -2
  108. package/mcp-server/packs/README.md +1 -1
  109. package/mcp-server/server.mjs +16 -2
  110. package/package.json +4 -3
  111. package/portal-ui/package.json +3 -3
  112. package/providers/_shared/count.mjs +9 -2
  113. package/providers/_shared/enumerate.mjs +125 -41
  114. package/providers/_shared/execute-plan.mjs +15 -4
  115. package/providers/corsearch/src/capabilities.js +1 -1
  116. package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
  117. package/providers/oauth-mcp-bridge/package.json +2 -2
  118. package/providers/signa/src/capabilities.js +32 -2
  119. package/providers/signa/src/core.js +262 -36
  120. package/scripts/band-shape-probe.mjs +2 -2
  121. package/scripts/deprecate-below.mjs +28 -4
  122. package/scripts/e2e-first-time.mjs +17 -2
  123. package/scripts/env-audit.mjs +3 -2
  124. package/scripts/freeze-example-run.mjs +10 -10
  125. package/scripts/mcp-registry-listing.mjs +199 -0
  126. package/scripts/mint-plugin-folder.mjs +65 -0
  127. package/scripts/release-version.mjs +44 -0
  128. package/scripts/score.mjs +1 -1
  129. package/shared/register-floor.mjs +19 -0
  130. package/shared/scope.mjs +18 -2
  131. package/shared/trigger-lane.mjs +1 -1
  132. package/skills/clearotron-ops/SKILL.md +81 -24
@@ -1193,8 +1193,8 @@ export function findRegistryArithmeticIssues(text) {
1193
1193
  // most national registers actually do — is TEN YEARS FROM FILING, with registration landing
1194
1194
  // somewhere in between. Two live records from the 2026-07-28 worldwide run, both correct and both
1195
1195
  // failed by the old rule:
1196
- // ZA SUPA STICKY filed 2023-07-12 registered 2025-04-23 expires 2033-07-12 (filing +10)
1197
- // PA TAKIS filed 2023-10-19 registered 2025-04-16 expires 2033-10-19 (filing +10)
1196
+ // ZA FROSTY GRIP filed 2023-07-12 registered 2025-04-23 expires 2033-07-12 (filing +10)
1197
+ // PA WAVA filed 2023-10-19 registered 2025-04-16 expires 2033-10-19 (filing +10)
1198
1198
  // It blocked delivery of a finished report — "expiry 2033 is not a 10-year cycle from claimed
1199
1199
  // registration 2025" — over data that was right. So the cycle may anchor on EITHER year when both
1200
1200
  // are stated; an expiry that fits neither is still a real arithmetic fault and still fails.
@@ -708,7 +708,7 @@ export function decideRecovery({ failClass, sig, reason = "", history = [], prio
708
708
  // the literal remedy for a provider that is down or refusing, so the second occurrence earns the
709
709
  // next rung. Everything else keeps repeat-terminal:
710
710
  // transient-but-wedged — nonzero_exit, unparseable_json, lane_wedge, a stage that times out every
711
- // time: a byte-identical repeat means it is stuck, and three parks is the Wilderness shape
711
+ // time: a byte-identical repeat means it is stuck, and three parks is the Open Country shape
712
712
  // stale — the remedy is a deterministic recompute; if the recompute did not settle it, running
713
713
  // it again cannot (the real fix for the observed stale kill is restamping, not retries)
714
714
  // unknown — its one fresh sample already failed identically, so it has disproved itself
@@ -110,9 +110,9 @@ export function findingsDocFor(runDir, ordinal) {
110
110
  // "jurisdiction"}` — there is no `register` and no `number`, on any record, ever. So both fallbacks
111
111
  // fired on every card and the composed bullet read
112
112
  //
113
- // - Source: [register-euipo · /mark/eu/018575624](https://euipo.europa.eu/…)
113
+ // - Source: [register-euipo · /mark/eu/099999992](https://euipo.europa.eu/…)
114
114
  //
115
- // where the corpus it replaced reads `- Source: [EUIPO · 018575624](…)`. An internal enum token and an
115
+ // where the corpus it replaced reads `- Source: [EUIPO · 099999992](…)`. An internal enum token and an
116
116
  // internal URI path, in the one line of a client-facing card whose job is to say which register holds
117
117
  // the record and under what number. publish/index.mjs quotes the correct shape verbatim in a comment of
118
118
  // its own, which is what makes the divergence provable rather than a matter of taste.
@@ -122,7 +122,7 @@ export function findingsDocFor(runDir, ordinal) {
122
122
  // delivered report.md" — and writing it is what caught this. The guard now lives beside the code it checks, and
123
123
  // this is the argument for building the guard an issue asks for even when the code already looks done.
124
124
  //
125
- // THE NUMBER comes off the URI's last segment: `/mark/eu/018575624` → `018575624`. That is a parse, and
125
+ // THE NUMBER comes off the URI's last segment: `/mark/eu/099999992` → `099999992`. That is a parse, and
126
126
  // a parse is what this file exists to avoid — but the alternative is a field that does not exist, and
127
127
  // the URI's shape is the driver's own (`/mark/<jurisdiction>/<id>`, dictated in gateway.mjs and bound
128
128
  // from the fetched record). A URI that does not match it yields NO number rather than a wrong one, and
@@ -136,7 +136,7 @@ export function findingsDocFor(runDir, ordinal) {
136
136
  // against evidence that does not exist.
137
137
  const REGISTER_OFFICE = Object.freeze({ "register-euipo": "EUIPO" });
138
138
 
139
- /** The registration number inside a driver record URI (`/mark/eu/018575624`), or `""`. PURE. */
139
+ /** The registration number inside a driver record URI (`/mark/eu/099999992`), or `""`. PURE. */
140
140
  export function registrationNumber(uri) {
141
141
  const m = /^\/mark\/[a-z]{2}\/([A-Za-z0-9._-]+)$/.exec(str(uri));
142
142
  return m ? m[1] : "";
@@ -44,7 +44,7 @@
44
44
  // `verdict` is one of:
45
45
  // "result" — the value states what happened. The name is honest.
46
46
  // "invocation" — the value states that something was CALLED. Fix by renaming, or by adding a sibling
47
- // that carries the result (the remedy `repairs.mjs:724` already uses, keeping
47
+ // that carries the result (the remedy createRepairLedger() in repairs.mjs already uses, keeping
48
48
  // `outcome` for existing readers beside `dispatch` and `verdict`).
49
49
  // "out-of-scope" — never reaches `_driver/*.json` or `run.jsonl`; `atWriteSite` is 0.
50
50
 
@@ -62,8 +62,8 @@ export const RESULT_NOUN_FIELDS = Object.freeze([
62
62
  { file: "driver/pipeline-knockout.mjs", noun: "recovered", sites: 1, atWriteSite: 1, verdict: "result",
63
63
  why: "written after the chunk files were actually removed" },
64
64
  { file: "driver/pipeline-knockout.mjs", noun: "outcome", sites: 3, atWriteSite: 3, verdict: "result",
65
- why: "the reviewing pass's own verdict on itself, read at each writing site: `nothing-flagged` after "
66
- + "the measurement came back empty, `applied` after the rewrite passed the merged gate and was "
65
+ why: "the reviewing pass's own verdict on itself, read at each writing site: `nothing-flagged` when "
66
+ + "the record had no line a reader meets first, `applied` after the rewrite passed the merged gate and was "
67
67
  + "written, and the `keep()` forms after the artifact or the gate refused it. Every one is a fact "
68
68
  + "about what already happened to the record, never the return of the call being logged" },
69
69
  { file: "driver/pipeline.mjs", noun: "closed", sites: 6, atWriteSite: 4, verdict: "result",
@@ -77,9 +77,12 @@ export function deriveScopeFacts({ instructedScope = null, plan = null, planExec
77
77
  // nothing. This reader took the state and dropped the number, and the coverage line then described a
78
78
  // slice that found nothing as one that "returned more records than could be listed in full".
79
79
  const hitsByQid = new Map((planExecution?.executed ?? []).map((x) => [x.qid, Number.isFinite(x.total_hits) ? x.total_hits : null]));
80
+ // A slice that ran and that the executor deferred: the register could not answer it (an owner name it never
81
+ // resolved), which is positive evidence it was not an overflow. See the producer.
82
+ const deferredExecutedSet = new Set((planExecution?.executed ?? []).filter((x) => x?.deferred === true).map((x) => x.qid));
80
83
  const missingSet = new Set(planExecution?.missing ?? []);
81
84
  // The skip's own reason: why the broader search did not enumerate. See the producer.
82
- const skippedByQid = new Map((planExecution?.skipped ?? []).map((x) => [x.qid, String(x.parent_state ?? "")]));
85
+ const skippedByQid = new Map((planExecution?.skipped ?? []).map((x) => [x.qid, { state: String(x.parent_state ?? ""), error: x.parent_error === true }]));
83
86
  const skippedSet = new Set(skippedByQid.keys());
84
87
  const deferredByQid = new Map((planExecution?.deferred ?? []).map((x) => [x.qid, String(x.reason ?? "")]));
85
88
  // A WAITING FAMILY THAT NEVER BECAME A SEARCH OF ITS OWN LEAVES THE COUNT. One the reading turn asked
@@ -131,8 +134,11 @@ export function deriveScopeFacts({ instructedScope = null, plan = null, planExec
131
134
  // AN ABSENT STATE IS NOT EVIDENCE, and this is the same trap as the count below: a receipt written
132
135
  // before the producer carried the parent's state says nothing about why, and reading its silence as
133
136
  // a failure would relabel every archived run's skips. Silence leaves the line as it was.
137
+ //
138
+ // AND `incomplete` IS NOT EVIDENCE OF A CROWD ON ITS OWN: the executor writes a provider error as an
139
+ // `incomplete` band with `error: true` beside it, and the producer now carries that flag with the skip.
134
140
  const parent = skippedByQid.get(e.qid);
135
- if (parent && parent !== "incomplete") a.skipped_after_failure++;
141
+ if (parent && (parent.error || (parent.state && parent.state !== "incomplete"))) a.skipped_after_failure++;
136
142
  continue;
137
143
  }
138
144
  if (missingSet.has(e.qid)) { a.missing.push(e.qid); continue; }
@@ -170,7 +176,9 @@ export function deriveScopeFacts({ instructedScope = null, plan = null, planExec
170
176
  // rather than too much. Only a MEASURED zero demotes it: a null is a count that could not be
171
177
  // taken, which is not evidence either way, and reading absence as evidence is the defect this
172
178
  // whole file keeps meeting.
173
- if (hitsByQid.get(e.qid) === 0) a.incomplete_found_nothing++;
179
+ // An owner sweep the executor deferred is the same fact reached another way: the register could not
180
+ // resolve the name, so the search found nothing to list, and there is no count to measure.
181
+ if (hitsByQid.get(e.qid) === 0 || deferredExecutedSet.has(e.qid)) a.incomplete_found_nothing++;
174
182
  }
175
183
  } else {
176
184
  // a plan entry in NO execution bucket is UNACCOUNTED — the fail-closed reading is "missing"
@@ -99,7 +99,7 @@ export function checkClearanceScopeRules({ job = null, profile = null, resolved
99
99
  //
100
100
  // Everything a CLIENT reads quotes `named`. This module was the one caller that discarded the
101
101
  // originals, so the wall refused a two-country Full country search naming "GB", "FR" while the door
102
- // refused the identical request naming "United Kingdom", "France" — products.mjs:141 states exactly
102
+ // refused the identical request naming "United Kingdom", "France" — tally() in products.mjs states exactly
103
103
  // that failure ("a refusal that quotes \"EU\" at someone who typed \"European Union\" sends them
104
104
  // hunting for a value they never used") and territory-tiers.mjs restates it for partitionTerritories.
105
105
  // The COUNT is the same either way: canonicalize dedups on code and products.mjs `tally` dedups on
@@ -131,7 +131,7 @@ export function checkClearanceScopeRules({ job = null, profile = null, resolved
131
131
  // `nativeLanguage: true` over a knockout base) and through any account whose defaultProduct does not
132
132
  // carry it.
133
133
  //
134
- // That is the shape products.mjs:293 refuses `caseLaw` for in as many words — "a flag accepted and
134
+ // That is the shape CASE_LAW_NOT_A_REQUEST declared in products.mjs refuses `caseLaw` for in as many words — "a flag accepted and
135
135
  // dropped is the worst available shape: whoever sent it believes they bought the deep reading, and
136
136
  // nothing anywhere disagrees" — and it was this build's own doctrine broken by this build's own code.
137
137
  //
@@ -1165,7 +1165,26 @@ export function countJobMarks(job) {
1165
1165
  // The mark → filesystem-key derivation (research payloads, fixtures). ONE definition — the knockout
1166
1166
  // lane keys per-mark artifacts on it, so two batch marks that collide here ("MOTO X" / "MOTO-X") would
1167
1167
  // silently share a research payload; every door checks collisions with THIS function.
1168
- export const kebab = (s) => String(s ?? "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "mark";
1168
+ //
1169
+ // THE KEY IS UNIQUE FOR TWO NAMES THAT DIFFER IN ANY LETTER OR NUMBER, in any script. The readable part
1170
+ // keeps only `a-z0-9`, because the key is also a report route segment. It used to be the whole key, so
1171
+ // every name written wholly in Greek, Cyrillic or Han keyed as `mark`, and two different names of that
1172
+ // kind were refused as duplicates at intake; two Latin names differing only in an accented letter merged
1173
+ // the same way. A name whose letters and numbers the readable part does not hold in full now carries a
1174
+ // short hash of them, NFKC-folded and lowercased. Spacing, punctuation, symbols and case still change
1175
+ // nothing, so a true duplicate still collides. A name the readable part already held in full keys exactly
1176
+ // as before, byte for byte: research payloads, published report files and per-name links were keyed on it.
1177
+ // SYMBOLS GO BEFORE THE FOLD. NFKC turns ™ into "TM" and ℠ into "SM", which would move "BRAND™" off the key
1178
+ // it shares with "BRAND"; symbols are the only class NFKC turns into letters or numbers, so dropping them
1179
+ // first leaves the fold acting on letters and numbers alone.
1180
+ const asciiKey = (s) => String(s ?? "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1181
+ const nameLetters = (s) => String(s ?? "").replace(/\p{S}/gu, "").normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "");
1182
+ export const kebab = (s) => {
1183
+ const ascii = asciiKey(s);
1184
+ const letters = nameLetters(s);
1185
+ if (letters === ascii.replace(/-/g, "")) return ascii.slice(0, 60) || "mark";
1186
+ return `${(ascii || "mark").slice(0, 51).replace(/-+$/, "")}-${createHash("sha256").update(letters).digest("hex").slice(0, 8)}`;
1187
+ };
1169
1188
  export function kebabCollisions(names) {
1170
1189
  const seen = new Map(); const out = [];
1171
1190
  for (const n of names ?? []) {
@@ -137,7 +137,7 @@ they render as nothing: no reader ever sees them.
137
137
  not need to match the template's wording, and you will not be failed for choosing different words.
138
138
 
139
139
  ```markdown
140
- # Common-law findings — Dawn: Legends of Thornmantle (2026-05-11)
140
+ # Common-law findings — Dusk: Tales of Thornmantle (2026-05-11)
141
141
 
142
142
  ## Summary
143
143
 
@@ -146,7 +146,7 @@ not need to match the template's wording, and you will not be failed for choosin
146
146
  - Findings surfaced: 12 risk-relevant + 3 competitor intel + 1 PR risk
147
147
  - Open verification flags: 2
148
148
 
149
- ## Findings — Mark: Dawn: Legends of Thornmantle
149
+ ## Findings — Mark: Dusk: Tales of Thornmantle
150
150
  <!-- clearotron:section=findings -->
151
151
 
152
152
  ### Consumer-confusion risks (gaming-industry overlap)
@@ -155,24 +155,24 @@ For game-title rows, the `developer_of_record` and `publisher_of_record` columns
155
155
 
156
156
  | Finding | Source / Platform | URL | developer_of_record | publisher_of_record | Type | Notes |
157
157
  |---|---|---|---|---|---|---|
158
- | "Chronicles of Ember" | Steam | https://... | Dreamatrix | Topware Interactive | Direct conflict — similar gaming title | "Mostly negative" reviews; commercially active 2013/2015 |
159
- | "Thornmantle" | itch.io | https://... | Thornfall Games | not extracted | Direct conflict — identical game-title | Single-player adventure, "made for SAE Studio 2 Home Brief"; little online presence; not reviewed in 7 years |
158
+ | "Tales of Dusk" | Steam | https://... | Glimmerhouse | Northgale Interactive | Direct conflict — similar gaming title | "Mostly negative" reviews; commercially active 2013/2015 |
159
+ | "Thornmantle" | itch.io | https://... | Thornfall Games | not extracted | Direct conflict — identical game-title | Single-player adventure made for a game-design course brief; little online presence; not reviewed in 7 years |
160
160
  | "Thornmantle: Of Magic and Power" | Moby Games | https://... | not extracted | not extracted | Direct conflict — historical gaming title | Released 2007; status unavailable; verify-publisher flag set |
161
- | "Thornmantle" Astragate ARPG | News / web | https://... | Astragate | not extracted | Direct conflict — gaming title | Browser game; appears cancelled |
162
- | "Ysolde the Thornmantle" | Pathfinder wiki | https://... | N/A | Grimtable Press | Conceptual — deity name in tabletop RPG | Tabletop, not video game; commercial risk in RPG space |
161
+ | "Thornmantle" Starhollow ARPG | News / web | https://... | Starhollow | not extracted | Direct conflict — gaming title | Browser game; appears cancelled |
162
+ | "Ysolde the Thornmantle" | tabletop RPG wiki | https://... | N/A | Grimtable Press | Conceptual — deity name in tabletop RPG | Tabletop, not video game; commercial risk in RPG space |
163
163
 
164
164
  ### Commercial awareness (identical/similar in unrelated fields)
165
165
 
166
166
  | Finding | Source / Platform | URL | Notes |
167
167
  |---|---|---|---|
168
- | "Raising Your Play" | HP marketing | https://... | HP uses tagline for gaming hardware; no register protection found (flagged for clearance-register cross-check) |
169
- | 1,600+ "Dawn" titles on Steam | Steam | https://... | Crowded field — supportive evidence |
168
+ | "Raising Your Play" | Brightwick marketing | https://... | Brightwick uses tagline for gaming hardware; no register protection found (flagged for clearance-register cross-check) |
169
+ | 1,600+ "Dusk" titles on Steam | Steam | https://... | Crowded field — supportive evidence |
170
170
 
171
171
  ### Competitor intelligence
172
172
 
173
173
  | Finding | Source / Platform | URL | Notes |
174
174
  |---|---|---|---|
175
- | Sony "Pulse Elevate" portfolio | Sony products | https://... | Sony uses "Elevate" in audio products; flagged for clearance-register cross-check |
175
+ | Veltrona "Pulse Raise" portfolio | Veltrona products | https://... | Veltrona uses "Raise" in audio products; flagged for clearance-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
@@ -187,7 +187,7 @@ means *blackberry*" is context, not a clearance: a connotation reads clean only
187
187
 
188
188
  | Finding | Source | Notes |
189
189
  |---|---|---|
190
- | meaning readings surfaced | Urban Dictionary / Wikipedia / news / forums | per form, the readings the search returned + benign/loaded label — e.g. `ELEVATE → ordinary verb "raise" (benign)`; near-forms `RAIZE / RAYSE → no slang/gang/offensive reading`. Write `(None identified)` as the *bottom line* only after the readings are laid out and none is loaded. |
190
+ | meaning readings surfaced | Urban Dictionary / Wikipedia / news / forums | per form, the readings the search returned + benign/loaded label — e.g. `RAISE → ordinary verb "lift" (benign)`; near-forms `RAIZE / RAYSE → no slang/gang/offensive reading`. Write `(None identified)` as the *bottom line* only after the readings are laid out and none is loaded. |
191
191
 
192
192
  A clean PR/connotation row MUST cite its search — add a `**Connotation-search source:** <URL | "perplexity_research — no result">` line. The driver dictates the meaning sweep into the grid (the matter frame's meaning questions) and the plugin records every query into the ledger's `extras.pr_risk[]`; the `commonLaw` validator rejects a `(None identified)` claim with no recorded queries (`connotation_search_missing`). An empty-results search is a clean receipt; a *missing* search is not.
193
193
 
@@ -340,13 +340,13 @@ forms (and the gap form) — this is what the driver's receipt gate counts:
340
340
 
341
341
  | Variant | Platform | Result |
342
342
  |---|---|---|
343
- | Dawn: Legends of Thornmantle | Steam | No results |
344
- | Dawn: Legends of Thornmantle | Epic Games Store | No similar listings (6 candidates reviewed) |
345
- | Dawn: Legends of Thornmantle | Google Play | No results |
346
- | Dawn: Legends of Thornmantle | Apple App Store | No similar listings (3 candidates reviewed) |
347
- | Dawn: Legends of Thornmantle | Microsoft Store | No results |
348
- | Dawn: Legends of Thornmantle | itch.io | Similar listing(s) found — see Findings (2 candidates) |
349
- | Dawn: Legends of Thornmantle | web | No similar listings (8 candidates reviewed) |
343
+ | Dusk: Tales of Thornmantle | Steam | No results |
344
+ | Dusk: Tales of Thornmantle | Epic Games Store | No similar listings (6 candidates reviewed) |
345
+ | Dusk: Tales of Thornmantle | Google Play | No results |
346
+ | Dusk: Tales of Thornmantle | Apple App Store | No similar listings (3 candidates reviewed) |
347
+ | Dusk: Tales of Thornmantle | Microsoft Store | No results |
348
+ | Dusk: Tales of Thornmantle | itch.io | Similar listing(s) found — see Findings (2 candidates) |
349
+ | Dusk: Tales of Thornmantle | web | No similar listings (8 candidates reviewed) |
350
350
  | エバーライト | Steam | not executed — coverage-limited (see ledger) |
351
351
 
352
352
  ### Coverage ledger (feeds synthesis coverage-honesty + skeptic audit)
@@ -366,8 +366,8 @@ Record the same statuses by calling `record_coverage_status`, passing `grid_spec
366
366
 
367
367
  | Trigger | Suggested cross-check |
368
368
  |---|---|
369
- | HP appears using "RAISING YOUR PLAY" | Check register: does HP have any elevate-related trademark in target classes? |
370
- | Sony "Pulse Elevate" portfolio | Check register: any Sony elevate filings in target classes? |
369
+ | Brightwick appears using "RAISING YOUR PLAY" | Check register: does Brightwick have any raise-related trademark in target classes? |
370
+ | Veltrona "Pulse Raise" portfolio | Check register: any Veltrona raise filings in target classes? |
371
371
  | Thornfall Games "Thornmantle" (itch.io) | Check register: any Thornfall trademark protection? |
372
372
 
373
373
  ### Audit trail
@@ -377,8 +377,8 @@ Record the same statuses by calling `record_coverage_status`, passing `grid_spec
377
377
  |---|---|---|---|
378
378
  | 1 | Grid (sandbox) | Search-as-code grid: 9 variants × 7 platforms + extras | 63 cells, 0 gaps, 487 candidates recorded; 12 risk-relevant after judgment |
379
379
  | 2 | Famous-mark | None flagged in manifest | (not run) |
380
- | 3 | Follow-up | HP gaming portfolio + "raising your play" deeper trace | 3 additional findings |
381
- | 4 | Follow-up | Sony pulse-elevate portfolio | 2 additional findings |
380
+ | 3 | Follow-up | Brightwick gaming portfolio + "raising your play" deeper trace | 3 additional findings |
381
+ | 4 | Follow-up | Veltrona pulse-raise portfolio | 2 additional findings |
382
382
 
383
383
  **Grid program receipt** — the executed sandbox program returned by the tool, verbatim:
384
384
 
@@ -389,7 +389,7 @@ Record the same statuses by calling `record_coverage_status`, passing `grid_spec
389
389
  ### Open verification flags
390
390
 
391
391
  - Thornfall Games "Thornmantle" itch.io listing — URL needs re-verification (404'd during follow-up call)
392
- - HP "Raising Your Play" marketing presence in EU — coverage was thin, may need a region-specific follow-up
392
+ - Brightwick "Raising Your Play" marketing presence in EU — coverage was thin, may need a region-specific follow-up
393
393
  ```
394
394
 
395
395
  ## Process
@@ -135,8 +135,8 @@ drop can be neither examined nor audited. Batching stays correct for *retrieval*
135
135
  enumeration, `_query` attribution); it is only forbidden as a unit of dismissal.
136
136
 
137
137
  One entry, one record, one reason. A `drop_reason` is the judgment alone — the uri, verdict, class and
138
- status are rendered beside it: `dropped — off-field (relevance gate): DAWN-only hit; the
139
- descriptive-compound risk theory retains DAWN-only hits only in gaming, and this is film production`.
138
+ status are rendered beside it: `dropped — off-field (relevance gate): DUSK-only hit; the
139
+ descriptive-compound risk theory retains DUSK-only hits only in gaming, and this is film production`.
140
140
 
141
141
  ### Dominant-element reconciliation (driver-gated — every screened composite ENDS somewhere)
142
142
 
@@ -435,7 +435,7 @@ read. Keep if plausibly on-point; drop only genuine field-irrelevance, or a near
435
435
  | Archetype | Keep when | Drop when |
436
436
  |---|---|---|
437
437
  | **Coined word** | Mark text contains a phonetic/visual neighbour of the coined element; or is the coined element exact-match | Mark text shares only an unrelated real-word token via tokenisation (e.g., searching coined THORNMANTLE and surfacing a mark whose only overlap is a "LIGHT" token) |
438
- | **Descriptive compound** | Mark contains the distinctive anchor; OR is an identical/near-identical compound-phrase match; OR is a single-element registered mark on a common element with goods/services in the target field | Mark is a 5+ element compound that happens to share one common token (e.g., a film studio's "DAWN OF JUSTICE" filing surfaced when searching "Dawn: Legends of Thornmantle" — DAWN is common, surrounding context unrelated) |
438
+ | **Descriptive compound** | Mark contains the distinctive anchor; OR is an identical/near-identical compound-phrase match; OR is a single-element registered mark on a common element with goods/services in the target field | Mark is a 5+ element compound that happens to share one common token (e.g., a film studio's "DUSK OF VALOR" filing surfaced when searching "Dusk: Tales of Thornmantle" — DUSK is common, surrounding context unrelated) |
439
439
  | **Slogan** | Mark matches the slogan structure (same template, same semantic field, family-pattern hit, verb-swap, slang variant, co-brand wildcard); OR is the unique distinctive token of the slogan if any | Single-word hit on a slogan element in an unrelated context (DAWN alone surfaced when searching "DAWN OF NEW DAY" slogan); slogan in a wildly different semantic field with no template overlap |
440
440
  | **Acronym / initialism** | Mark text contains the letter string in target classes; OR is the expansion form of the acronym | Random word that happens to contain the letters as a substring of a longer word |
441
441
  | **Device-led** (modifier) | Standard tests for primary archetype, plus visual similarity per design-code search | Pure-text noise with no visual signal |
@@ -46,13 +46,13 @@ the cross-cutting relevance / sufficiency / owner-aggregation / prioritisation o
46
46
 
47
47
  ## Recipe 1 — Compound tagline (multi-word mark with distinctive element)
48
48
 
49
- **When to use:** mark has 3+ words where 1 element is distinctive and the others are common (e.g., "Dawn: Legends of Lumengarde" — LUMENGARDE distinctive, DAWN + LEGENDS common).
49
+ **When to use:** mark has 3+ words where 1 element is distinctive and the others are common (e.g., "Dusk: Tales of Lumengarde" — LUMENGARDE distinctive, DUSK + TALES common).
50
50
 
51
51
  **Pattern:**
52
52
 
53
53
  ```
54
54
  1. Crowd descriptor — count-only probe each saturated common element (limit=1, fields=[uri])
55
- → name:DAWN classes 9,28,41,42 → write an `incomplete` crowd-descriptor block (count + reason)
55
+ → name:DUSK classes 9,28,41,42 → write an `incomplete` crowd-descriptor block (count + reason)
56
56
  → name:LEGEND classes 9,28,41,42 → write an `incomplete` crowd-descriptor block
57
57
  (these enumerate nothing — they describe the crowd for judgment)
58
58
 
@@ -60,11 +60,11 @@ the cross-cutting relevance / sufficiency / owner-aggregation / prioritisation o
60
60
  → name:LUMENGARDE classes 9,28,41,42 → enumerated|incomplete block (every record carried forward)
61
61
 
62
62
  3. ENUMERATE each common element as a named query in the in-scope classes
63
- → name:DAWN match=exact classes 9,28,41,42 → register_enumerate (exact-in-class name-list)
63
+ → name:DUSK match=exact classes 9,28,41,42 → register_enumerate (exact-in-class name-list)
64
64
  → name:LEGEND match=exact classes 9,28,41,42 → register_enumerate
65
65
 
66
66
  4. ENUMERATE the compound phrase
67
- → name:"Dawn: Legends of Lumengarde" match=phrase classes 9,28,41,42 → register_enumerate
67
+ → name:"Dusk: Tales of Lumengarde" match=phrase classes 9,28,41,42 → register_enumerate
68
68
 
69
69
  5. ENUMERATE a cross-class merch query if any identical match
70
70
  → name:<exact mark> match=exact classes 25 → register_enumerate
@@ -168,11 +168,11 @@ For each transliteration variant in the manifest:
168
168
 
169
169
  ```
170
170
  1. Crowd descriptors — count-only probes on the saturated common elements (limit=1, fields=[uri])
171
- → name:ELEVATE classes 9/28/41/42 → `incomplete` crowd-descriptor block
172
- → name:GAME classes 9/28/41/42 → `incomplete` crowd-descriptor block
171
+ → name:RAISE classes 9/28/41/42 → `incomplete` crowd-descriptor block
172
+ → name:PLAY classes 9/28/41/42 → `incomplete` crowd-descriptor block
173
173
 
174
174
  2. Crowd descriptor — partial-phrase prefix count (the narrative anchor)
175
- → phrase:"ELEVATE YOUR" classes <target> limit=1 → `incomplete` block ("234 live ELEVATE YOUR ___")
175
+ → phrase:"RAISE YOUR" classes <target> limit=1 → `incomplete` block ("234 live RAISE YOUR ___")
176
176
 
177
177
  3. ENUMERATE the compound phrase, 3 match-modes (register_enumerate each)
178
178
  → exact:"RAISE YOUR PLAY" classes <target> → identical-mark band
@@ -186,12 +186,12 @@ For each transliteration variant in the manifest:
186
186
  → ...
187
187
 
188
188
  5. ENUMERATE wildcard variant queries (per manifest)
189
- → name:"Elevate * game" classes <target> → register_enumerate
190
- → name:"Elevate your *" classes <target> → register_enumerate
191
- → name:"* your game" classes <target> → register_enumerate
189
+ → name:"Raise * play" classes <target> → register_enumerate
190
+ → name:"Raise your *" classes <target> → register_enumerate
191
+ → name:"* your play" classes <target> → register_enumerate
192
192
 
193
193
  6. ENUMERATE each common element's exact-in-class name-list (register_enumerate)
194
- → name:ELEVATE match=exact classes <target> (scope by CLASS + REGION, not a product/goods term)
194
+ → name:RAISE match=exact classes <target> (scope by CLASS + REGION, not a product/goods term)
195
195
 
196
196
  7. ENUMERATE a cross-class merch query if identical match
197
197
  ```
@@ -143,7 +143,7 @@ The order matters. Specifically:
143
143
  | `"PLAYVERSE LEVEL UP GAMING"` | `"Level up together"` | false |
144
144
  | `"It's Your Play"` | `"In Your Play"` | false (apostrophe matters) |
145
145
 
146
- These fixtures should be encoded in test cases for any agent or analyzer implementing the rule. Worked example for the "Dawn: Legends of Lumengarde" matter: "Dawn: Legends of Lumengarde" ≡ "DAWN LEGENDS OF LUMENGARDE" ≡ "dawn legends of lumengarde" — all identical. "Dawn of Lumengarde" or "Dawn: Legends" — NOT identical.
146
+ These fixtures should be encoded in test cases for any agent or analyzer implementing the rule. Worked example for the "Dusk: Tales of Lumengarde" matter: "Dusk: Tales of Lumengarde" ≡ "DUSK TALES OF LUMENGARDE" ≡ "dusk tales of lumengarde" — all identical. "Dusk of Lumengarde" or "Dusk: Tales" — NOT identical.
147
147
 
148
148
  ## Chinese status strings
149
149
 
@@ -150,7 +150,7 @@ band its plan entries call for is missing is refused as `named_band_missing`.
150
150
  ### Per-axis specifics
151
151
 
152
152
  - **`saturation-probe`** — fires when any element is flagged `saturation: high`/`very-high`, plus a partial-phrase structural-prefix probe for slogan/descriptive-compound multi-word marks. Run count-only probes (`limit:1, fields:["uri"]`) capturing `total_hits`, and write **one `incomplete` crowd-descriptor block per probed element** (`fetched:0`, `reason:"crowd descriptor — saturated element <X>, count-only"`). It measures how crowded an element is — it **enumerates nothing and clears nothing**. Skip (not applicable) if all elements are distinctive/low-saturation. **A meaning-translation variant (`translit-*-meaning`) is NOT special-cased into a drop:** an everyday-word-scale count is just a crowd descriptor; whether the field-scoped named slice inside it matters is judgment's call — the funnel surfaces the count, and the **primary-sweep** axis runs the field-scoped named enumeration where the frame says that market matters (see `transliteration-numeric`). Record `translit-too-generic` in the descriptor `reason` as a context note (NOT a drop / re-narrow / swap-to-phonetic signal). An implausibly LOW count on a translation that ought to have peers is the separate `translit-underretrieved` context note — also recorded in the descriptor `reason`, never acted on as a drop. Both are signals the lawyer reads.
153
- - **`primary-sweep`** — always applicable. Run the playbook's named variant queries: the exact mark + each `phrase-substitution`, `visual-substitution`, family-pattern wildcard, and slang variant the manifest lists, **each via `register_enumerate`** (the tool guarantees the page loop). Owns the family-pattern / phrase-substitution / slang / visual-substitution variant queries. **Owns the dead-inclusive named enumeration** (its `register_enumerate` calls carry live AND dead records forward with their status — see step 6; there is no separate "dead-but-identical" or "D4" pass, because the funnel no longer filters dead records). **On a saturated unit, owns the [named-band enumeration of the dangerous category](#exact-in-class-live-floor-primary-sweep-unit-owns-it)** (the exact mark + the **distinctive** formative root / dominant token as the substring-in-scope-class-live named slice, per major+material jurisdiction, plus the phonetic fringe) — enumerated, not sampled. **The named enumeration covers a saturated `translit-*-meaning` meaning token too, where the frame says that market matters (see `transliteration-numeric`)** (scoped to the filed in-scope Nice classes as the `nice_classes` × `regions` filter on the `register_enumerate` call, NOT goods-words ANDed into the search text) — not only the Latin dominant element / formative root. **But a COMMON component that is NOT the distinctive anchor** (a "stripped" common word the manifest marks common/descriptive — DAWN / LEGENDS / GREAT / OUTDOORS) **is count-only, owned by `saturation-probe`, and is NOT enumerated here**: `saturation-probe` already counts it and hands that count up; primary-sweep does **not** run the substring / per-major / phonetic enumeration on it and does **not** author a second crowd block for it (see the linked section — wholesale-enumerating a common component per-jurisdiction is the mega-crowd grind that SIGKILLs the stage).
153
+ - **`primary-sweep`** — always applicable. Run the playbook's named variant queries: the exact mark + each `phrase-substitution`, `visual-substitution`, family-pattern wildcard, and slang variant the manifest lists, **each via `register_enumerate`** (the tool guarantees the page loop). Owns the family-pattern / phrase-substitution / slang / visual-substitution variant queries. **Owns the dead-inclusive named enumeration** (its `register_enumerate` calls carry live AND dead records forward with their status — see step 6; there is no separate "dead-but-identical" or "D4" pass, because the funnel no longer filters dead records). **On a saturated unit, owns the [named-band enumeration of the dangerous category](#exact-in-class-live-floor-primary-sweep-unit-owns-it)** (the exact mark + the **distinctive** formative root / dominant token as the substring-in-scope-class-live named slice, per major+material jurisdiction, plus the phonetic fringe) — enumerated, not sampled. **The named enumeration covers a saturated `translit-*-meaning` meaning token too, where the frame says that market matters (see `transliteration-numeric`)** (scoped to the filed in-scope Nice classes as the `nice_classes` × `regions` filter on the `register_enumerate` call, NOT goods-words ANDed into the search text) — not only the Latin dominant element / formative root. **But a COMMON component that is NOT the distinctive anchor** (a "stripped" common word the manifest marks common/descriptive — DUSK / TALES / GREAT / OUTDOORS) **is count-only, owned by `saturation-probe`, and is NOT enumerated here**: `saturation-probe` already counts it and hands that count up; primary-sweep does **not** run the substring / per-major / phonetic enumeration on it and does **not** author a second crowd block for it (see the linked section — wholesale-enumerating a common component per-jurisdiction is the mega-crowd grind that SIGKILLs the stage).
154
154
  - **`transliteration-numeric`** — fires when the manifest has `translit-*` variants or numeric-substitution variants. Ask one script question per market the frame names. Where the register files foreign marks by their romanised form, and it says so, the Latin question already covers them; do not ask it again in other scripts. A word's meaning in another language is asked only where the frame says that market matters and buyers would read the word as the mark. For each numeric-substitution variant enumerate its query. Owns the foreign-transliteration + numeric-substitution variant queries. Skip if the manifest classifies the mark English-only / single-language. (Multi-script accumulation is no longer a window risk for the unit — `register_enumerate` owns the page loop and a crowd returns `incomplete`, so the unit does not deep-page raw rows itself.)
155
155
  - **`incumbent-class`** — fires when the manifest has an `industry_incumbent_alert`. `register_enumerate` the named band in the matter's in-scope Nice set and markets — **never an all-class incumbent sweep** (that is the unscoped crowd that timed this very axis out live). **Beyond the NAMED watchlist incumbents, the named enumeration is an UNNAMED-OWNER exact-in-class-live enumeration in those classes** — the watchlist seeds prioritise *attention* (judgment's use), they do **not** bound the funnel's enumeration. Every exact-token-in-class-live record crosses the firewall whether or not its owner was pre-listed; the watchlist is provably incomplete, so it never decides what the funnel enumerates. Skip only if there is no incumbent alert AND the frozen plan carries no entries on this axis — the plan is the search authority. **The plan carries no owner lane from the manifest's watchlist.** Read owners from the records this axis and the primary sweep returned. Propose an owner slice only where an owner's other marks could change the assessment, for example when the records show a family of marks sharing the element. A bare-owner count is never written up as a reviewed portfolio or as "portfolio too large, noted". Read an owner's portfolio as far as it answers a question about this owner: can it block, would it, and what kind of filer it is. Never its whole register because it is an incumbent.
156
156
 
@@ -229,12 +229,13 @@ sensitivity, or a commercial partnership cannot lift a finding past what the fra
229
229
  they only change *whether they'd win* or the *character* of the dispute, and the framework then produces
230
230
  the band.
231
231
 
232
- ### Enforcer profiling — mandatory for findings above the lowest band
232
+ ### Enforcer profiling — mandatory for findings rated medium risk or higher
233
233
 
234
234
  Assess: Is this a portfolio brand? TTAB/UDRP history? Institutional vs individual filer? "No large brands
235
235
  or assertive enforcers" is a practical factor the staff lawyer weights heavily. *(`courtlistener__search` covers US
236
236
  federal courts incl. CAFC trademark-appeal opinions — NOT raw TTAB proceedings. For TTAB direct
237
- (oppositions, cancellations, ex parte), source is TTABVUE — separate integration, not yet wired.)*
237
+ (oppositions, cancellations, ex parte), source is TTABVUE — separate integration, not yet wired.)* The Step 3.5 query (`phase2-execution.md`) asks for
238
+ the owner's enforcement record; what it finds is a lead, cited inline, never a verified authority.
238
239
 
239
240
  Profiling informs the **business / practical read only — it never raises the legal read** (the read is the
240
241
  confusion comparison; see *The consumer confusion test*). When the signals cut both ways — a broad
@@ -17,7 +17,7 @@
17
17
  - [Variant execution strategy (register layer)](#variant-execution-strategy-register-layer)
18
18
  - [Step 2.6 — Skeptic review](#step-26--skeptic-review-a-fresh-eyes-self-audit-before-you-trust-the-findings)
19
19
  - [Step 3 — Cross-pollination (Option D — deterministic cross-checks, cap N=10)](#step-3--cross-pollination-option-d--deterministic-cross-checks-cap-n10)
20
- - [Step 3.5 — Actual-use check (mandatory for Medium/High/Very High register hits)](#step-35--actual-use-check-mandatory-for-mediumhighvery-high-register-hits)
20
+ - [Step 3.5 — Actual-use and enforcement check (mandatory for register hits rated medium risk or higher)](#step-35--actual-use-and-enforcement-check-mandatory-for-register-hits-rated-medium-risk-or-higher)
21
21
  - [Step 3.6 — Owner workup (mandatory for high-risk findings)](#step-36--owner-workup-mandatory-for-high-risk-findings)
22
22
  - [Step 4 — Joint synthesis](#step-4--joint-synthesis)
23
23
  - [Step 4.5 — Case-law grounding](#step-45--case-law-grounding)
@@ -78,7 +78,7 @@ skipped step must be logged with its reason in the unit's digest audit.
78
78
  1. **Macro probes** — one search per element of the compound + one for the full compound phrase (limit=10 each), to establish saturation levels. Minimum 3 searches. SKIP only if all elements are non-common-words (rare).
79
79
  2. **Compound-phrase sweeps** — the compound phrase across **three match-modes (exact, phrase, default)**, limit=100 per search. Minimum 3 searches. SKIP only if the phrase is a single word (then it's a single-word funnel, not compound).
80
80
  3. **Single-word coverage hunts** — for each element marked "demote to filter" in the manifest, one search with product/industry filters applied (limit=50). Minimum 1 per such element.
81
- 4. **Wildcard reorders** — if the manifest lists word-order variants, at least one wildcard sweep per distinct ordering (e.g. `Elevate * game`, `game * Elevate`).
81
+ 4. **Wildcard reorders** — if the manifest lists word-order variants, at least one wildcard sweep per distinct ordering (e.g. `Raise * play`, `play * Raise`).
82
82
  5. **Numeric-substitution and transliteration sweeps** — execute every sweep marked ✅ (requires verification) in the variant manifest, UNLESS the manifest classifies the mark as English-only (single-language Recipe 1 pattern). Skipping any ✅ requires a stated reason in the unit's digest audit.
83
83
  6. **Material-jurisdiction sub-queries** — a **protected, non-yielding** line item, NOT folded into the sweep budget. Run one scoped sub-query per jurisdiction `matter-context` declares materially-matters (no top-N cap). These outrank within-axis breadth: if budget is tight, an extra script group (step 5) yields and is logged `coverage-limited` — a material jurisdiction is never dropped to fund breadth, and a jurisdiction that genuinely can't run is logged `deferred`, never silent.
84
84
 
@@ -230,9 +230,9 @@ Cap-overflow MUST be flagged in the deliverable audit trail with a specific reas
230
230
 
231
231
  Every cross-check (including "checked, found nothing") logged in the unified audit trail as proof-of-work. The collaborator value-add (vs assistant) is in this section: synthesis can be decisive ("HP uses, does not register → Level 2 B") rather than tentative ("recommend the staff lawyer check HP register").
232
232
 
233
- ## Step 3.5 — Actual-use check (mandatory for register hits above the framework's lowest band)
233
+ ## Step 3.5 — Actual-use and enforcement check (mandatory for register hits rated medium risk or higher)
234
234
 
235
- Before synthesis, for **every register finding** advisory-rated **above the framework's lowest band**, run a marketplace check via `perplexity_research` to determine whether the registered mark is **actually being used in the marketplace within the scope of the current search** (i.e. for the goods/services and field this clearance targets).
235
+ Before synthesis, for **every register finding** advisory-rated on a rung **the framework rates medium risk or higher** (the stage message names those rungs; a rung below that is not checked, on any client's ladder), run a marketplace check via `perplexity_research` to determine whether the registered mark is **actually being used in the marketplace within the scope of the current search** (i.e. for the goods/services and field this clearance targets). **The same query also asks for the owner's enforcement record:** whether the owner has opposed, disputed or sued over this mark or a similar one.
236
236
 
237
237
  Example: a JELLY register hit owned by Jellycat (a toy company) for Class 9 video game software — check whether Jellycat actually sells or markets video games under JELLY. If they don't (their commercial use is toys), note the filed-vs-used distinction in Key Factors and weigh it as a Stage-2 factor together with revocation-vulnerability — no automatic downgrade (see `synthesis-rules.md` → *Actual-use assessment results*).
238
238
 
@@ -244,6 +244,7 @@ Example: a JELLY register hit owned by Jellycat (a toy company) for Class 9 vide
244
244
  - If actual use in the relevant field is **not found** (despite the filing), do **NOT** automatically downgrade — weigh the lack of field use as a Stage-2 factor together with revocation-vulnerability (`synthesis-rules.md` → *Actual-use assessment results* / *Rule on downgrade quantum*), and note in Key Factors: "Register filing claims [field] but no marketplace use identified — filed-vs-used noted; weighed with revocability."
245
245
  - This check measures *use in the field*, not registry status: a renewal / re-registration / "Registered" status is administrative upkeep, not marketplace use, and does not satisfy the actual-use check or defeat a non-use mitigant (see `synthesis-rules.md` → "Administrative liveness is not market use").
246
246
  - Owner-portfolio-bound common-law checks already counted in this step count toward the Option D cap (Step 3).
247
+ - **Enforcement the query finds is a lead, not a verified authority** (the ENFORCEMENT AUTHORITY rule in the stage message): it informs the enforcer meter with basis `inferred-from-signal`, and its URL is cited inline in the finding's prose. If the query finds no enforcement record, the enforcer is `low` with basis `inferred-from-signal`, and the finding says the search found none. The enforcer stays `unknown`, and nothing is claimed, only when the query could not run.
247
248
 
248
249
  **Output contract — ENFORCED by the driver (spec 11), not optional.** For every Composite 3+ finding whose
249
250
  mitigant or verdict turns on the **absence** of use (it says the mark is "not in actual use" / "unused" / has
@@ -360,7 +361,7 @@ the verdict** (the driver decides this in code):
360
361
  Runs **ONCE**, after all marks have completed Steps 2–4.5. Review findings across all marks and identify:
361
362
 
362
363
  1. **Shared findings:** a single entity/finding relevant to more than one proposed mark (e.g., "Prize Crate Retail" is relevant to both "PRIZE CRATE" and "CAVERN CRATES")
363
- 2. **Pattern findings:** recurring themes across marks (e.g., "the 'loot' prefix is crowded across all gaming platforms")
364
+ 2. **Pattern findings:** recurring themes across marks (e.g., "the 'prize' prefix is crowded across all gaming platforms")
364
365
  3. **Compound risk:** where the combination of marks might amplify risk (e.g., using both "PRIZE CRATE" and "CAVERN CRATES" increases the chance of a dispute with Prize Crate Retail)
365
366
 
366
367
  For each cross-reference: note which marks are affected, describe the connection, flag any compounding risk. Cross-references appear in the narrative summary and as a note in the Findings sheet.
@@ -28,7 +28,7 @@ Across the synthesis, two postures are load-bearing:
28
28
  - [Per-finding synthesis structure](#per-finding-synthesis-structure)
29
29
  - [Cancelled / invalid marks — include if exact, no commentary](#cancelled--invalid-marks--include-if-exact-no-commentary)
30
30
  - [The mark itself — standing assessment (every run)](#the-mark-itself--standing-assessment-every-run)
31
- - [Filed-vs-used check — mandatory for findings above the lowest band](#filed-vs-used-check--mandatory-for-findings-above-the-lowest-band)
31
+ - [Filed-vs-used check — mandatory for findings rated medium risk or higher](#filed-vs-used-check--mandatory-for-findings-rated-medium-risk-or-higher)
32
32
  - [Prosecution history — mandatory for identical-mark hits in relevant classes](#prosecution-history--mandatory-for-identical-mark-hits-in-relevant-classes)
33
33
  - [Revocability — mandatory assessment for register findings above the lowest band](#revocability--mandatory-assessment-for-register-findings-above-the-lowest-band)
34
34
  - [Verify-or-defer — procedure, deadlines, registry statistics](#verify-or-defer--procedure-deadlines-registry-statistics)
@@ -449,15 +449,15 @@ reviewing lawyer to assess), frames the report, and **never moves any band**.
449
449
  into a longer paragraph. Real PR / reputational **hits** still live in the PR / reputational section (see
450
450
  above); this block is the standing read, not the incident report.
451
451
 
452
- ## Filed-vs-used check — mandatory for findings above the lowest band
452
+ ## Filed-vs-used check — mandatory for findings rated medium risk or higher
453
453
 
454
- For every register finding advisory-rated ABOVE the framework's lowest band, synthesis runs an actual-use marketplace check via `perplexity_research` (see `phase2-execution.md` Step 3.5), scoped to owner + mark + the goods/field, and **records the result inline**. Any finding whose mitigant turns on the *absence* of use (non-use revocation/cancellation, "not in actual use", "no marketplace use found", "owner's use unknown") MUST carry a `- **Use-check source:** <URL | "perplexity_research — no result">` line — the driver's `narrative` validator rejects the narrative otherwise (spec 11). A use-negative is sayable **only** when a search produced it; an inference from the owner's profile ("a music act → probably no game use") is **not** a searched result and must be either run or labelled an inference, never dressed as a clean negative.
454
+ For every register finding advisory-rated on a rung the framework rates medium risk or higher, synthesis runs an actual-use marketplace check via `perplexity_research` (see `phase2-execution.md` Step 3.5, which also asks for the owner's enforcement record), scoped to owner + mark + the goods/field, and **records the result inline**. Any finding whose mitigant turns on the *absence* of use (non-use revocation/cancellation, "not in actual use", "no marketplace use found", "owner's use unknown") MUST carry a `- **Use-check source:** <URL | "perplexity_research — no result">` line — the driver's `narrative` validator rejects the narrative otherwise (spec 11). A use-negative is sayable **only** when a search produced it; an inference from the owner's profile ("a music act → probably no game use") is **not** a searched result and must be either run or labelled an inference, never dressed as a clean negative.
455
455
 
456
456
  ## Own-rights evidence — mandatory when reasoning relies on the client's house mark (spec-v3 A4)
457
457
 
458
458
  The same enforced contract applies to the client's OWN rights: if a finding's clearance reasoning relies on the client's house mark or franchise root ("the prefix is the client's own registered mark"), synthesis runs ONE own-portfolio check over the run's FROZEN register material — `band_lookup` with `owner:<the applicant/house-mark owner>` (plus `band_record` for any registration cited) — and the finding MUST end with a `- **Own-rights source:** <record URI(s) | "no applicant-owned registrations in the searched register material">` line — the driver's `narrative` validator rejects the narrative otherwise. **Know what the negative means:** the frozen material covers only this matter's dispatched slices (its classes, its mark texts), so an empty owner lookup is NEVER evidence the client's registrations do not exist — a house mark's registrations usually live in the client's home classes under mark text no slice covers. Never write "no registrations found" or any portfolio-wide register-negative: state the honest scoped negative, let the reasoning stand or fall without the crutch, and record the un-run owner query (owner + house root + its home classes/key jurisdictions) as an open Coverage/open-item row so the escalation lane can propose it through the supplemental mint — the same treatment as any instructed check the frozen material cannot answer. The affiliate-exclusion mandate governs *conflicts* (never flag the client against itself); it does **not** suppress this *supporting-evidence* check — those are different things.
459
459
 
460
- **The staff lawyer's rule:** Any use of a registered trademark that poses medium risk or higher (any band above the framework's lowest) is relevant. The actual-use check applies **after** the preliminary legal read is made — not before. The correct sequence is:
460
+ **The staff lawyer's rule:** Any use of a registered trademark that poses medium risk or higher (any band the framework rates medium risk or higher, whatever its position on the ladder) is relevant. The actual-use check applies **after** the preliminary legal read is made — not before. The correct sequence is:
461
461
 
462
462
  1. **Stage 1 — Legal claim strength, net of merits defences:** Make the legal read as the probability the claim prevails, stated in plain likelihood words — the whole-mark confusion read, **net of the credible merits defences** named in the per-finding structure (descriptiveness / weak senior mark, the opponent's own undermining conduct, the client's fair-use defence, priority). Whether **our actual use meets theirs in the market** (real commercial / manner-of-use / channel overlap) IS part of this Stage-1 read — it is the goods-half of likelihood-of-confusion and it sets the read: our use being a descriptive / in-product label vs their standalone source-brand, or the two sitting in a genuinely different commercial field, is a merits reason the claim may not prevail → the **client-favoured read**. **The basis for "theirs" splits by right type:** a **registered** senior meets us on the mark and G&S **as registered** (read the specification; quote it when it decides the point) — never on the owner's current presentation; a **common-law** senior meets us on its **actual trade**. OUR side of the meeting stays our own actual/intended manner of use (per the request form) — that lever is untouched. What stays in Stage 2 is the *senior's enforcement reality* — is the mark actually used, revocable for non-use, will the owner bother, how the owner currently dresses or positions it in the market — **NOT** whether the uses meet. (Merits defences are claim-strength, not practical mitigation; they belong in the legal read.)
463
463
  2. **Stage 2 — Practical risk mitigation:** For every hit whose legal read reaches "more likely than not they'd win" or worse, apply the practical mitigants — actual use, revocation-vulnerability, owner enforcement appetite, coexistence. These are the **business read**. **Response availability is Stage 2 at most:** that a route exists — coexistence, consent, rebrand, a cancellation lever — never enters the Stage-1 Legal position; mitigation-availability speaks only through the framework's own **Practical position** words for the band it belongs to.