clearotron 0.3.2-beta.3 → 0.3.2-beta.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/build-info.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "d4059c3a0c77911c1592fda411dc27948b4aaa95",
3
- "version": "0.3.2-beta.3"
2
+ "commit": "9bb3ab371e736a3b4a80f2d6dbdd71fd719b173e",
3
+ "version": "0.3.2-beta.4"
4
4
  }
@@ -1,5 +1,14 @@
1
1
  # clearotron-driver
2
2
 
3
+ ## 0.3.2-beta.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 3f4367e: Fixed: A multi-country search no longer refuses to start on a form that is already showing the territories it will search. When you have not chosen territories yourself, the form shows your company's own and the search uses those.
8
+
9
+ Fixed: A new clearance form now opens with one line saying what to do. It no longer shows two warning panels about work you have not started.
10
+ - 3f4367e: Fixed: On a phone, the list of clearances can now be scrolled sideways to read its columns. Before, the risk word was printed on top of the date and names broke in the middle of a word.
11
+
3
12
  ## 0.3.2-beta.3
4
13
 
5
14
  ### Patch Changes
@@ -2,7 +2,7 @@
2
2
  "name": "clearotron-driver",
3
3
  "private": true,
4
4
  "type": "module",
5
- "version": "0.3.2-beta.3",
5
+ "version": "0.3.2-beta.4",
6
6
  "license": "AGPL-3.0-only",
7
7
  "description": "Deterministic driver for the trademark clearance workflow: orchestration in code (fan-out, fan-in barrier, gating, retries); the model does judgment leaves only, through a reasoning CLI spawned per stage.",
8
8
  "engines": {
@@ -2872,8 +2872,8 @@
2872
2872
  "todos": 0
2873
2873
  },
2874
2874
  "no-vendor-provenance-in-the-public-cut.test.mjs": {
2875
- "tests": 4,
2876
- "asserts": 11,
2875
+ "tests": 8,
2876
+ "asserts": 23,
2877
2877
  "skips": 0,
2878
2878
  "todos": 0
2879
2879
  },
@@ -5942,8 +5942,8 @@
5942
5942
  "todos": 0
5943
5943
  },
5944
5944
  "composerProduct.test.ts": {
5945
- "tests": 30,
5946
- "asserts": 113,
5945
+ "tests": 32,
5946
+ "asserts": 124,
5947
5947
  "skips": 0,
5948
5948
  "todos": 0
5949
5949
  },
@@ -6213,7 +6213,7 @@
6213
6213
  },
6214
6214
  "screenCopy.test.ts": {
6215
6215
  "tests": 63,
6216
- "asserts": 239,
6216
+ "asserts": 240,
6217
6217
  "skips": 0,
6218
6218
  "todos": 0
6219
6219
  },
@@ -1,5 +1,9 @@
1
1
  # trademark-artifacts-mcp
2
2
 
3
+ ## 0.3.2-beta.4
4
+
5
+ No changes in this release.
6
+
3
7
  ## 0.3.2-beta.3
4
8
 
5
9
  No changes in this release.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trademark-artifacts-mcp",
3
- "version": "0.3.2-beta.3",
3
+ "version": "0.3.2-beta.4",
4
4
  "license": "AGPL-3.0-only",
5
5
  "private": true,
6
6
  "description": "MCP server to interrogate clearotron trademark-clearance runs — list/read artifacts, trace the full decision flow, telemetry/cost, coverage, single-run search, and a gated single-step what-if. Imports the clearotron-driver read-only; touches no driver/template/deploy files.",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clearotron",
3
3
  "type": "module",
4
- "version": "0.3.2-beta.3",
4
+ "version": "0.3.2-beta.4",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
7
7
  "type": "git",
@@ -14063,7 +14063,7 @@ function missingPieces(names, classes, goods) {
14063
14063
  if (!classes.length && !goods.trim()) out.push("Say what the name is used for — pick classes, or describe the goods and services. Either one is enough.");
14064
14064
  return out;
14065
14065
  }
