clearotron 0.3.2-beta.5 → 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.
@@ -74,8 +74,11 @@ export const RESULT_NOUN_FIELDS = Object.freeze([
74
74
  why: "the one member that reported an invocation — taint-rerun's `r.ok ? \"ok\" : …`, which travels on a StageFailure packet — now carries a `cleared` sibling read from the taint, the remedy 10/5 -> 11/6 at the profile-store receipt's `outcome: pr.outcome`. Classified by READING ITS WRITING SITE, which is profiles.mjs and not this file: the value is one of three literals chosen by a situation the resolver decided (`overlay` and `bundled-fallback` are `pass`, `env-arrived-late` is `blocked`), never a call's return read as a verdict. `bundled-fallback` being `pass` is the point of the whole receipt — a legitimate install that nobody was told about is what this row says out loud." },
75
75
  { file: "driver/pipeline.mjs", noun: "permanent", sites: 1, atWriteSite: 1, verdict: "result",
76
76
  why: "`permanent.length` — a count of the classified set" },
77
- { file: "driver/pipeline.mjs", noun: "recovered", sites: 3, atWriteSite: 3, verdict: "result",
78
- why: "each sits on a branch reached only after the gap was cleared; one follows a `throw` that guarantees the ledger exists" },
77
+ { file: "driver/pipeline.mjs", noun: "recovered", sites: 5, atWriteSite: 5, verdict: "result",
78
+ why: "each sits on a branch reached only after the gap was cleared; one follows a `throw` that guarantees the ledger exists. "
79
+ + "The two newest record what an engine-side re-issue of a missing meaning search actually brought back, counted off the rows "
80
+ + "in hand at the write site rather than from anything the call promised — an attempt that recovered nothing writes 0, which "
81
+ + "is the fact the disclosure downstream depends on" },
79
82
  { file: "driver/pipeline.mjs", noun: "settled", sites: 5, atWriteSite: 4, verdict: "result",
80
83
  why: "counts off the union and the doubt ledger" },
81
84
  { file: "driver/pipeline.mjs", noun: "verified", sites: 3, atWriteSite: 1, verdict: "result",
package/driver/stages.mjs CHANGED
@@ -1920,7 +1920,7 @@ export const STAGES = {
1920
1920
  },
1921
1921
  "Findings section prose (both branches — on seat m, every loaded reading as its own finding with its receipt)": {
1922
1922
  class: "judgment", tokens: ["missing", "too_short", "declared_unavailable"],
1923
- why: "#850 rules the prose J. The findings-heading arm differs per seat: verify.mjs:447 for a/b, verify.mjs:252 for m (which also accepts meaning/connotation). [citation unverified]",
1923
+ why: "#850 rules the prose J. The findings-heading arm differs per seat: `commonLawStructural()` in verify.mjs for a/b, `commonLawMeaningSeat()` for m (which also accepts meaning/connotation)",
1924
1924
  },
1925
1925
  "Negative-results matrix — one receipt-carrying row per (variant x platform) cell (seats a/b only)": {
1926
1926
  class: "judgment", tokens: ["missing"],
@@ -2071,7 +2071,7 @@ export const STAGES = {
2071
2071
  contractElements: {
2072
2072
  "execute the frozen plan — ONE register_execute_plan call with {plan_path, axis, output_path}": {
2073
2073
  class: "mechanical:tool-written", tokens: ["named_band_missing", "tool_timeout"],
2074
- why: "All three args are driver values interpolated into the message; the tool writes every band block. #850 calls this already right. Note #793: `named_band_missing` and `tool_timeout` are one evidence state with two causes, and registerPlanCallKilled (verify.mjs:1265) separates them from the call log, not from the model. [citation unverified]",
2074
+ why: "All three args are driver values interpolated into the message; the tool writes every band block. #850 calls this already right. Note #793: `named_band_missing` and `tool_timeout` are one evidence state with two causes, and registerPlanCallKilled (verify.mjs:1291) separates them from the call log, not from the model. [citation unverified]",
2075
2075
  },
2076
2076
  "the dictated entry list — qid, predicate, terms, owner, nice_classes, regions, when-guard, expected_kind, covered_by": {
2077
2077
  class: "mechanical:pre-bound", tokens: [],
@@ -2123,7 +2123,7 @@ export const STAGES = {
2123
2123
  },
2124
2124
  "layer-execution declaration — whether the prose says the register layer / provider tools were not executed or not bound": {
2125
2125
  class: "mechanical:code-extracted", tokens: ["declared_not_executed"],
2126
- why: "_driver/plan-execution.json and the tool-call log already hold whether the call ran — registerPlanCallKilled (verify.mjs:1265) reads exactly that to settle the same question one arm below. This arm still decides it from the model's sentence. [citation unverified]",
2126
+ why: "_driver/plan-execution.json and the tool-call log already hold whether the call ran — registerPlanCallKilled (verify.mjs:1291) reads exactly that to settle the same question one arm below. This arm still decides it from the model's sentence. [citation unverified]",
2127
2127
  },
2128
2128
  "`CROSS-CHECK REQUIRED: <what> — <why>` — the check that is needed and why": {
2129
2129
  class: "judgment", tokens: [],
@@ -573,6 +573,12 @@
573
573
  "skips": 0,
574
574
  "todos": 0
575
575
  },
576
+ "a-query-is-not-crossed-with-its-own-variant.test.mjs": {
577
+ "tests": 4,
578
+ "asserts": 6,
579
+ "skips": 0,
580
+ "todos": 0
581
+ },
576
582
  "a-queued-job-is-visible-before-a-worker-claims-it.test.mjs": {
577
583
  "tests": 6,
578
584
  "asserts": 16,
@@ -645,6 +651,12 @@
645
651
  "skips": 0,
646
652
  "todos": 0
647
653
  },
654
+ "a-refusal-names-the-collection-it-searched.test.mjs": {
655
+ "tests": 5,
656
+ "asserts": 13,
657
+ "skips": 0,
658
+ "todos": 0
659
+ },
648
660
  "a-refusal-names-the-file-when-the-command-it-offers-cannot-be-run.test.mjs": {
649
661
  "tests": 8,
650
662
  "asserts": 16,
@@ -669,6 +681,12 @@
669
681
  "skips": 0,
670
682
  "todos": 0
671
683
  },
684
+ "a-refused-query-says-whether-the-provider-declined-it.test.mjs": {
685
+ "tests": 3,
686
+ "asserts": 12,
687
+ "skips": 0,
688
+ "todos": 0
689
+ },
672
690
  "a-refused-unit-says-what-is-half-started.test.mjs": {
673
691
  "tests": 9,
674
692
  "asserts": 31,
@@ -687,6 +705,12 @@
687
705
  "skips": 0,
688
706
  "todos": 0
689
707
  },
708
+ "a-release-note-cannot-be-counted-twice.test.mjs": {
709
+ "tests": 12,
710
+ "asserts": 33,
711
+ "skips": 0,
712
+ "todos": 0
713
+ },
690
714
  "a-release-note-is-written-for-its-reader.test.mjs": {
691
715
  "tests": 9,
692
716
  "asserts": 66,
@@ -1246,8 +1270,8 @@
1246
1270
  "todos": 0
1247
1271
  },
1248
1272
  "browser-check-membership.test.mjs": {
1249
- "tests": 8,
1250
- "asserts": 17,
1273
+ "tests": 11,
1274
+ "asserts": 26,
1251
1275
  "skips": 8,
1252
1276
  "todos": 0
1253
1277
  },
@@ -3058,8 +3082,8 @@
3058
3082
  "todos": 0
3059
3083
  },
3060
3084
  "pipeline.mock.test.mjs": {
3061
- "tests": 94,
3062
- "asserts": 795,
3085
+ "tests": 97,
3086
+ "asserts": 806,
3063
3087
  "skips": 0,
3064
3088
  "todos": 0
3065
3089
  },
@@ -3694,8 +3718,8 @@
3694
3718
  "todos": 0
3695
3719
  },
3696
3720
  "release-pipeline.test.mjs": {
3697
- "tests": 99,
3698
- "asserts": 376,
3721
+ "tests": 101,
3722
+ "asserts": 384,
3699
3723
  "skips": 0,
3700
3724
  "todos": 0
3701
3725
  },
@@ -5374,8 +5398,8 @@
5374
5398
  "todos": 0
5375
5399
  },
5376
5400
  "update-refuses-on-a-packaged-install.test.mjs": {
5377
- "tests": 6,
5378
- "asserts": 21,
5401
+ "tests": 8,
5402
+ "asserts": 26,
5379
5403
  "skips": 0,
5380
5404
  "todos": 0
5381
5405
  },
@@ -5721,8 +5745,8 @@
5721
5745
  "todos": 0
5722
5746
  },
5723
5747
  "http-handler.test.mjs": {
5724
- "tests": 17,
5725
- "asserts": 40,
5748
+ "tests": 20,
5749
+ "asserts": 47,
5726
5750
  "skips": 0,
5727
5751
  "todos": 0
5728
5752
  },
@@ -5860,7 +5884,7 @@
5860
5884
  },
5861
5885
  "the-portal-can-tell-who-has-connected-an-assistant.test.mjs": {
5862
5886
  "tests": 12,
5863
- "asserts": 33,
5887
+ "asserts": 35,
5864
5888
  "skips": 0,
5865
5889
  "todos": 0
5866
5890
  },
package/driver/verify.mjs CHANGED
@@ -10,10 +10,11 @@
10
10
  import { readFileSync, existsSync, readdirSync } from "node:fs";
11
11
  import { dirname, join, basename } from "node:path";
12
12
  import { driverDir } from "../shared/driver-dir.mjs"; //
13
- import { findReceiptViolations, findGridLedgerViolations, findPlatformIdentityViolations, parsePrRiskQueries, MEANING_SEAT } from "./common-law-receipts.mjs";
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, MEANING_ANGLES_RE,
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) {
@@ -413,13 +421,49 @@ function commonLawMeaningSeat(p, c) {
413
421
  // The gate cannot tell the two apart and is not being asked to. There is no identity to join on —
414
422
  // that is the entire reason the dictated-versus-recorded comparison exists. So the label states the
415
423
  // observation, the remedy carries both cases, and no threshold decides which one a seat is told.
424
+ // ── A QUERY THE PROVIDER REFUSED IS NOT A QUERY NOBODY RAN ────────────────────────────────────
425
+ //
426
+ // The plugin's contract is to append `<query> | connotation | <exception>` to the ledger's gaps and
427
+ // carry on, so a query it threw on says so IN THE FILE THIS GATE JUST READ. The gate did not look:
428
+ // it refused on receipt membership alone, and a query the provider had already refused got the same
429
+ // sentence as one nobody ever issued — the two repairs, "edit the wording" and "run it", neither of
430
+ // which is the remedy when the provider itself declined.
431
+ //
432
+ // It still FAILS, and deliberately: `findErroredConnotationQueries` says so in its own words —
433
+ // laundering an honest error into a clean receipt would be a worse defect than this one. What
434
+ // changes is only what the seat is told, and therefore how many attempts it spends being told the
435
+ // wrong thing. On the clearance that prompted this, the half failed eight attempts across two
436
+ // recovery cycles.
437
+ //
438
+ // The missing list is THIS gate's own, joined per half on `queryKey`; the helper joins gap rows on
439
+ // `norm`, which is the one author of what a gap row names. Two populations, one key.
440
+ let ledgerParsed = null;
441
+ try { ledgerParsed = JSON.parse(ledgerRaw); } catch { /* unparseable was refused above */ }
442
+ const gapRows = (Array.isArray(ledgerParsed) ? ledgerParsed : [ledgerParsed])
443
+ .flatMap((b) => (Array.isArray(b?.gaps) ? b.gaps : []));
444
+ const reportedError = new Map(
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
+ : "";
416
456
  const parts = dropped.slice(0, 3).map((q) => {
457
+ const reported = reportedError.get(q);
458
+ // Named separately because the remedy is different: the search was attempted and the provider
459
+ // declined it, so re-running it unchanged is the one repair that cannot work.
460
+ if (reported) return `${abbrev(q, 40)} [not recorded in ${LEDGER} because the provider REPORTED an error on it: ${abbrev(reported, 60)}]`;
417
461
  const n = nearest(q);
418
462
  return n
419
- ? `${abbrev(q, 40)} [unmatched; nearest recorded: ${abbrev(n, 40)}]`
420
- : `${abbrev(q, 40)} [no recorded query resembles this one]`;
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`;
421
465
  });
422
- 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}`);
423
467
  }
424
468
  if (spec?.connotation?.disposition_required === true) {
425
469
  const recorded = parsePrRiskResults(ledgerRaw);
@@ -1,5 +1,13 @@
1
1
  # trademark-artifacts-mcp
2
2
 
3
+ ## 0.3.2-beta.7
4
+
5
+ No changes in this release.
6
+
7
+ ## 0.3.2-beta.6
8
+
9
+ No changes in this release.
10
+
3
11
  ## 0.3.2-beta.5
4
12
 
5
13
  No changes in this release.
@@ -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); });
@@ -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 } : {}), ...(door ? { door } : {}) }) + "\n";
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
- const door = clientSurface ? "client" : undefined;
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 */ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trademark-artifacts-mcp",
3
- "version": "0.3.2-beta.5",
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.",
@@ -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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clearotron",
3
3
  "type": "module",
