clearotron 0.3.2-beta.5 → 0.3.2-beta.6

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": "ed09608753479ab7824e8f5f7dc256c12ca0bc07",
3
- "version": "0.3.2-beta.5"
2
+ "commit": "91905bd9b7ffd04985537f799d2ef49359e3d0e6",
3
+ "version": "0.3.2-beta.6"
4
4
  }
@@ -1,5 +1,15 @@
1
1
  # clearotron-driver
2
2
 
3
+ ## 0.3.2-beta.6
4
+
5
+ ### Patch Changes
6
+
7
+ - 911b0b4: New: The report a client opens has been redrawn. It opens with what was asked, then the rating with its reasons, then the conflicts. What a search is and is not is no longer narrated at length.
8
+
9
+ New: A finding's full detail shows the goods as the register recorded them, and the record's own dates. This is on a full country search.
10
+
11
+ New: A knockout report closes with what happens next. The paragraph moves out of the long read rather than being repeated.
12
+
3
13
  ## 0.3.2-beta.5
4
14
 
5
15
  ### Patch Changes
@@ -611,6 +611,32 @@ export function findDroppedConnotationQueries(spec, mergedGrid) {
611
611
  * Deliberately NOT counted as searched: laundering an honest error into a clean receipt would be a worse
612
612
  * defect than the one being fixed.
613
613
  */
614
+ /**
615
+ * Of a caller's OWN list of missing queries, which ones did the plugin honestly report as having thrown?
616
+ *
617
+ * The sibling below derives its missing list itself, from the full spec. This one is handed one, because
618
+ * the meaning-seat gate computes a different list: it joins per half on `queryKey` where this module
619
+ * joins the merged grid on `norm`. Two callers, two populations, ONE author of what a gap row means —
620
+ * which is the point of the split. Reuse the key, not the question.
621
+ *
622
+ * Returns the CALLER'S query verbatim, never the gap row's term, so the caller can index what comes back
623
+ * against the list it passed in. A gap term that matched is by definition the same query under `norm`,
624
+ * and handing back the other spelling would make the caller join twice on two keys to use the answer.
625
+ */
626
+ export function erroredConnotationQueriesAmong(missingQueries, mergedGrid) {
627
+ const gaps = (Array.isArray(mergedGrid?.gaps) ? mergedGrid.gaps : [])
628
+ .filter((g) => g && String(g.platform ?? "").toLowerCase() === "connotation");
629
+ if (!gaps.length) return [];
630
+ const out = [];
631
+ for (const m of missingQueries ?? []) {
632
+ const k = norm(String(m ?? ""));
633
+ if (!k) continue;
634
+ const hit = gaps.find((g) => norm(String(g.term ?? "").trim()) === k);
635
+ if (hit) out.push({ query: m, error: String(hit.error ?? "unspecified") });
636
+ }
637
+ return out;
638
+ }
639
+
614
640
  export function findErroredConnotationQueries(spec, mergedGrid) {
615
641
  const missing = new Set(findDroppedConnotationQueries(spec, mergedGrid).map(norm));
616
642
  if (!missing.size) return [];
@@ -488,7 +488,7 @@ export const E3_BACKLOG = [
488
488
  where: "driver/stages.mjs:3231",
489
489
  surface: "stage-message",
490
490
  evidence: "- meters: {\"mark_similarity\":{...},\"goods_proximity\":{...},\"use\":{...},\"enforcer\":{...}} — all four present, each {\"token\",\"basis\",\"source\"}. … mark_similarity = high | medium | low. goods_proximity = high | medium | low. enforcer = high | medium | low | unknown. use = confirmed | not-confirmed | un",
491
- reparsedBy: "driver/findings-model.mjs:813 parseFindingsJson; driver/verify.mjs:1127 checkFindingsSibling gates meters.*.source; finding_basis_source_missing",
491
+ reparsedBy: "driver/findings-model.mjs:813 parseFindingsJson; driver/verify.mjs:1153 checkFindingsSibling gates meters.*.source; finding_basis_source_missing",
492
492
  removedByMove: "NOTHING ON THE #850 PLAN REMOVES THIS",
493
493
  },
494
494
  {
@@ -684,7 +684,7 @@ export const E3_BACKLOG = [
684
684
  // because an un-anchorable row lands in the NOT-CHECKED slice, and that is coverage lost rather than
685
685
  // a pass.
686
686
  evidence: "is ONE of coverage-disposition | fact | rating | narrative — pick the one your own legal read says the correction IS: coverage-disposition (a coverage row / disposition placement is wrong or dishonest)",
687
- reparsedBy: "driver/verify.mjs:803 CORRECTION_KIND_RE = /\\[kind:\\s*([a-z][a-z-]*)\\s*\\]/i → parseCorrectionKinds (verify.mjs:1026), consumed in pipelineInner() in pipeline.mjs for the run.jsonl `correction-kinds` histogram, which since #1558 also carries `kindChannelOk` — the counts are DERIVED from the parsed rows, and that key states whether the reviewer's kind channel produced anything at all rather than leaving a reader to infer it by comparing untyped against total. Telemetry only today",
687
+ reparsedBy: "driver/verify.mjs:829 CORRECTION_KIND_RE = /\\[kind:\\s*([a-z][a-z-]*)\\s*\\]/i → parseCorrectionKinds (verify.mjs:1052), consumed in pipelineInner() in pipeline.mjs for the run.jsonl `correction-kinds` histogram, which since #1558 also carries `kindChannelOk` — the counts are DERIVED from the parsed rows, and that key states whether the reviewer's kind channel produced anything at all rather than leaving a reader to infer it by comparing untyped against total. Telemetry only today",
688
688
  removedByMove: "NOTHING ON THE #850 PLAN REMOVES THIS",
689
689
  },
690
690
  {
@@ -697,7 +697,7 @@ export const E3_BACKLOG = [
697
697
  // `[on: -]` case went from a value to an ABSENCE — you omit the field — which is the one part a
698
698
  // reader could get wrong from the old wording, since there is no value meaning "no finding".
699
699
  evidence: "**AND EVERY FLAG CARRIES WHICH FINDING IT IS ABOUT** — the `on` field, an array of ordinals. Same rule as `kind`: you send the values, the driver renders the token.",
700
- reparsedBy: "driver/verify.mjs:820 CORRECTION_ON_RE = /\\[on:\\s*([0-9,\\s-]*?)\\s*\\]/i. SKILL-FILE ONLY — the stage message at stages.mjs:1842-1877 never mentions `[on:]`. This is #850's \"the element shape is in the skill file, not the stage message\" in its purest form: an E3 lint reading stages.mjs alone sees the [kind:] token and misses its twin",
700
+ reparsedBy: "driver/verify.mjs:846 CORRECTION_ON_RE = /\\[on:\\s*([0-9,\\s-]*?)\\s*\\]/i. SKILL-FILE ONLY — the stage message at stages.mjs:1842-1877 never mentions `[on:]`. This is #850's \"the element shape is in the skill file, not the stage message\" in its purest form: an E3 lint reading stages.mjs alone sees the [kind:] token and misses its twin",
701
701
  removedByMove: "NOTHING ON THE #850 PLAN REMOVES THIS",
702
702
  },
703
703
  {
@@ -17,12 +17,12 @@
17
17
  //
18
18
  // D1 verify.mjs:940 fail(`connotation_${reason}:…`) — reason iterates the table at line 894
19
19
  // D2 verify.mjs fail(String(e.message)) — parseFindingsJson throws token-first
20
- // D3 verify.mjs:1184 checkJson: fail(String(e.message)) — FIVE parsers reach this one site
20
+ // D3 verify.mjs:1210 checkJson: fail(String(e.message)) — FIVE parsers reach this one site
21
21
  // D4 verify.mjs parseCoverageLedgerJson, same shape
22
22
  // D5 verify.mjs:1504 fail(`${unaccounted[0].token}:…`) — token minted in a DATA ROW
23
23
  // D6 verify.mjs:1567 fail(`${violations[0].token}…`) — validatePlanFeasibility in register-plan.mjs
24
24
  // D7 verify.mjs:1558 fail(`${v2[0].token}${detail}…`) — register-plan.mjs:2018 disclosureTextByAxis
25
- // D8 verify.mjs:1692 fail(caseLawLedgerFail(…)) — token built in case-law-ledger.mjs:204
25
+ // D8 verify.mjs:2425 caseLawLedgerFail fail(caseLawLedgerFail(…)) — token built in case-law-ledger.mjs:204 caseLawLedgerFail
26
26
  //
27
27
  // A partition built on the 60 tokens a regex CAN see would run green while blind to the rest, which is
28
28
  // worse than having no E2 at all: it certifies a partition it never checked. So the census is authored
@@ -109,7 +109,7 @@ export const VOCABULARY = [
109
109
  { token: "coverage_form_missing", stages: ["register-digest"], site: "driver/verify.mjs" },
110
110
  { token: "coverage_form_empty", stages: ["register-digest"], site: "driver/verify.mjs" },
111
111
  { token: "coverage_status_offenum", stages: ["register-digest"], site: "driver/verify.mjs:2050" },
112
- { token: "coverage_deferred_unaccounted", stages: ["register-digest"], site: "driver/verify.mjs:1510 coverageFormFail", family: "driver/register-plan.mjs:1642 PROVIDER_HARD_ERROR_PREFIX — token on a data row", dynamic: "D5" },
112
+ { token: "coverage_deferred_unaccounted", stages: ["register-digest"], site: "driver/verify.mjs:1536 coverageFormFail", family: "driver/register-plan.mjs:1642 PROVIDER_HARD_ERROR_PREFIX — token on a data row", dynamic: "D5" },
113
113
  { token: "coverage_clean_unexecuted", stages: ["register-digest"], site: "driver/verify.mjs, the matterContext validator", family: "driver/register-plan.mjs:1417 validatePlanFeasibility", dynamic: "D6" },
114
114
  { token: "coverage_clean_skipped", stages: ["register-digest"], site: "driver/verify.mjs, the matterContext validator", family: "driver/register-plan.mjs:1766 searchedJurisdictionsFromPlan", dynamic: "D6" },
115
115
  { token: "coverage_clean_unverified_incomplete", stages: ["register-digest"], site: "driver/verify.mjs, the matterContext validator", family: "driver/register-plan.mjs:2018 disclosureTextByAxis", dynamic: "D7" },
@@ -130,7 +130,7 @@ export const VOCABULARY = [
130
130
  { token: "variantmodel_term_markup", stages: ["prelim-variants"], site: "driver/verify.mjs" },
131
131
  { token: "variantmodel_missing", stages: ["prelim-variants"], site: "driver/verify.mjs:1153, 1212" },
132
132
  // Recovered during E2 authoring, absent from the draft census: variant-manifest.json is strict-parsed
133
- // through checkSiblingJson (verify.mjs:1197) → checkJson (:742), so the WHOLE variantmodel_* family
133
+ // through checkSiblingJson (verify.mjs:1223) → checkJson (:742), so the WHOLE variantmodel_* family
134
134
  // reaches prelim-variants, not just the four literal tokens above.
135
135
  // CONVERSION 3 widened this family's SOURCE without widening its prefix. `acceptPrelimVariants` raises
136
136
  // `variantmodel_scope_layer_invalid`, `_scope_status_invalid`, `_scope_item_missing` and `_scope_pipe`
@@ -184,7 +184,7 @@ export const VOCABULARY = [
184
184
  // ── case-law / narrative-refutation ────────────────────────────────────────────────────────────────
185
185
  { token: "caselaw_ledger_missing", stages: ["case-law"], site: "driver/verify.mjs:1682, 1685" },
186
186
  { token: "caselaw_ledger_unparseable", stages: ["case-law"], site: "driver/verify.mjs" },
187
- { token: "caselaw_ledger", stages: ["case-law"], site: "driver/verify.mjs:1692", family: "driver/case-law-ledger.mjs:204 (census reasons)", dynamic: "D8" },
187
+ { token: "caselaw_ledger", stages: ["case-law"], site: "driver/verify.mjs:2425 caseLawLedgerFail", family: "driver/case-law-ledger.mjs caseLawLedgerFail (census reasons)", dynamic: "D8" },
188
188
  { token: "no_verdict_line", stages: ["narrative-refutation"], site: "driver/verify.mjs" },
189
189
  { token: "plan_audit_missing", stages: ["narrative-refutation"], site: "driver/verify.mjs" },
190
190
 
@@ -477,7 +477,7 @@ export const INNER_CODES = Object.freeze([
477
477
  // `axis_invalid` cause into its own family before counting the rest. A ruling naming only
478
478
  // `coverage_no_status` would be true of most `no_status` records and false of the ones that matter most.
479
479
  { code: "no_status", mints: ["driver/coverage-form.mjs:836"], rollsUpTo: ["coverage_no_status", "coverage_form_axis_invalid"],
480
- why: "Two composites, split on the record's `cause`: verify.mjs:1172 for cause `axis_invalid`, verify.mjs:1263 for the rest. NOT cited at coverage-form.mjs:810 — that is the JSDoc @returns annotation, not the mint." },
480
+ why: "Two composites, split on the record's `cause`: `checkFindingsSibling()` in verify.mjs for cause `axis_invalid`, `dispositionForm()` there for the rest. NOT the `@returns` annotation above `COVERAGE_CAUSES` in coverage-form.mjs — that is the annotation, not the mint." },
481
481
  { code: "engine_vocabulary", mints: ["driver/coverage-form.mjs:826"], rollsUpTo: ["coverage_form_engine_vocabulary"],
482
482
  why: "#669 — the seat wrote an engine token into the `reason` sentence that reaches the reader's page. Checked on settled rows too, because a row the seat considers finished is exactly the one whose sentence gets printed. Namespaced at verify.mjs:1165; the bare code names a row, never a stage." },
483
483
 
@@ -2,7 +2,7 @@
2
2
  "name": "clearotron-driver",
3
3
  "private": true,
4
4
  "type": "module",
5
- "version": "0.3.2-beta.5",
5
+ "version": "0.3.2-beta.6",
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/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,
@@ -669,6 +675,12 @@
669
675
  "skips": 0,
670
676
  "todos": 0
671
677
  },
678
+ "a-refused-query-says-whether-the-provider-declined-it.test.mjs": {
679
+ "tests": 3,
680
+ "asserts": 11,
681
+ "skips": 0,
682
+ "todos": 0
683
+ },
672
684
  "a-refused-unit-says-what-is-half-started.test.mjs": {
673
685
  "tests": 9,
674
686
  "asserts": 31,
@@ -1246,8 +1258,8 @@
1246
1258
  "todos": 0
1247
1259
  },
1248
1260
  "browser-check-membership.test.mjs": {
1249
- "tests": 8,
1250
- "asserts": 17,
1261
+ "tests": 11,
1262
+ "asserts": 26,
1251
1263
  "skips": 8,
1252
1264
  "todos": 0
1253
1265
  },
package/driver/verify.mjs CHANGED
@@ -10,7 +10,7 @@
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
16
  import { findConnotationViolations, parsePrRiskResults, MEANING_ANGLES_RE,
@@ -413,7 +413,33 @@ function commonLawMeaningSeat(p, c) {
413
413
  // The gate cannot tell the two apart and is not being asked to. There is no identity to join on —
414
414
  // that is the entire reason the dictated-versus-recorded comparison exists. So the label states the
415
415
  // observation, the remedy carries both cases, and no threshold decides which one a seat is told.
416
+ // ── A QUERY THE PROVIDER REFUSED IS NOT A QUERY NOBODY RAN ────────────────────────────────────
417
+ //
418
+ // The plugin's contract is to append `<query> | connotation | <exception>` to the ledger's gaps and
419
+ // carry on, so a query it threw on says so IN THE FILE THIS GATE JUST READ. The gate did not look:
420
+ // it refused on receipt membership alone, and a query the provider had already refused got the same
421
+ // sentence as one nobody ever issued — the two repairs, "edit the wording" and "run it", neither of
422
+ // which is the remedy when the provider itself declined.
423
+ //
424
+ // It still FAILS, and deliberately: `findErroredConnotationQueries` says so in its own words —
425
+ // laundering an honest error into a clean receipt would be a worse defect than this one. What
426
+ // changes is only what the seat is told, and therefore how many attempts it spends being told the
427
+ // wrong thing. On the clearance that prompted this, the half failed eight attempts across two
428
+ // recovery cycles.
429
+ //
430
+ // The missing list is THIS gate's own, joined per half on `queryKey`; the helper joins gap rows on
431
+ // `norm`, which is the one author of what a gap row names. Two populations, one key.
432
+ let ledgerParsed = null;
433
+ try { ledgerParsed = JSON.parse(ledgerRaw); } catch { /* unparseable was refused above */ }
434
+ const gapRows = (Array.isArray(ledgerParsed) ? ledgerParsed : [ledgerParsed])
435
+ .flatMap((b) => (Array.isArray(b?.gaps) ? b.gaps : []));
436
+ const reportedError = new Map(
437
+ erroredConnotationQueriesAmong(dropped, { gaps: gapRows }).map((e) => [e.query, e.error]));
416
438
  const parts = dropped.slice(0, 3).map((q) => {
439
+ const reported = reportedError.get(q);
440
+ // Named separately because the remedy is different: the search was attempted and the provider
441
+ // 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 this query: ${abbrev(reported, 60)}]`;
417
443
  const n = nearest(q);
418
444
  return n
419
445
  ? `${abbrev(q, 40)} [unmatched; nearest recorded: ${abbrev(n, 40)}]`
@@ -1,5 +1,9 @@
1
1
  # trademark-artifacts-mcp
2
2
 
3
+ ## 0.3.2-beta.6
4
+
5
+ No changes in this release.
6
+
3
7
  ## 0.3.2-beta.5
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.5",
3
+ "version": "0.3.2-beta.6",
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.5",
4
+ "version": "0.3.2-beta.6",
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.6",
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": {
@@ -1,5 +1,9 @@
1
1
  # trademark-oauth-mcp-bridge
2
2
 
3
+ ## 0.3.2-beta.6
4
+
5
+ No changes in this release.
6
+
3
7
  ## 0.3.2-beta.5
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.5",
3
+ "version": "0.3.2-beta.6",
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).",