clearotron 0.3.1-beta.3 → 0.3.1

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.
@@ -849,6 +849,12 @@
849
849
  "skips": 0,
850
850
  "todos": 0
851
851
  },
852
+ "a-two-form-matter-owes-a-read-per-form.test.mjs": {
853
+ "tests": 8,
854
+ "asserts": 25,
855
+ "skips": 0,
856
+ "todos": 0
857
+ },
852
858
  "a-unit-configuration-cannot-reach-is-resolved-at-install.test.mjs": {
853
859
  "tests": 5,
854
860
  "asserts": 14,
@@ -1384,8 +1390,8 @@
1384
1390
  "todos": 0
1385
1391
  },
1386
1392
  "common-law-receipts.test.mjs": {
1387
- "tests": 36,
1388
- "asserts": 129,
1393
+ "tests": 40,
1394
+ "asserts": 141,
1389
1395
  "skips": 0,
1390
1396
  "todos": 0
1391
1397
  },
@@ -1445,7 +1451,7 @@
1445
1451
  },
1446
1452
  "connect-clients-are-data.test.mjs": {
1447
1453
  "tests": 17,
1448
- "asserts": 87,
1454
+ "asserts": 91,
1449
1455
  "skips": 0,
1450
1456
  "todos": 0
1451
1457
  },
@@ -1511,7 +1517,7 @@
1511
1517
  },
1512
1518
  "contract-audit.test.mjs": {
1513
1519
  "tests": 54,
1514
- "asserts": 167,
1520
+ "asserts": 168,
1515
1521
  "skips": 0,
1516
1522
  "todos": 0
1517
1523
  },
@@ -1731,6 +1737,12 @@
1731
1737
  "skips": 0,
1732
1738
  "todos": 0
1733
1739
  },
1740
+ "deprecating-below-a-stable-touches-nothing-above-it.test.mjs": {
1741
+ "tests": 4,
1742
+ "asserts": 18,
1743
+ "skips": 0,
1744
+ "todos": 0
1745
+ },
1734
1746
  "depth-ladder-loop-bounds.test.mjs": {
1735
1747
  "tests": 6,
1736
1748
  "asserts": 10,
@@ -2211,6 +2223,12 @@
2211
2223
  "skips": 0,
2212
2224
  "todos": 0
2213
2225
  },
2226
+ "every-json-response-says-how-it-may-be-cached.test.mjs": {
2227
+ "tests": 4,
2228
+ "asserts": 14,
2229
+ "skips": 0,
2230
+ "todos": 0
2231
+ },
2214
2232
  "every-lane-declares-its-transport-position.test.mjs": {
2215
2233
  "tests": 7,
2216
2234
  "asserts": 12,
@@ -2829,6 +2847,12 @@
2829
2847
  "skips": 0,
2830
2848
  "todos": 0
2831
2849
  },
2850
+ "no-two-dictated-queries-fold-to-one-key.test.mjs": {
2851
+ "tests": 5,
2852
+ "asserts": 10,
2853
+ "skips": 0,
2854
+ "todos": 0
2855
+ },
2832
2856
  "no-vendor-provenance-in-the-public-cut.test.mjs": {
2833
2857
  "tests": 4,
2834
2858
  "asserts": 11,
@@ -4545,6 +4569,12 @@
4545
4569
  "skips": 0,
4546
4570
  "todos": 0
4547
4571
  },
4572
+ "the-containment-rule-does-not-depend-on-the-string-tmp.test.mjs": {
4573
+ "tests": 2,
4574
+ "asserts": 3,
4575
+ "skips": 0,
4576
+ "todos": 0
4577
+ },
4548
4578
  "the-corrections-section-is-an-allowlist.test.mjs": {
4549
4579
  "tests": 6,
4550
4580
  "asserts": 18,
@@ -4563,6 +4593,12 @@
4563
4593
  "skips": 0,
4564
4594
  "todos": 0
4565
4595
  },