14066
- function blockers(d, product, names = 0) {
14066
+ function blockers(d, product, names = 0, inheritedTerritories = []) {
14067
14067
  const out = [];
14068
14068
  if (!product) {
14069
14069
  out.push("Pick a search above. The four differ in where they look and how deep they read.");
@@ -14071,7 +14071,7 @@ function blockers(d, product, names = 0) {
14071
14071
  }
14072
14072
  const budget = nameBudget(product, names);
14073
14073
  if (budget) out.push(budget.allowed === 1 ? `A ${product.name} reads one name at a time, and you have ${budget.allowed + budget.over}.` : `A ${product.name} reads ${budget.allowed} names at a time, and you have ${budget.allowed + budget.over}.`);
14074
- const named = d.territories;
14074
+ const named = product.geography !== "worldwide, and nothing else" && !d.territories.length ? inheritedTerritories : d.territories;
14075
14075
  const countries = named.filter((t) => tierOf(t) === "country");
14076
14076
  const regions = named.filter((t) => tierOf(t) === "region");
14077
14077
  switch (product.geography) {
@@ -14091,6 +14091,10 @@ function blockers(d, product, names = 0) {
14091
14091
  }
14092
14092
  function readiness(i) {
14093
14093
  const NOTHING_PICKED = "Pick one of the searches above to begin.";
14094
+ if (i.untouched) return {
14095
+ ready: false,
14096
+ blockedBy: "Add a name and pick a search."
14097
+ };
14094
14098
  const blocking = [
14095
14099
  [i.nameStops.length > 0, i.nameStops[0] ?? ""],
14096
14100
  [i.gaps.length > 0, i.gaps[0] ?? ""],
@@ -18607,20 +18611,22 @@ function NewClearance({ ctx }) {
18607
18611
  kind: searches.kind,
18608
18612
  onRetry: retrySearches
18609
18613
  });
18610
- const stops = blockers(draft.pick, activeLevel, names.length);
18614
+ const stops = blockers(draft.pick, activeLevel, names.length, own.territories);
18611
18615
  const overlong = searches.value.maxMarkName == null ? [] : names.filter((n) => n.length > (searches.value.maxMarkName ?? Infinity));
18612
18616
  const markLimit = searches.value.maxMarkName;
18613
18617
  const nameStops = overlong.map((n) => `“${n.slice(0, 30)}…” is ${n.length} characters. A mark name may be at most ${markLimit} — it becomes this run's name and part of every report link, so it cannot carry a description. Put the goods and the description in their own fields below.`);
18614
18618
  const budget = nameBudget(activeLevel, names.length);
18615
18619
  const exhausted = searchesLeft(usage) === 0;
18616
18620
  const gaps = missingPieces(names, classes, draft.goods);
18621
+ const untouched = JSON.stringify(draft) === JSON.stringify(EMPTY);
18617
18622
  const { ready, blockedBy } = readiness({
18618
18623
  gaps,
18619
18624
  stops,
18620
18625
  nameStops,
18621
18626
  budget,
18622
18627
  exhausted,
18623
- hasProduct: activeLevel != null
18628
+ hasProduct: activeLevel != null,
18629
+ untouched
18624
18630
  });
18625
18631
  const startedFrom = draft.savedSearch ? savedRow ? displayLabel(savedRow) : "a search template" : activeLevel?.name ?? "no search picked yet";
18626
18632
  const nativeOn = activeLevel?.nativeLanguage === "automatic" || nativeControl === "toggle" && (draft.savedSearch ? savedRow?.nativeLanguage === true : draft.pick.nativeLanguage);
@@ -19286,7 +19292,7 @@ function NewClearance({ ctx }) {
19286
19292
  })
19287
19293
  })]
19288
19294
  }),
19289
- gaps.length ? (0, import_jsx_runtime.jsxs)("div", {
19295
+ !untouched && gaps.length ? (0, import_jsx_runtime.jsxs)("div", {
19290
19296
  className: "notice",
19291
19297
  style: {
19292
19298
  borderColor: "var(--tone-medium)",
@@ -19304,7 +19310,7 @@ function NewClearance({ ctx }) {
19304
19310
  }, `g${i}`))
19305
19311
  })]
19306
19312
  }) : null,
