clearotron 0.3.2-beta.6 → 0.3.2-beta.7
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/bin/update.mjs +24 -0
- package/build-info.json +2 -2
- package/driver/CHANGELOG.md +7 -0
- package/driver/connotation-search.mjs +45 -0
- package/driver/contract-e3-backlog.mjs +4 -4
- package/driver/contract-vocabulary.mjs +17 -17
- package/driver/gateway.mjs +6 -3
- package/driver/package.json +1 -1
- package/driver/pipeline.mjs +191 -3
- package/driver/publish/templates/report.css +10 -1
- package/driver/result-noun-fields.mjs +5 -2
- package/driver/suite-census.json +22 -10
- package/driver/verify.mjs +23 -5
- package/mcp-server/CHANGELOG.md +4 -0
- package/mcp-server/http-server.mjs +4 -0
- package/mcp-server/lib/audit.mjs +11 -1
- package/mcp-server/lib/http-handler.mjs +6 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +1 -1
- package/package.json +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/clarivate/src/capabilities.js +5 -5
- package/providers/clarivate/src/core.js +1 -1
- package/providers/corsearch/src/core.js +2 -2
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/perplexity/src/core.js +3 -3
- package/providers/signa/src/capabilities.js +5 -6
- package/providers/signa/src/core.js +1 -1
- package/scripts/release-duplicate-notes.mjs +246 -0
- package/scripts/release-publish-guard.mjs +64 -6
- package/scripts/report-print-check.mjs +194 -0
- package/shared/driver-dir.mjs +1 -1
package/driver/suite-census.json
CHANGED
|
@@ -651,6 +651,12 @@
|
|
|
651
651
|
"skips": 0,
|
|
652
652
|
"todos": 0
|
|
653
653
|
},
|
|
654
|
+
"a-refusal-names-the-collection-it-searched.test.mjs": {
|
|
655
|
+
"tests": 5,
|
|
656
|
+
"asserts": 13,
|
|
657
|
+
"skips": 0,
|
|
658
|
+
"todos": 0
|
|
659
|
+
},
|
|
654
660
|
"a-refusal-names-the-file-when-the-command-it-offers-cannot-be-run.test.mjs": {
|
|
655
661
|
"tests": 8,
|
|
656
662
|
"asserts": 16,
|
|
@@ -677,7 +683,7 @@
|
|
|
677
683
|
},
|
|
678
684
|
"a-refused-query-says-whether-the-provider-declined-it.test.mjs": {
|
|
679
685
|
"tests": 3,
|
|
680
|
-
"asserts":
|
|
686
|
+
"asserts": 12,
|
|
681
687
|
"skips": 0,
|
|
682
688
|
"todos": 0
|
|
683
689
|
},
|
|
@@ -699,6 +705,12 @@
|
|
|
699
705
|
"skips": 0,
|
|
700
706
|
"todos": 0
|
|
701
707
|
},
|
|
708
|
+
"a-release-note-cannot-be-counted-twice.test.mjs": {
|
|
709
|
+
"tests": 12,
|
|
710
|
+
"asserts": 33,
|
|
711
|
+
"skips": 0,
|
|
712
|
+
"todos": 0
|
|
713
|
+
},
|
|
702
714
|
"a-release-note-is-written-for-its-reader.test.mjs": {
|
|
703
715
|
"tests": 9,
|
|
704
716
|
"asserts": 66,
|
|
@@ -3070,8 +3082,8 @@
|
|
|
3070
3082
|
"todos": 0
|
|
3071
3083
|
},
|
|
3072
3084
|
"pipeline.mock.test.mjs": {
|
|
3073
|
-
"tests":
|
|
3074
|
-
"asserts":
|
|
3085
|
+
"tests": 97,
|
|
3086
|
+
"asserts": 806,
|
|
3075
3087
|
"skips": 0,
|
|
3076
3088
|
"todos": 0
|
|
3077
3089
|
},
|
|
@@ -3706,8 +3718,8 @@
|
|
|
3706
3718
|
"todos": 0
|
|
3707
3719
|
},
|
|
3708
3720
|
"release-pipeline.test.mjs": {
|
|
3709
|
-
"tests":
|
|
3710
|
-
"asserts":
|
|
3721
|
+
"tests": 101,
|
|
3722
|
+
"asserts": 384,
|
|
3711
3723
|
"skips": 0,
|
|
3712
3724
|
"todos": 0
|
|
3713
3725
|
},
|
|
@@ -5386,8 +5398,8 @@
|
|
|
5386
5398
|
"todos": 0
|
|
5387
5399
|
},
|
|
5388
5400
|
"update-refuses-on-a-packaged-install.test.mjs": {
|
|
5389
|
-
"tests":
|
|
5390
|
-
"asserts":
|
|
5401
|
+
"tests": 8,
|
|
5402
|
+
"asserts": 26,
|
|
5391
5403
|
"skips": 0,
|
|
5392
5404
|
"todos": 0
|
|
5393
5405
|
},
|
|
@@ -5733,8 +5745,8 @@
|
|
|
5733
5745
|
"todos": 0
|
|
5734
5746
|
},
|
|
5735
5747
|
"http-handler.test.mjs": {
|
|
5736
|
-
"tests":
|
|
5737
|
-
"asserts":
|
|
5748
|
+
"tests": 20,
|
|
5749
|
+
"asserts": 47,
|
|
5738
5750
|
"skips": 0,
|
|
5739
5751
|
"todos": 0
|
|
5740
5752
|
},
|
|
@@ -5872,7 +5884,7 @@
|
|
|
5872
5884
|
},
|
|
5873
5885
|
"the-portal-can-tell-who-has-connected-an-assistant.test.mjs": {
|
|
5874
5886
|
"tests": 12,
|
|
5875
|
-
"asserts":
|
|
5887
|
+
"asserts": 35,
|
|
5876
5888
|
"skips": 0,
|
|
5877
5889
|
"todos": 0
|
|
5878
5890
|
},
|
package/driver/verify.mjs
CHANGED
|
@@ -13,7 +13,8 @@ import { driverDir } from "../shared/driver-dir.mjs"; //
|
|
|
13
13
|
import { findReceiptViolations, findGridLedgerViolations, findPlatformIdentityViolations, parsePrRiskQueries, MEANING_SEAT, erroredConnotationQueriesAmong } from "./common-law-receipts.mjs";
|
|
14
14
|
// Conversion 2 — the discriminator the two rulings above key on. PURE-ish: one existsSync-shaped read.
|
|
15
15
|
import { matterFrameWasRecorded, frameRatifiedForms } from "./matter-frame-record.mjs";
|
|
16
|
-
import { findConnotationViolations, parsePrRiskResults,
|
|
16
|
+
import { findConnotationViolations, parsePrRiskResults, prRiskPopulation,
|
|
17
|
+
CONNOTATION_UNMATCHED_MARK, CONNOTATION_NO_RESEMBLANCE_MARK, MEANING_ANGLES_RE,
|
|
17
18
|
parseDispositionForm, CONNOTATION_UNRULED_REASONS, queryKey } from "./connotation-search.mjs";
|
|
18
19
|
import { formSidecarName, formSidecarPath } from "./disposition-union.mjs";
|
|
19
20
|
// B — the transport's own four failure states. The audit reads the run's records; this file locates them.
|
|
@@ -365,6 +366,13 @@ function commonLawMeaningSeat(p, c) {
|
|
|
365
366
|
let recordedRaw;
|
|
366
367
|
try { recordedRaw = parsePrRiskResults(ledgerRaw).map((e) => String(e?.query ?? "")); }
|
|
367
368
|
catch (e) { return fail(`grid_ledger_unparseable:${String(e.message).slice(0, 80)}`); }
|
|
369
|
+
// THE REFUSAL NAMES THE FILE IT JOINED AGAINST, because "recorded" is not one question in a run.
|
|
370
|
+
// A run answers "what did this half record" in four places that each mean something different — this
|
|
371
|
+
// results ledger, its gap rows, the obligations sidecar, and the final-state receipts audit — and a
|
|
372
|
+
// sentence that says only "recorded" invites a reader to answer from whichever they happen to open.
|
|
373
|
+
// Two readers did exactly that on one clearance and reached three different wrong mechanisms, each
|
|
374
|
+
// from a true measurement of a real record.
|
|
375
|
+
const LEDGER = `common-law-grid.half-${MEANING_SEAT}.json`;
|
|
368
376
|
const recordedQ = new Set(recordedRaw.map(queryKey));
|
|
369
377
|
const dropped = dictated.filter((q) => !recordedQ.has(queryKey(q)));
|
|
370
378
|
if (dropped.length) {
|
|
@@ -435,17 +443,27 @@ function commonLawMeaningSeat(p, c) {
|
|
|
435
443
|
.flatMap((b) => (Array.isArray(b?.gaps) ? b.gaps : []));
|
|
436
444
|
const reportedError = new Map(
|
|
437
445
|
erroredConnotationQueriesAmong(dropped, { gaps: gapRows }).map((e) => [e.query, e.error]));
|
|
446
|
+
// AND IT SAYS WHEN THE READER REDUCED WHAT IT READ. `parsePrRiskResults` folds rows onto the raw
|
|
447
|
+
// query text, so its output is smaller than the ledger whenever a query was recorded twice. Every
|
|
448
|
+
// count taken during one evening's diagnosis was post-fold and nobody had named the raw population,
|
|
449
|
+
// which made "the seat wrote 59 rows" and "59 survived the fold" the same number and different
|
|
450
|
+
// facts: one query recorded twice while another was skipped reads exactly like one simply skipped.
|
|
451
|
+
const pop = prRiskPopulation(ledgerRaw);
|
|
452
|
+
const foldNote = pop.repeated > 0
|
|
453
|
+
? ` (${LEDGER} carries ${pop.rows} row(s) that fold to ${pop.distinct} distinct query(ies): `
|
|
454
|
+
+ `${pop.repeated} repeat a query already counted, so a repeat here may stand where a dictated query is missing)`
|
|
455
|
+
: "";
|
|
438
456
|
const parts = dropped.slice(0, 3).map((q) => {
|
|
439
457
|
const reported = reportedError.get(q);
|
|
440
458
|
// Named separately because the remedy is different: the search was attempted and the provider
|
|
441
459
|
// declined it, so re-running it unchanged is the one repair that cannot work.
|
|
442
|
-
if (reported) return `${abbrev(q, 40)} [the provider REPORTED an error on
|
|
460
|
+
if (reported) return `${abbrev(q, 40)} [not recorded in ${LEDGER} because the provider REPORTED an error on it: ${abbrev(reported, 60)}]`;
|
|
443
461
|
const n = nearest(q);
|
|
444
462
|
return n
|
|
445
|
-
? `${abbrev(q, 40)}
|
|
446
|
-
: `${abbrev(q, 40)}
|
|
463
|
+
? `${abbrev(q, 40)} ${CONNOTATION_UNMATCHED_MARK} ${abbrev(n, 40)}] in ${LEDGER} — that is evidence a query LIKE it was recorded there, not that these two are the same query`
|
|
464
|
+
: `${abbrev(q, 40)} ${CONNOTATION_NO_RESEMBLANCE_MARK} in ${LEDGER}, which is the only file this gate joins against`;
|
|
447
465
|
});
|
|
448
|
-
return fail(`connotation_query_unrecorded:${parts.join(",")}${dropped.length > 3 ? ` (+${dropped.length - 3} more)` : ""}`);
|
|
466
|
+
return fail(`connotation_query_unrecorded:${parts.join(",")}${dropped.length > 3 ? ` (+${dropped.length - 3} more)` : ""}${foldNote}`);
|
|
449
467
|
}
|
|
450
468
|
if (spec?.connotation?.disposition_required === true) {
|
|
451
469
|
const recorded = parsePrRiskResults(ledgerRaw);
|
package/mcp-server/CHANGELOG.md
CHANGED
|
@@ -362,6 +362,10 @@ if (isMain) {
|
|
|
362
362
|
// read inside it, so the factory keeps one rule and the caller names which door it is.
|
|
363
363
|
createSession: (sessions, scope, owner) => createSession(sessions, scope, owner, { networkDoor: false }),
|
|
364
364
|
authHeader: AUTH_HEADER, firmDomains: ALLOWED_DOMAINS, log,
|
|
365
|
+
// THE THIRD DOOR, and it has to name itself. It is not the staff surface and it is not the network
|
|
366
|
+
// client surface; a key presented here arrives over a local socket. Until this was passed, every
|
|
367
|
+
// audit line this door wrote carried no door at all and read as the staff surface by elimination.
|
|
368
|
+
door: "key",
|
|
365
369
|
});
|
|
366
370
|
openKeyDoor({ handler: keyHandler, path: KEY_SOCKET, log })
|
|
367
371
|
.catch((e) => { log(`FATAL: could not open the key socket at ${KEY_SOCKET} — ${e.message}`); process.exit(1); });
|
package/mcp-server/lib/audit.mjs
CHANGED
|
@@ -40,6 +40,16 @@ export function summarize(body) {
|
|
|
40
40
|
return out;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The value written when a caller names no door. A door-less line USED TO BE POSSIBLE and one writer
|
|
45
|
+
* produced them: the key door built its handler without saying which surface it was, so every line it
|
|
46
|
+
* wrote omitted the field while the lines either side of it carried it. Absence then read as the staff
|
|
47
|
+
* surface, because that was the only other thing it could have been, so the trail quietly attributed a
|
|
48
|
+
* client's calls to staff. The field is now always written and an unnamed door is loud rather than
|
|
49
|
+
* missing — a reader can search for this value, which is not true of a key that is not there.
|
|
50
|
+
*/
|
|
51
|
+
export const UNNAMED_DOOR = "unnamed";
|
|
52
|
+
|
|
43
53
|
export function appendAudit({ email, sub, body, status, transport, door, path = DEFAULT_AUDIT_PATH }) {
|
|
44
54
|
// `sub` = the inner-token PRINCIPAL (ops-token issuance, INSTALL.md §8) — distinguishes
|
|
45
55
|
// two automations sharing a transport identity. null for internal/user sessions without a sub claim.
|
|
@@ -59,7 +69,7 @@ export function appendAudit({ email, sub, body, status, transport, door, path =
|
|
|
59
69
|
// WRITTEN ONLY WHEN GIVEN, the same rule as `transport` and for the same reason: the existing log
|
|
60
70
|
// shape must not move for records that have no answer to this. A door that does not name itself is a
|
|
61
71
|
// record with no `door` key, not a record claiming to be from nowhere.
|
|
62
|
-
const line = JSON.stringify({ ts: new Date().toISOString(), email: email ?? null, sub: sub ?? null, ...summarize(body), status: status ?? null, ...(transport ? { transport } : {}),
|
|
72
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), email: email ?? null, sub: sub ?? null, ...summarize(body), status: status ?? null, ...(transport ? { transport } : {}), door: door || UNNAMED_DOOR }) + "\n";
|
|
63
73
|
try { mkdirSync(dirname(path), { recursive: true }); appendFileSync(path, line); } catch { /* best-effort */ }
|
|
64
74
|
}
|
|
65
75
|
|
|
@@ -69,7 +69,7 @@ export function evictOldest(sessions) {
|
|
|
69
69
|
* presents another identity's mcp-session-id is refused (403) — a leaked/guessed session id must never
|
|
70
70
|
* let one CF-authed person attach to another's session (which may carry an ops-scoped inner token).
|
|
71
71
|
*/
|
|
72
|
-
export function makeHttpHandler({ verify, limiter, opsLimiter = null, sessions, createSession, ns = "trademark-artifacts", sessionMax = 500, maxBody = 4 * 1024 * 1024, authHeader = "cf-access-jwt-assertion", firmDomains = [], clientSurface = false, devMode = false, tokenOnly = false, keyDoorPath = null,
|
|
72
|
+
export function makeHttpHandler({ verify, limiter, opsLimiter = null, sessions, createSession, ns = "trademark-artifacts", sessionMax = 500, maxBody = 4 * 1024 * 1024, authHeader = "cf-access-jwt-assertion", firmDomains = [], clientSurface = false, door: doorName = null, devMode = false, tokenOnly = false, keyDoorPath = null,
|
|
73
73
|
// Is this identity still on the guest list? ASKED PER REQUEST on an ACCOUNT session, cached on the
|
|
74
74
|
// grants file's mtime, so it costs a stat between edits. Injected so an arm can move the answer
|
|
75
75
|
// without a file; the default is the real read, because a door composed without this seam would
|
|
@@ -96,7 +96,11 @@ export function makeHttpHandler({ verify, limiter, opsLimiter = null, sessions,
|
|
|
96
96
|
// caller who does not exist. Those lines carry no email and no principal, and say a call was refused
|
|
97
97
|
// at this door at this time — which is true, and is the shape the local route already uses for the
|
|
98
98
|
// same reason: a synthesized identity would match somebody who did nothing.
|
|
99
|
-
|
|
99
|
+
// WHICH SURFACE THIS HANDLER IS, named rather than inferred. `clientSurface` answers a scope question
|
|
100
|
+
// and was doing double duty as the door's name, which left the key door — built with neither
|
|
101
|
+
// `clientSurface` nor a name — writing lines with no door at all. A caller that knows it is a third
|
|
102
|
+
// thing passes `door`; the two that do not get the surface they already declare.
|
|
103
|
+
const door = doorName ?? (clientSurface ? "client" : "portal");
|
|
100
104
|
const refuse = (res, status, obj, who = {}) => {
|
|
101
105
|
try { appendAudit({ email: who.email ?? null, sub: who.sub ?? null, body: who.body ?? null, status: "refused", door }); }
|
|
102
106
|
catch { /* best-effort: a write failure must never change what the caller is told */ }
|
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.7",
|
|
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/mcp-server/server.mjs
CHANGED
|
@@ -1096,7 +1096,7 @@ if (isMain) {
|
|
|
1096
1096
|
// route. On an install with no hosted client door the reader IS the operator, which is the same
|
|
1097
1097
|
// split `stdioConnectOffer` already trusts, so that fact answers the question by itself; on a
|
|
1098
1098
|
// hosted install it answers for nobody and the portal ignores it.
|
|
1099
|
-
appendAudit({ email: null, sub: null, body: { method: "initialize" }, status: "connected", transport: "stdio" });
|
|
1099
|
+
appendAudit({ email: null, sub: null, body: { method: "initialize" }, status: "connected", transport: "stdio", door: "local" });
|
|
1100
1100
|
})
|
|
1101
1101
|
.catch((e) => { log(`fatal: ${e?.stack ?? e}`); process.exit(1); });
|
|
1102
1102
|
}
|
package/package.json
CHANGED
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.7",
|
|
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": {
|
|
@@ -217,11 +217,11 @@ export const CAPABILITIES = Object.freeze({
|
|
|
217
217
|
// ── WHICH FORM OF A NON-LATIN MARK DOES THE INDEX HOLD? ──────────────────────────────────────────
|
|
218
218
|
// `false` = the TRANSLITERATION ONLY. The characters are not indexed, so searching them returns 0
|
|
219
219
|
// with no error — the exact false-clean shape a reader calls CLEAN:
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
220
|
+
// Searching the characters of a non-Latin mark returns nothing; searching its transliteration
|
|
221
|
+
// returns records, and those records carry the characters. Both halves are needed to see it —
|
|
222
|
+
// the empty answer alone is indistinguishable from a mark nobody has filed.
|
|
223
223
|
// Universal, not a CJK-specific behaviour: non-Latin records across CN/TW/JP/KR/TH/GR/UA/EG/IL/SA
|
|
224
|
-
//
|
|
224
|
+
// carry a populated markTransliteration.
|
|
225
225
|
//
|
|
226
226
|
// This declaration is what makes the refusal a CONTRACT rather than one vendor file's hand-written
|
|
227
227
|
// check: the shared executor (providers/_shared/execute-plan.mjs, via script-form.mjs) reads it and
|
|
@@ -250,7 +250,7 @@ export const CAPABILITIES = Object.freeze({
|
|
|
250
250
|
// makeEnumerate({ capabilities: {...CAPABILITIES.kernel} }) — these values are the LIVE seam settings,
|
|
251
251
|
// no longer a design note. pageGuard is 1 because /search is single-shot: there is no page 2 to
|
|
252
252
|
// fetch, so the guard can only ever be a backstop. namesChunkDefault = maxOrWidth (500): the kernel
|
|
253
|
-
// chunks a wide OR-stack to the parser's
|
|
253
|
+
// chunks a wide OR-stack to the parser's nesting bound before it reaches the wire.
|
|
254
254
|
kernel: Object.freeze({
|
|
255
255
|
countProbe: "endpoint",
|
|
256
256
|
screenSource: "billed-record-fetch",
|
|
@@ -1211,7 +1211,7 @@ export async function doBatchScreen(apiKey, base, params, tctx) {
|
|
|
1211
1211
|
// judgment was shown a native-script mark beside its own romanised query with no reading on
|
|
1212
1212
|
// either and concluded they were different marks. A delivered report told a client a
|
|
1213
1213
|
// jurisdiction had not been searched in its own script while the run held both the query and
|
|
1214
|
-
// this value.
|
|
1214
|
+
// this value. A large share of records carry one, so a dropped reading is not a rare edge.
|
|
1215
1215
|
//
|
|
1216
1216
|
// Null where the office records none, which is most Latin-script filings: this says what the
|
|
1217
1217
|
// register says, and inventing a romanisation here would be this row certifying a reading
|
|
@@ -192,7 +192,7 @@ export const DEFAULT_SEARCH_FIELDS = [
|
|
|
192
192
|
// ONE JSON envelope away: a 200 whose body is `{"message":"upstream search cluster unavailable"}`
|
|
193
193
|
// is valid JSON, so parseError never fires — and the old fallback coerced "an object with no
|
|
194
194
|
// totalHitCount" to 0, on a comment claiming a present body is "the provider answering". It is not.
|
|
195
|
-
// A SEARCH RESPONSE is a body that carries the search-response shape this endpoint
|
|
195
|
+
// A SEARCH RESPONSE is a body that carries the search-response shape this endpoint is documented to
|
|
196
196
|
// return: totalHitCount (the count), rows (the records) or nextRequest (the paging cursor). A
|
|
197
197
|
// parseable body with none of the three — an error envelope, a gateway stub — is the provider
|
|
198
198
|
// saying something OTHER than an answer, and it rides out as a non-answer (null total, an error
|
|
@@ -435,7 +435,7 @@ export async function doExpandPhoneme(sessionKey, params, tctx) {
|
|
|
435
435
|
// provider); they are RE-EXPORTED here unchanged so every existing importer — engine/mcp/corsearch-server.mjs,
|
|
436
436
|
// register-plan.mjs, the driver — keeps resolving them from this module with identical semantics.
|
|
437
437
|
// classifyStatus carries brand-json's vocabulary (Valid/Pending/GracePeriod live; Invalid/Expired dead;
|
|
438
|
-
// anything else AMBIGUOUS → never auto-drop). BATCH_SCREEN_CHUNK = 100
|
|
438
|
+
// anything else AMBIGUOUS → never auto-drop). BATCH_SCREEN_CHUNK = 100 matches the brand-json page size.
|
|
439
439
|
export { BATCH_SCREEN_CHUNK, chunk, classifyStatus, isAllClass, normalizeBrandRow, screenVerdict };
|
|
440
440
|
|
|
441
441
|
export async function doBatchScreen(sessionKey, params, tctx) {
|
|
@@ -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.7",
|
|
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).",
|
|
@@ -523,9 +523,9 @@ export function reconcileGridLedger(stdoutStr, spec) {
|
|
|
523
523
|
*
|
|
524
524
|
* Why identity and not a count: the sandbox program TRANSCRIBES the dictated queries into its own source,
|
|
525
525
|
* and a mutated string is a DIFFERENT SEARCH. The count stays right while the dictated query never ran, so
|
|
526
|
-
* every count-based check upstream and downstream reads clean.
|
|
527
|
-
*
|
|
528
|
-
*
|
|
526
|
+
* every count-based check upstream and downstream reads clean. The substitution this catches fuses one
|
|
527
|
+
* row's script into another row's transliteration: the two strings differ, the dictated and recorded
|
|
528
|
+
* counts still match exactly, and one dictated query is never searched by anybody.
|
|
529
529
|
*
|
|
530
530
|
* A query that THREW and said so is not this defect — it owns a gap row (`<query> | connotation | <error>`,
|
|
531
531
|
* or the reconciled object form) and the driver's merge gate weighs it separately. Only a silent
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
// it beats a number that is right for one shape and wrong for the other. See
|
|
35
35
|
// OWNER_SCOPED_WINDOW below, which is the machine-readable half.
|
|
36
36
|
//
|
|
37
|
-
//
|
|
37
|
+
// The total is a separate matter: it saturates at 10000 and flags itself
|
|
38
38
|
// approximate there — a fact about the count, not about the window.
|
|
39
39
|
//
|
|
40
40
|
// PURE: no node imports, no vendor HTTP.
|
|
@@ -129,7 +129,7 @@ export const CAPABILITIES = Object.freeze({
|
|
|
129
129
|
classFilter: "native",
|
|
130
130
|
// Search rows already carry status / nice_classes / owner_name → screening is inline, zero extra calls.
|
|
131
131
|
screenSource: "search-row",
|
|
132
|
-
// No
|
|
132
|
+
// No hard result ceiling, and no total to compare one against.
|
|
133
133
|
resultCeiling: null,
|
|
134
134
|
|
|
135
135
|
// ── the predicates, and WHICH REQUEST SHAPE each one rides ──────────────────────────────────────
|
|
@@ -247,10 +247,9 @@ export const CAPABILITIES = Object.freeze({
|
|
|
247
247
|
// `legacy_code`. asks for `code` to be primary, and it now is, in the one place it decides
|
|
248
248
|
// anything: `SIGNA_OFFICE_CODES` is what a reader and a future translate() should reach for.
|
|
249
249
|
//
|
|
250
|
-
// THE MIGRATION IS NOT URGENT AND THE REASON IS
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
// prv/SE 22, ukipo/GB 96, uspto/US 169, wipo/WO 68). Nothing on the wire moves if `translate` keeps
|
|
250
|
+
// THE MIGRATION IS NOT URGENT, AND THE REASON IS A PROPERTY OF THE REGISTER rather than a guess:
|
|
251
|
+
// each legacy key and its ISO code address the same office, so the two spellings are interchangeable
|
|
252
|
+
// for every office this deployment reaches. Nothing on the wire moves if `translate` keeps
|
|
254
253
|
// emitting keys, so it does — a vocabulary swap under the executor buys nothing and risks a live
|
|
255
254
|
// office lookup.
|
|
256
255
|
//
|
|
@@ -316,7 +316,7 @@ export function isSearchResponseBody(body) {
|
|
|
316
316
|
// 21, 101, 18) and — the case that matters — an empty band answered `total_count: 0, approximate:
|
|
317
317
|
// false`, an EXACT zero, which is the only kind this repository is allowed to render.
|
|
318
318
|
//
|
|
319
|
-
//
|
|
319
|
+
// An approximate total of exactly 10000 is a saturation marker rather than an estimate.
|
|
320
320
|
// The vendor is saying "at least ten thousand", and it says so on the broad sweeps (a bare owner
|
|
321
321
|
// filter, `match: similar`, an unanchored `contains`) — precisely the bands a clearance cannot
|
|
322
322
|
// enumerate anyway.
|