4596
+ "the-cut-approves-its-own-parked-run.test.mjs": {
4597
+ "tests": 4,
4598
+ "asserts": 10,
4599
+ "skips": 0,
4600
+ "todos": 0
4601
+ },
4566
4602
  "the-demo-account-shows-a-configured-customer.test.mjs": {
4567
4603
  "tests": 4,
4568
4604
  "asserts": 24,
@@ -5786,8 +5822,8 @@
5786
5822
  "ext": "ts",
5787
5823
  "perFile": {
5788
5824
  "api.test.ts": {
5789
- "tests": 44,
5790
- "asserts": 176,
5825
+ "tests": 45,
5826
+ "asserts": 183,
5791
5827
  "skips": 0,
5792
5828
  "todos": 0
5793
5829
  },
@@ -5918,8 +5954,8 @@
5918
5954
  "todos": 0
5919
5955
  },
5920
5956
  "home.test.ts": {
5921
- "tests": 51,
5922
- "asserts": 177,
5957
+ "tests": 56,
5958
+ "asserts": 196,
5923
5959
  "skips": 0,
5924
5960
  "todos": 0
5925
5961
  },
package/driver/verify.mjs CHANGED
@@ -12,9 +12,9 @@ import { dirname, join, basename } from "node:path";
12
12
  import { driverDir } from "../shared/driver-dir.mjs"; //
13
13
  import { findReceiptViolations, findGridLedgerViolations, findPlatformIdentityViolations, parsePrRiskQueries, MEANING_SEAT } from "./common-law-receipts.mjs";
14
14
  // Conversion 2 — the discriminator the two rulings above key on. PURE-ish: one existsSync-shaped read.
15
- import { matterFrameWasRecorded } from "./matter-frame-record.mjs";
15
+ import { matterFrameWasRecorded, frameRatifiedForms } from "./matter-frame-record.mjs";
16
16
  import { findConnotationViolations, parsePrRiskResults, MEANING_ANGLES_RE,
17
- parseDispositionForm, CONNOTATION_UNRULED_REASONS } from "./connotation-search.mjs";
17
+ parseDispositionForm, CONNOTATION_UNRULED_REASONS, queryKey } from "./connotation-search.mjs";
18
18
  import { formSidecarName, formSidecarPath } from "./disposition-union.mjs";
19
19
  // B — the transport's own four failure states. The audit reads the run's records; this file locates them.
20
20
  import { auditDispositionCalls, CALL_FAILURE_REASONS } from "./disposition-call-audit.mjs";
