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 +2 -2
- package/driver/CHANGELOG.md +9 -0
- package/driver/package.json +1 -1
- package/driver/suite-census.json +5 -5
- package/mcp-server/CHANGELOG.md +4 -0
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
- package/portal-ui/dist/assets/{index-Bki5jT_N.js → index-5UyqAyNM.js} +12 -6
- package/portal-ui/dist/assets/{index-De2RFLbT.css → index-CVOIvdhc.css} +20 -0
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/_shared/script-form.mjs +2 -2
- package/providers/clarivate/src/core.js +18 -27
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/signa/src/core.js +3 -3
- package/scripts/env-classify.mjs +6 -0
package/build-info.json
CHANGED
package/driver/CHANGELOG.md
CHANGED
|
@@ -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
|
package/driver/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "clearotron-driver",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.3.2-beta.
|
|
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": {
|
package/driver/suite-census.json
CHANGED
|
@@ -2872,8 +2872,8 @@
|
|
|
2872
2872
|
"todos": 0
|
|
2873
2873
|
},
|
|
2874
2874
|
"no-vendor-provenance-in-the-public-cut.test.mjs": {
|
|
2875
|
-
"tests":
|
|
2876
|
-
"asserts":
|
|
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":
|
|
5946
|
-
"asserts":
|
|
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":
|
|
6216
|
+
"asserts": 240,
|
|
6217
6217
|
"skips": 0,
|
|
6218
6218
|
"todos": 0
|
|
6219
6219
|
},
|
package/mcp-server/CHANGELOG.md
CHANGED
package/mcp-server/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trademark-artifacts-mcp",
|
|
3
|
-
"version": "0.3.2-beta.
|
|
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
|
@@ -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-
|
|
53
|
-
<link rel="stylesheet" crossorigin href="/portal/assets/index-
|
|
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>
|
package/portal-ui/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "portal-ui",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.3.2-beta.
|
|
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.
|
|
110
|
-
* but
|
|
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
|
|
224
|
-
//
|
|
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
|
-
*
|
|
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
|
|
243
|
-
* adjacency still reaches across. It is a superset of the literal phrase — safe in
|
|
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
|
|
247
|
-
*
|
|
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…".
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
//
|
|
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
|
|
300
|
-
//
|
|
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
|
|
521
|
-
*
|
|
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trademark-oauth-mcp-bridge",
|
|
3
|
-
"version": "0.3.2-beta.
|
|
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:
|
|
797
|
-
//
|
|
798
|
-
//
|
|
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
|
package/scripts/env-classify.mjs
CHANGED
|
@@ -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
|