4
- "version": "0.3.2-beta.5",
4
+ "version": "0.3.2-beta.7",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
7
7
  "type": "git",
@@ -2,7 +2,7 @@
2
2
  "name": "portal-ui",
3
3
  "private": true,
4
4
  "type": "module",
5
- "version": "0.3.2-beta.5",
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
- // 华威豹 → 0 HUA WEI BAO → 32 (and the 32 CONTAIN 华威豹)
221
- // 小米 → 0 XIAOMI → 57632
222
- // 스타벅스 → 0
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
- // carried a populated markTransliteration.
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 probed nesting bound before it reaches the wire.
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. On the measured round 558 of 1,937 records had one to carry.
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 was probed to
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 = the observed brand-json page size.
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,5 +1,13 @@
1
1
  # trademark-oauth-mcp-bridge
2
2
 
3
+ ## 0.3.2-beta.7
4
+
5
+ No changes in this release.
6
+
7
+ ## 0.3.2-beta.6
8
+
9
+ No changes in this release.
10
+
3
11
  ## 0.3.2-beta.5
4
12
 
5
13
  No changes in this release.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trademark-oauth-mcp-bridge",
3
- "version": "0.3.2-beta.5",
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. Observed: the dictated
527
- * `提基斯拉什 offensive meaning` came back as `提基斯ラッシュ offensive meaning` — the katakana of the
528
- * sibling Japanese row fused into the Chinese transliteration. 27 dictated, 27 recorded, one never searched.
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
- // What IS observed about the total is separate: it saturates at 10000 and flags itself
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 documented or observed hard result ceiling, and no total to compare one against.
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 MEASURED, not assumed. All eleven legacy keys were
251
- // sent to the live wire beside their ISO codes, and every pair returned an identical
252
- // total (cipo/CA 83, euipo/EM 80, inpi-fr/FR 19, ipau/AU 72, ipi/CH 46, ipos/SG 58, nipo/NO 42,
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
- // Every approximate answer came back as exactly 10000: it is a saturation marker, not an estimate.
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.