@@ -354,12 +354,54 @@ function commonLawMeaningSeat(p, c) {
354
354
  try { ledgerRaw = readFileSync(join(dirname(p), `common-law-grid.half-${MEANING_SEAT}.json`), "utf8"); } catch { /* missing → fail-closed below */ }
355
355
  if (ledgerRaw == null) return fail(`grid_ledger_missing:common-law-grid.half-${MEANING_SEAT}.json absent while _driver/grid-spec.half-${MEANING_SEAT}.json dictates the meaning sweep`);
356
356
  const dictated = Array.isArray(spec?.connotation?.queries) ? spec.connotation.queries : [];
357
- let recordedQ;
358
- try { recordedQ = new Set(parsePrRiskResults(ledgerRaw).map((e) => String(e?.query ?? "").trim())); }
357
+ // COMPARED ON A KEY, NOT ON THE RAW TEXT — `queryKey` in connotation-search.mjs, beside the parser,
358
+ // so there is one author of what "the same query" means. The dictated spelling is the driver's; the
359
+ // recorded one comes back through a provider that returns typographic punctuation. Measured on a
360
+ // production clearance, 2026-09-16: one query of sixty-one differed by a single right single quotation
361
+ // mark where the driver wrote an apostrophe, and the grid had run and recorded every one of them.
362
+ //
363
+ // THE ORIGINAL TEXT IS WHAT THE REFUSAL QUOTES, below. Somebody chasing a query that really is missing
364
+ // needs the spelling the driver asked for, not a flattened key.
365
+ let recordedRaw;
366
+ try { recordedRaw = parsePrRiskResults(ledgerRaw).map((e) => String(e?.query ?? "")); }
359
367
  catch (e) { return fail(`grid_ledger_unparseable:${String(e.message).slice(0, 80)}`); }
360
- const dropped = dictated.filter((q) => !recordedQ.has(String(q).trim()));
361
- if (dropped.length)
362
- return fail(`connotation_query_unrecorded:${dropped.slice(0, 3).map((q) => abbrev(q, 40)).join(",")}${dropped.length > 3 ? ` (+${dropped.length - 3} more)` : ""}`);
368
+ const recordedQ = new Set(recordedRaw.map(queryKey));
369
+ const dropped = dictated.filter((q) => !recordedQ.has(queryKey(q)));
370
+ if (dropped.length) {
371
+ // ── THE REFUSAL SAYS WHICH OF TWO FAULTS THIS IS, because they need opposite remedies ──────────
372
+ //
373
+ // ABSENT: no recorded query resembles it, so the search was not run and the seat must run it.
374
+ // UNMATCHED: something close IS recorded, so the search ran and the two spellings disagree beyond
375
+ // what the key folds — a re-ordering, a translation, a truncation, a query the provider chose for
376
+ // itself. Telling the seat to "re-run the missing query" in that case asks for the one thing that
377
+ // cannot help, and that is what turned one attempt into four on a production clearance.
378
+ //
379
+ // NO THRESHOLD DECIDES ANYTHING (owner's ruling). The nearest recorded query is shown so a person or
380
+ // a seat can SEE the difference in one attempt; it never makes the gate pass. A similarity score
381
+ // that could pass this gate would be a score that can hide a skipped query, which is what the gate
382
+ // is for.
383
+ const nearest = (q) => {
384
+ const words = new Set(queryKey(q).split(" ").filter(Boolean));
385
+ if (!words.size) return null;
386
+ let best = null, bestScore = 0;
387
+ for (const r of recordedRaw) {
388
+ const rw = queryKey(r).split(" ").filter(Boolean);
389
+ if (!rw.length) continue;
390
+ const shared = rw.filter((w) => words.has(w)).length / Math.max(words.size, rw.length);
391
+ if (shared > bestScore) { bestScore = shared; best = r; }
392
+ }
393
+ // A single shared word is a coincidence in a sweep about one mark, where the mark's own name is in
394
+ // every query. Below half the words in common, say nothing rather than point at a red herring.
395
+ return bestScore >= 0.5 ? best : null;
396
+ };
397
+ const parts = dropped.slice(0, 3).map((q) => {
398
+ const n = nearest(q);
399
+ return n
400
+ ? `${abbrev(q, 40)} [unmatched; nearest recorded: ${abbrev(n, 40)}]`
401
+ : `${abbrev(q, 40)} [absent from the ledger]`;
402
+ });
403
+ return fail(`connotation_query_unrecorded:${parts.join(",")}${dropped.length > 3 ? ` (+${dropped.length - 3} more)` : ""}`);
404
+ }
363
405
  if (spec?.connotation?.disposition_required === true) {
364
406
  const recorded = parsePrRiskResults(ledgerRaw);
365
407
  const form = dispositionForm(dirname(p), spec?.connotation?.dispositions_path);
@@ -2249,6 +2291,40 @@ export const validators = {
2249
2291
  return fail(`intake_ask_unanswered:${asks.length - answered}:of:${asks.length}`);
2250
2292
  }
2251
2293
  } catch { /* no sidecar / unreadable — legacy run, gate off */ }
2294
+
2295
+ // ── A TWO-FORM MATTER OWES A READ PER FORM ────────────────────────────────────────────────────
2296
+ //
2297
+ // Synthesis is told to reason each ratified form through the framework and to say which conflicts
2298
+ // move between them — or to state in one line that the reads are the same for both. Until this,
2299
+ // nothing enforced it: the arms on that dictation match the wording, so they red when the prose
2300
+ // moves and cannot red when a model ignores the ask. This is the deterministic floor under it.
2301
+ //
2302
+ // GATED ON THE FRAME HAVING FROZEN MORE THAN ONE FORM, which is why it can exist at all. A run with
2303
+ // no frame, an archived or replayed run whose accepted call predates the field, and every ordinary
2304
+ // single-form run reach `forms.length > 1` as false and leave here untouched — so every one of them
2305
+ // re-verifies byte-identically. Same shape as the intake-asks gate above, and the same reason.
2306
+ //
2307
+ // WHAT IT ASKS FOR IS THE CHEAPEST HONEST THING: each form NAMED in the narrative, or the
2308
+ // stated-alike line. It does not judge the reasoning — a floor that tried would be a worse version
2309
+ // of the refutation pass, which stays the semantic backstop. It judges that the question was
2310
+ // answered at all, which is exactly what was missing: a narrative that never mentions the second
2311
+ // form is indistinguishable from one that examined it and found nothing, and a client who ratified
2312
+ // two forms is choosing between them.
2313
+ try {
2314
+ const forms = frameRatifiedForms(dirname(p));
2315
+ if (forms.length > 1) {
2316
+ const text = String(c ?? "");
2317
+ // The alternative the dictation offers, matched on its own terms rather than by a fixed
2318
+ // sentence: a narrative may state the reads are alike instead of splitting them.
2319
+ const alike = /\bthe reads are the same for both forms\b/i.test(text);
2320
+ if (!alike) {
2321
+ const unnamed = forms.filter((f) => !text.toLowerCase().includes(f.toLowerCase()));
2322
+ if (unnamed.length)
2323
+ return fail(`ratified_form_unread:${unnamed.length}:of:${forms.length}:${abbrev(unnamed[0], 60)}`);
2324
+ }
2325
+ }
2326
+ } catch { /* no frame / unreadable — gate off, exactly as above */ }
2327
+
2252
2328
  // V4-4 item 6 — narrative coverage contract (code-checked, conditional on the closure receipt):
2253
2329
  // a marketplace coverage gap may be stated ONLY as an attempted-and-unreachable fact, never as
2254
2330
  // "commission a re-run" work for a human — the driver already closed or proved-unclosable every
@@ -1,5 +1,24 @@
1
1
  # trademark-artifacts-mcp
2
2
 
3
+ ## 0.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - f96c089: Fixed: A clearance for a new client can be ordered through an assistant connector without setting up a company first.
8
+ - 56a760f: Fixed: Removing somebody's access now ends their assistant's connection on its next request. Until now an assistant that had already connected kept the access it started with for up to half an hour.
9
+ - c05e0a7: Fixed: Ask your assistant for a client's recent searches by the client's name and it finds them. Every search in the list now says which client it was for, and which project. A search whose own record cannot be read says so, instead of appearing to belong to nobody.
10
+ - d9798db: Fixed: A clearance that stops now always records that its notice is still owed, so a failure cannot be passed over as already handled.
11
+ - 240673c: Fixed: Sending an access key to the engine's network address now gets a refusal that says so. It reports that the address takes an identity from the sign-in proxy and never a key.
12
+
13
+ Fixed: That refusal also names the local door where a key is accepted. Before, it reported only a missing sign-in assertion, which sent operators to the wrong configuration.
14
+
15
+ For operators: A program on the same machine can now reach the engine's local key door without setting a host name for it. The local door no longer applies a browser protection that only a network address needs.
16
+ - f96c089: New: The free preview of a search lists, for each territory ordered, which registers legally bind it.
17
+
18
+ ## 0.3.1-beta.4
19
+
20
+ No changes in this release.
21
+
3
22
  ## 0.3.1-beta.3
4
23
 
5
24
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trademark-artifacts-mcp",
3
- "version": "0.3.1-beta.3",
3
+ "version": "0.3.1",
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.1-beta.3",
4
+ "version": "0.3.1",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
7
7
  "type": "git",