19307
- stops.length || nameStops.length ? (0, import_jsx_runtime.jsxs)("div", {
19313
+ !untouched && (stops.length || nameStops.length) ? (0, import_jsx_runtime.jsxs)("div", {
19308
19314
  className: "notice",
19309
19315
  style: {
19310
19316
  borderColor: "var(--tone-medium)",
@@ -565,6 +565,26 @@ table.data.fixed td {
565
565
  overflow-wrap: anywhere;
566
566
  }
567
567
 
568
+ /* BELOW PHONE WIDTH A FIXED TABLE HAS NOTHING LEFT TO DIVIDE, so it stops dividing and scrolls instead.
569
+ Fixed layout shares the container out as percentages, which is what lets this table fit any desktop
570
+ width without a scrollbar. On a phone the shares become slivers: measured at 400px, the Risk word
571
+ printed through the Updated date, ordinary marks broke mid-word, and the count pills rendered as
572
+ circles with their text outside them. The page itself then scrolled sideways, because the row's own
573
+ controls are wider than the column they were given.
574
+ Under this breakpoint the table becomes an ordinary content-sized one — which is what `table.data`
575
+ already is, floor included, so no width is invented here — and its wrapper, which has always had
576
+ `overflow-x: auto`, does the scrolling. A table a reader drags sideways is readable; one printed
577
+ through itself is not.
578
+ THE FLOOR IS RESTORED ONLY HERE, and that is the whole point. `table.data.fixed` sets `min-width: 0`
579
+ above, because a floor on a nearly-wide-enough container is what puts a 19px scrollbar on a
580
+ client-facing page for no reason. That argument is about desktop widths and it still holds there. */
581
+ @media (max-width: 560px) {
582
+ table.data.fixed {
583
+ table-layout: auto;
584
+ min-width: 860px;
585
+ }
586
+ }
587
+
568
588
  /* An expanded read: quiet enough to read as subordinate to the row it belongs to. The indent is on the
569
589
  cells, not here, because it is structural — see ReadRow. */
570
590
  table.data tr.read-row > td {
@@ -49,8 +49,8 @@
49
49
  -->
50
50
  <link rel="preconnect" href="https://api.fontshare.com" crossorigin />
51
51
  <link href="https://api.fontshare.com/v2/css?f[]=satoshi@400,500,700,900&display=swap" rel="stylesheet" />
52
- <script type="module" crossorigin src="/portal/assets/index-Bki5jT_N.js"></script>
53
- <link rel="stylesheet" crossorigin href="/portal/assets/index-De2RFLbT.css">
52
+ <script type="module" crossorigin src="/portal/assets/index-5UyqAyNM.js"></script>
53
+ <link rel="stylesheet" crossorigin href="/portal/assets/index-CVOIvdhc.css">
54
54
  </head>
55
55
  <body>
56
56
  <div id="root"></div>
@@ -2,7 +2,7 @@
2
2
  "name": "portal-ui",
3
3
  "private": true,
4
4
  "type": "module",
5
- "version": "0.3.2-beta.3",
5
+ "version": "0.3.2-beta.4",
6
6
  "license": "AGPL-3.0-only",
7
7
  "description": "The unified trademark portal UI. One address, one login: who you are decides what you see. Built as a static bundle, served by driver/portal-service.mjs — the browser never reaches profile-service or recipe-service.",
8
8
  "engines": {
@@ -106,8 +106,8 @@ export function romanizationRefusal(value) {
106
106
  }
107
107
 
108
108
  /** The two spellings a register may hold for one romanisation — syllable-separated as the record
109
- * writes it, and run together as a searcher would type it. Probed: identical counts on CN/TW/GR/KR/TH,
110
- * but EG differs (7 vs 10), so BOTH are emitted and OR-joined rather than picking one. */
109
+ * writes it, and run together as a searcher would type it. On most scripts a register holds the two
110
+ * spellings alike, but not on all of them — so BOTH are emitted and OR-joined rather than picking one. */
111
111
  export function romanizationSpellings(value) {
112
112
  const spaced = String(value ?? "").normalize("NFC").trim().replace(/\s+/g, " ");
113
113
  if (!spaced) return [];
@@ -220,8 +220,8 @@ export function assertSingleMarkShape(rawTerm) {
220
220
  }
221
221
  return tokens;
222
222
  }
223
- // Read as OPERATORS wherever they stand alone in a value string, case-insensitively ("monster adj
224
- // energy" = 67), with an optional proximity digit (ADJ2 = 72).
223
+ // Read as OPERATORS wherever they stand alone in a value string, case-insensitively, with an
224
+ // optional proximity digit.
225
225
  const RESERVED_TOKEN_RE = /^(?:AND|OR|NOT|ADJ|NEAR)\d*$/i;
226
226
  const PUNCT_ONLY_RE = /^[^\p{L}\p{N}]+$/u;
227
227
 
@@ -229,22 +229,18 @@ const PUNCT_ONLY_RE = /^[^\p{L}\p{N}]+$/u;
229
229
  * An operator word inside a phrase is DROPPED, and the gap it leaves is spanned by widening that one
230
230
  * adjacency to `ADJ<n+1>`.
231
231
  *
232
- * The obvious move — make the word lexically stop being an operator with an interior `?` — is WRONG,
233
- * and measurably so. Registers write these marks with an ampersand, not the word:
234
- *
235
- * *BLACK ADJ A?D ADJ DECKER* 0 ← what the `?` escape asks for, and nothing is named that
236
- * *BLACK ADJ DECKER* 55 ← BLACK & DECKER
237
- * *BLACK ADJ2 DECKER* 55 ← identical: the connector is not indexed, so spanning costs nothing
238
- * *BEN ADJ A?D ADJ JERRYS* 0 vs *BEN ADJ JERRYS* 25
232
+ * The obvious move — make the word lexically stop being an operator with an interior `?` — is WRONG.
233
+ * Registers write these marks with an ampersand and not the word, so the escaped form names NOTHING,
234
+ * while the plain adjacency finds the mark and the widened adjacency finds exactly the same set.
239
235
  *
240
236
  * A zero over a mark that exists is the failure this whole contract is built to prevent, so the escape
241
237
  * is gone. Dropping + `ADJ2` is right whether or not the index holds the word: if it is a stopword the
242
- * neighbours are already adjacent (55 == 55 above), and if some register does index it the widened
243
- * adjacency still reaches across. It is a superset of the literal phrase — safe in the direction a
244
- * clearance sweep needs, which fails by MISSING a mark and never by surfacing an extra one.
238
+ * neighbours are already adjacent — plain and widened return the same set — and if some register does
239
+ * index it the widened adjacency still reaches across. It is a superset of the literal phrase — safe in
240
+ * the direction a clearance sweep needs, which fails by MISSING a mark and never by surfacing an extra one.
245
241
  *
246
- * `ADJ<n>` is real and probed: MONSTER ADJ ENERGY = 67, MONSTER ADJ2 ENERGY = 72, SALT ADJ PEPPER = 27
247
- * vs SALT ADJ2 PEPPER = 52 — so the widening is genuinely a widening, not a no-op we cannot see.
242
+ * `ADJ<n>` is real: the widened form returns strictly more than the tighter one, so the widening is
243
+ * genuinely a widening and not a no-op we cannot see.
248
244
  */
249
245
  export function isReservedToken(tok) {
250
246
  return RESERVED_TOKEN_RE.test(tok);
@@ -286,21 +282,16 @@ export function compilePhraseValue(term, { pre = "", post = "", dropReserved = f
286
282
 
287
283
  // A LEADING `*` on a ONE-CHARACTER first token is a sub-query over most of the register, and inside an
288
284
  // ADJ chain the provider gives up on it: HTTP 500 "Near/Adj queries with sub queries that can return a
289
- // huge amount…". Probed — one character breaks, two do not:
290
- // *I ADJ CANT ADJ BELIEVE* 500 *IT ADJ STARTS ADJ WITH* 103
291
- // *A ADJ BAR* 500 *AN ADJ APPLE* 55
292
- // So the leading wrap is dropped for that one case, and ONLY that case. It costs a little recall at
293
- // the leading token boundary (AN ADJ APPLE* = 31 vs *AN ADJ APPLE* = 55), which is why it is not done
294
- // generally — but the alternative here is not a narrower search, it is no search at all.
285
+ // huge amount…". The refusal is specific to a first token of ONE character; two characters are answered.
286
+ // So the leading wrap is dropped for that one case, and ONLY that case. It costs a little recall at the
287
+ // leading token boundary, which is why it is not done generally — but the alternative here is not a
288
+ // narrower search, it is no search at all.
295
289
  const leadWrap = (kept.length > 1 && kept[0].length === 1) ? "" : pre;
296
290
  // AND THE SAME AT THE OTHER END, which was never added and cost a family search on a live matter. A
297
291
  // root whose LAST word is one character — a sequel number, an article, an initial — compiled to
298
292
  // `*PLAN ADJ B*`, and the trailing `B*` is the same sub-query over most of the register that the
299
- // leading rule above exists for. Probed on the test install, count calls only:
300
- // *PLAN ADJ B* 500 *PLAN ADJ B 200, 36 records
301
- // *LEVEL ADJ 2* 500 *LEVEL ADJ 2 200, 14 records
302
- // *ROB ADJ A* 500 *ROB ADJ A 200, 3 records
303
- // A delivered run planned a family search on such a root, every attempt was refused, the refusal was
293
+ // leading rule above exists for: the provider refuses that shape and answers the same query with the
294
+ // trailing star removed. A delivered run planned a family search on such a root, every attempt was refused, the refusal was
304
295
  // retried as if transient and the slice was filed as a provider gap — so a family of pending filings
305
296
  // for a third-party title went unseen and the matter was rated on what was left.
306
297
  const last = kept.length - 1;
@@ -517,8 +508,8 @@ export function hasAnyElement(p) {
517
508
 
518
509
  /**
519
510
  * Join terms into ONE value string with the EXPLICIT " OR " operator (a bare space is an implicit AND,
520
- * not an OR). ADJ binds tighter than OR, so phrase operands need no parentheses — probed:
521
- * `*MOUNTAIN ADJ DEW* OR *MONSTER ADJ ENERGY*` = 157 = 90 + 67, identical with parens.
511
+ * not an OR). ADJ binds tighter than OR, so phrase operands need no parentheses: an OR of two phrases
512
+ * returns the union of the two, and parentheses around them change nothing.
522
513
  * The width bound is the parser's document-nesting cap; the enumerate
523
514
  * kernel chunks wide `names` stacks at capabilities.kernel.namesChunkDefault (= maxOrWidth) before they
524
515
  * ever reach here, so hitting this throw means a caller bypassed the kernel.
@@ -1,5 +1,9 @@
1
1
  # trademark-oauth-mcp-bridge
2
2
 
3
+ ## 0.3.2-beta.4
4
+
5
+ No changes in this release.
6
+
3
7
  ## 0.3.2-beta.3
4
8
 
5
9
  No changes in this release.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trademark-oauth-mcp-bridge",
3
- "version": "0.3.2-beta.3",
3
+ "version": "0.3.2-beta.4",
4
4
  "license": "AGPL-3.0-only",
5
5
  "private": true,
6
6
  "description": "OAuth 2.1 MCP stdio bridge used by the engine's case-law gather stage (courtlistener / legaldatahunter).",
@@ -793,9 +793,9 @@ export async function doCountHits(apiKey, base, params, tctx, { mock = false } =
793
793
  const parsed = JSON.parse(r.text);
794
794
  // ── this is a COUNT now, and it was a presence probe ──────────────────────────────────
795
795
  // `include_total` returns the corpus total on the same call, so the smallest request the API will
796
- // take — limit 1 — carries the whole answer: probed, `limit:1` returned total 685, identical to the
797
- // paged query's. Stage 0.5 (driver/register-count.mjs) refused to run on this provider at all; it
798
- // now gets the number it exists to ask for.
796
+ // take — limit 1 — carries the whole answer: the total it reports is the one the paged query reports.
797
+ // Stage 0.5 (driver/register-count.mjs) refused to run on this provider at all; it now gets the
798
+ // number it exists to ask for.
799
799
  //
800
800
  // THE ZERO RULE IS UNCHANGED AND IS THE REASON THIS IS SAFE. `total_hits` is null unless the vendor
801
801
  // counted exactly — an approximation and a non-answer both report unknown. So the only 0 this can
@@ -341,6 +341,12 @@ function topLevelKeys(text, code, open) {
341
341
  // — the committed artifact is byte-identical across it.
342
342
  export const DEPLOYMENT_NAMES = new Set([
343
343
  "CF_ACCESS_TEAM",
344
+ // The administrator contact for this install — the address Preferences points a signed-in person at to
345
+ // have their access, permissions or companies changed. An identity and an address, which the rule above
346
+ // DEPLOY_RE calls never a knob, and its catalogue row declares `deployment`. No shape here matches the
347
+ // spelling, so unlisted it falls through to `tuning`, the bucket step 3 deletes from — where nothing
348
+ // records a set-site for it and nothing would object to its removal.
349
+ "CLEAROTRON_ADMINISTRATOR_CONTACT",
344
350
  // — `CLEAROTRON_DEMO`, which is `PORTAL_DEMO` renamed. It is listed here for
345
351
  // the exact reason this list exists: the old name was `deployment` because of its `PORTAL_` prefix and
346
352
  // by nothing else, so moving it to the house prefix dropped it through to `tuning` — the bucket step 3