clearotron 0.3.2-beta.0 → 0.3.2-beta.2

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.
@@ -1960,8 +1960,20 @@ input.filter {
1960
1960
  color: var(--text-strong); white-space: nowrap;
1961
1961
  }
1962
1962
  .home2-band-label.faint { color: var(--text-faint); }
1963
- .home2-band-count { font-size: 11.5px; color: var(--text-faint); }
1963
+ /* THE SAME SIZE AND COLOUR AS THE CAPACITY LINE OPPOSITE, because the two are one sentence read across
1964
+ the band: what is happening on the left, how much can happen at once on the right. It carried `mono`
1965
+ while it was a single total, which read as a machine's counter rather than as a statement. `nowrap`
1966
+ because it is now a breakdown — "2 running · 1 paused · 2 queued" — and breaking it mid-clause puts
1967
+ a number on its own line. */
1968
+ .home2-band-count { font-size: 11.5px; color: var(--text-faint); white-space: nowrap; }
1964
1969
  .home2-band-rule { height: 1px; background: var(--border-hairline); }
1970
+
1971
+ /* A RUNNING RUN AS A ROW. The queue's grid, with the two columns a running row has and a waiting one
1972
+ does not. Declared as a modifier rather than a new grid so the narrow-screen rule that hides columns
1973
+ keeps one definition for both. */
1974
+ .home2-runrows { display: flex; flex-direction: column; gap: 1px; margin-bottom: 18px; }
1975
+ .home2-runrow .home2-qstep { font-size: 11.5px; color: var(--text-muted); white-space: nowrap; }
1976
+ .home2-runrow .home2-qreason { font-size: 11.5px; color: var(--text-muted); white-space: nowrap; }
1965
1977
  .home2-band-note {
1966
1978
  font-size: 11.5px; color: var(--text-faint); text-align: right;
1967
1979
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
@@ -2045,6 +2057,10 @@ input.filter {
2045
2057
  display: flex; align-items: center; gap: 10px;
2046
2058
  }
2047
2059
  .home2-reason { flex: 1; min-width: 0; font-size: 12.5px; color: var(--text-muted); }
2060
+ /* QUIETER THAN THE ELAPSED TIME IT FOLLOWS. The elapsed line is this run's own fact; the quote beside it
2061
+ is the standing one for its pipeline, and a reader who wants to know how long this has been going
2062
+ should meet that first. Same line, one step back in the hierarchy. */
2063
+ .home2-expect { font-size: 12.5px; color: var(--text-faint); white-space: nowrap; }
2048
2064
  .home2-reason.paused { color: var(--tone-medium); }
2049
2065
  .home2-reason.failed { color: var(--tone-high); }
2050
2066
  .home2-stop, .home2-cancel, .home2-ack {
@@ -49,8 +49,8 @@
49
49
  -->
50
50
  <link rel="preconnect" href="https://api.fontshare.com" crossorigin />
51
51
  <link href="https://api.fontshare.com/v2/css?f[]=satoshi@400,500,700,900&display=swap" rel="stylesheet" />
52
- <script type="module" crossorigin src="/portal/assets/index-ChIQsMYp.js"></script>
53
- <link rel="stylesheet" crossorigin href="/portal/assets/index-DBIs21e4.css">
52
+ <script type="module" crossorigin src="/portal/assets/index-BsbasHjM.js"></script>
53
+ <link rel="stylesheet" crossorigin href="/portal/assets/index-DNQpLYZF.css">
54
54
  </head>
55
55
  <body>
56
56
  <div id="root"></div>
@@ -2,7 +2,7 @@
2
2
  "name": "portal-ui",
3
3
  "private": true,
4
4
  "type": "module",
5
- "version": "0.3.2-beta.0",
5
+ "version": "0.3.2-beta.2",
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,13 @@
1
1
  # trademark-oauth-mcp-bridge
2
2
 
3
+ ## 0.3.2-beta.2
4
+
5
+ No changes in this release.
6
+
7
+ ## 0.3.2-beta.1
8
+
9
+ No changes in this release.
10
+
3
11
  ## 0.3.2-beta.0
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.0",
3
+ "version": "0.3.2-beta.2",
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).",
@@ -576,7 +576,35 @@ export function toSignaParams(p = {}) {
576
576
  const mode = String(p.match_mode ?? "").trim();
577
577
  if (mode === "exact" || mode === "phonetic" || mode === "prefix") out.strategies = [mode];
578
578
  else if (mode === "starts_with" || mode === "ends_with" || mode === "contains") out.match = mode;
579
- else if (!mode && !p.match && !(Array.isArray(p.strategies) && p.strategies.length)) {
579
+ // ── `default` IS THE COUNT LANE'S WORD FOR THE SAME UNANCHORED QUERY THE PLAN LANE CALLS `{}` ────
580
+ //
581
+ // THE DEFECT. The count lane hands `match_mode: "default"` straight through — that is the word
582
+ // `COUNT_PREDICATES` gives the CONTAINING predicate, the one whose figure a client reads under
583
+ // "Filings containing the name". `default` matched none of the branches here, so neither request
584
+ // shape was selected, and `buildSearchRequest`'s else branch sent `strategies: ["exact"]`. The
585
+ // containing column therefore printed an exact count, on the axis that most signals how crowded a
586
+ // field is, and a client was told the field was less busy than it is in a number the report states
587
+ // as fact. Nothing failed and nothing was logged: a narrower query answers perfectly well, it just
588
+ // answers a different question.
589
+ //
590
+ // The branch below already does exactly this for the plan lane, which reaches here with NO mode at
591
+ // all. The two lanes mean the same thing by different words; they now take the same shape.
592
+ else if (mode === "default") {
593
+ if (typeof out.query === "string" && !out.query.includes("*")) out.match = "contains";
594
+ }
595
+ // ── AN UNRECOGNISED MODE REFUSES; IT DOES NOT QUIETLY BECOME A NARROWER SEARCH ──────────────────
596
+ //
597
+ // THE CLASS, and it has now bitten twice. This function's own header records `starts_with` falling
598
+ // through to a plain exact search — "a NARROWER query than the plan asked for, answering as though it
599
+ // were the one requested" — and `default` has just done the same on a client-facing figure. Both were
600
+ // fixed by adding the missing word. Adding words one defect at a time leaves the next unmapped mode
601
+ // to do it a third time, silently, in whichever direction happens to be wrong.
602
+ //
603
+ // So a mode this provider cannot express is now NAMED rather than approximated. The caller turns it
604
+ // into a refused cell, and a refused cell is a disclosed gap a reader can see; a wrong number is not
605
+ // visible at all. This can only fire where a number would otherwise have been silently wrong.
606
+ else if (mode) out.unsupported_match_mode = mode;
607
+ if (!mode && !p.match && !(Array.isArray(p.strategies) && p.strategies.length)) {
580
608
  // ── THE `default` PREDICATE, WHICH JUST MADE EXECUTABLE ────────────────────────────────
581
609
  // `planPredicateParams` returns {} for the plan's `default` predicate — no match_mode at all —
582
610
  // and this branch is the only thing standing between that and `strategies: ["exact"]`. With
@@ -29,7 +29,7 @@
29
29
  // _driver/run.jsonl the event log
30
30
  // _driver/stage-inputs/ what each stage was handed
31
31
  // _history/ pre-reopen snapshots
32
- // Dropping the telemetry drops `meta.tokens` (driver/publish/index.mjs:971 — the only consumer of
32
+ // Dropping the telemetry drops `meta.tokens` (driver/publish/index.mjs:1136 rollupTokens — the only consumer of
33
33
  // rollupTokens). That is the one difference step 5 is told to expect, and it says so out loud rather than
34
34
  // normalising it away in silence.
35
35
  //
@@ -55,26 +55,26 @@ const REPO = join(dirname(fileURLToPath(import.meta.url)), "..");
55
55
  // Every entry cites the read that puts it here. `required` means publish cannot render without it.
56
56
  const FROZEN_FILES = [
57
57
  // publish/index.mjs — parseReport(reportMd), the one mandatory input
58
- { path: "report.md", required: true, why: "publish/index.mjs:649 parseReport" },
59
- { path: "audit.md", why: "publish/index.mjs:835 audit workbook source" },
60
- { path: "findings.json", why: "publish/index.mjs:555 the per-finding machine contract" },
61
- { path: "status.json", why: "publish/index.mjs:730,905 machine ledger note + markName" },
62
- { path: "case-law-findings.md", why: "publish/index.mjs:660 case-law section" },
63
- { path: "common-law-grid.json", why: "publish/index.mjs:788 common-law coverage" },
58
+ { path: "report.md", required: true, why: "publish/index.mjs:650 parseReport" },
59
+ { path: "audit.md", why: "publish/index.mjs:1022 auditMd, the audit workbook source" },
60
+ { path: "findings.json", why: "publish/index.mjs:715 readStore, the per-finding machine contract" },
61
+ { path: "status.json", why: "publish/index.mjs:913 machineLedgerNote + markName" },
62
+ { path: "case-law-findings.md", why: "publish/index.mjs:849 clPath, the case-law section" },
63
+ { path: "common-law-grid.json", why: "publish/index.mjs:973 commonLawJoinedTerms, common-law coverage" },
64
64
  // publish/index.mjs — the _driver sidecars it reads by name
65
- { path: "_driver/receipts.json", why: "publish/index.mjs:592" },
66
- { path: "_driver/senior-rights.json", why: "publish/index.mjs:599" },
67
- { path: "_driver/verdict.json", why: "publish/index.mjs:604" },
68
- { path: "_driver/framework.json", why: "publish/index.mjs:608 the bands the run was rated under" },
69
- { path: "_driver/register-plan.json", why: "publish/index.mjs:634" },
70
- { path: "_driver/instructed-scope.json", why: "publish/index.mjs:641 fallback for register-plan" },
71
- { path: "_driver/enforcer-signals.json", why: "publish/index.mjs:672" },
72
- { path: "_driver/predelivery-lint.json", why: "publish/index.mjs:699,713" },
73
- { path: "_driver/escalation-state.json", why: "publish/index.mjs:714" },
74
- { path: "_driver/reasoning-integrity.json", why: "publish/index.mjs:715" },
75
- { path: "_driver/corrections-state.json", why: "publish/index.mjs:716" },
76
- { path: "_driver/search-policy.json", why: "publish/index.mjs:768,806 level + stage label" },
77
- { path: "_driver/profile.json", why: "publish/index.mjs:920 + report-registry.mjs:42 customer key" },
65
+ { path: "_driver/receipts.json", why: "publish/index.mjs:761 fetchReceipts" },
66
+ { path: "_driver/senior-rights.json", why: "publish/index.mjs:787 seniorRights" },
67
+ { path: "_driver/verdict.json", why: "publish/index.mjs:792 verdictInfo" },
68
+ { path: "_driver/framework.json", why: "publish/index.mjs, the frozen band vocabulary the run was rated under" },
69
+ { path: "_driver/register-plan.json", why: "publish/index.mjs:820 scopeBasis" },
70
+ { path: "_driver/instructed-scope.json", why: "publish/index.mjs:821 searchedJurisdictions, the fallback for register-plan" },
71
+ { path: "_driver/enforcer-signals.json", why: "publish/index.mjs:861 esPath" },
72
+ { path: "_driver/predelivery-lint.json", why: "publish/index.mjs:170 lintSink" },
73
+ { path: "_driver/escalation-state.json", why: "publish/index.mjs:171 escSink" },
74
+ { path: "_driver/reasoning-integrity.json", why: "publish/index.mjs:898 integritySink" },
75
+ { path: "_driver/corrections-state.json", why: "publish/index.mjs:172 correctionsSink" },
76
+ { path: "_driver/search-policy.json", why: "publish/index.mjs:955 searchPolicy, level + stage label" },
77
+ { path: "_driver/profile.json", why: "publish/index.mjs reads the frozen profile; report-registry.mjs:42 republishRun, customer key" },
78
78
  ];
79
79
 
80
80
  // ── THE KNOCKOUT LANE IS A DIFFERENT WORKSPACE, AND report.md IS NOT IN IT ─
@@ -91,22 +91,22 @@ const KNOCKOUT_FILES = [
91
91
  { path: "knockout-assessment.md", why: "the merged prose the lane writes (gateway.mjs:184)" },
92
92
  { path: "knockout-frame.md", why: "the batch scope note (gateway.mjs:65)" },
93
93
  { path: "email-body.md", why: "the delivery prose the lane writes beside the assessment" },
94
- { path: "status.json", why: "publish/index.mjs:730,905 machine ledger note + markName" },
95
- { path: "audit.md", why: "publish/index.mjs:835 audit workbook source" },
94
+ { path: "status.json", why: "publish/index.mjs:913 machineLedgerNote + markName" },
95
+ { path: "audit.md", why: "publish/index.mjs:1022 auditMd, the audit workbook source" },
96
96
  // The _driver sidecars publishKnockout reads by name. framework.json is REQUIRED and says so at its
97
97
  // call site: a knockout re-rendered under today's bands would silently restate its verdict.
98
98
  { path: "_driver/framework.json", required: true, why: "report-registry.mjs:68 the bands it was rated under" },
99
99
  { path: "_driver/search-policy.json", why: "report-registry.mjs:71 level + stage label" },
100
100
  { path: "_driver/profile.json", why: "report-registry.mjs:72 customer key and the delivery overlay" },
101
- { path: "_driver/verdict.json", why: "publish/index.mjs:604" },
102
- { path: "_driver/receipts.json", why: "publish/index.mjs:592" },
101
+ { path: "_driver/verdict.json", why: "publish/index.mjs:792 verdictInfo" },
102
+ { path: "_driver/receipts.json", why: "publish/index.mjs:761 fetchReceipts" },
103
103
  // THE COUNT SIDECARS, AND THE PROOF IS WHAT FOUND THEM. Without register-counts.json the republished
104
104
  // meta carries `registerCounts: undefined` where the source carried the provider, the taken-at stamp
105
105
  // and the per-mark counts — so the workbook's Register column and every counted figure in the
106
106
  // knockout report render empty (publish/knockout.mjs:140-155). Named by stages-knockout.mjs:32,41.
107
107
  { path: "_driver/register-counts.json", why: "publish/knockout.mjs:140-155 counted figures + the Register column" },
108
108
  { path: "_driver/register-records.json", why: "stages-knockout.mjs:41 the terms behind the close-variation axis" },
109
- { path: "_driver/instructed-scope.json", why: "publish/index.mjs:641 fallback for register-plan" },
109
+ { path: "_driver/instructed-scope.json", why: "publish/index.mjs:821 searchedJurisdictions, the fallback for register-plan" },
110
110
  ];
111
111
 
112
112
  /** The allowlist for a template. One place, so a new template cannot half-exist. */
@@ -161,8 +161,8 @@ const SCRUB = [
161
161
  // what varies and why, and step 5 prints them — a normalisation nobody can see is a normalisation that
162
162
  // hides the next real difference.
163
163
  const VOLATILE = [
164
- { id: "issued", re: /\d{4}-\d{2}-\d{2} · \d{2}:\d{2} [A-Z]{2,5}/g, sub: "<issued>", why: "publish/index.mjs:520 generation stamp, firm locale" },
165
- { id: "iso-timestamp", re: /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z/g, sub: "<ts>", why: "publish/index.mjs:665 asOf / :1067 issuedAt" },
164
+ { id: "issued", re: /\d{4}-\d{2}-\d{2} · \d{2}:\d{2} [A-Z]{2,5}/g, sub: "<issued>", why: "publish/index.mjs, the generation stamp in the firm locale" },
165
+ { id: "iso-timestamp", re: /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z/g, sub: "<ts>", why: "publish/index.mjs:666 asOf" },
166
166
  ];
167
167
 
168
168
  // ── REWRITES — what is CHANGED on the way out, as opposed to what is refused ───────────────────────
@@ -0,0 +1,111 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
3
+ //
4
+ // release-pre-gate.mjs — let a stable cut reach the version script when every note has already been
5
+ // consumed by a pre-release.
6
+ //
7
+ // THE PROBLEM THIS EXISTS FOR, MEASURED. `changesets/action` decides whether to run `version-script` by
8
+ // counting the release notes it can see, and it does that ONCE, at the top of the run, before it touches
9
+ // the working tree. Its reader is `readChangesetState`:
10
+ //
11
+ // let preState = await readPreState(cwd);
12
+ // let changesets = await readChangesets(cwd);
13
+ // if (preState !== undefined && preState.mode === "pre") {
14
+ // return { preState, changesets: changesets.filter((c) => !c.id.startsWith("pre/")) };
15
+ // }
16
+ // return { preState: undefined, changesets };
17
+ //
18
+ // `readChangesets` DOES enumerate `.changeset/pre/`, giving each note an id beginning `pre/`. The filter
19
+ // that hides them applies only while the tree says `mode: "pre"`.
20
+ //
21
+ // A pre-release consumes a note by MOVING it into `.changeset/pre/`, where it waits so the eventual
22
+ // stable can list every change since the last stable. So the ordinary state after cutting a beta is: no
23
+ // notes at the top level, every note under `pre/`, and the flag still saying `pre`. In that state the
24
+ // action counts zero, skips the version script, emits no pull request number, and the stable cut refuses
25
+ // with "Nothing to cut" — a refusal about the wrong thing, because the tree holds a whole release.
26
+ //
27
+ // WHAT THIS DOES, AND WHY IT IS NOT A TRICK. It writes the channel the dispatch asked for into the file
28
+ // the action reads, before the action reads it. A stable cut IS a tree leaving pre-release mode, and the
29
+ // notes under `pre/` ARE the notes that stable will carry; saying so is a true statement about this cut,
30
+ // not a device to get past a check. `release-version.mjs` already states the same principle for the
31
+ // other reader: "the mode a cut needs is a fact about the channel asked for, not about what the last cut
32
+ // happened to do." There are two readers of that fact and they are reached separately.
33
+ //
34
+ // THE WRITE IS DISCARDED, ON PURPOSE, AND THAT IS THE POINT. The action's own first act inside
35
+ // `runVersion` is `git checkout changeset-release/main` followed by `git reset --hard <the run's
36
+ // commit>`, which puts `pre.json` back to `pre`. The version script then runs in that rebuilt tree, finds
37
+ // the tree still in pre mode, and performs the real transition itself with `changeset pre exit` — which
38
+ // is where the transition has to happen, and the reason it was moved there. So this changes what the
39
+ // action COUNTS and nothing about what the cut DOES. Nothing here versions anything, and a tree this
40
+ // touched still produces the same version it would have produced had the gate never been in the way.
41
+ //
42
+ // WHAT IT REFUSES TO DO. It is silent on a beta, on a tree that is not in pre mode, on a tree whose notes
43
+ // are at the top level where the action can already see them, and on a tree with no notes anywhere. That
44
+ // last one matters most: a cut with genuinely nothing to cut must still refuse, and it still does,
45
+ // because this writes nothing when `.changeset/pre/` is empty.
46
+ import { readFileSync, writeFileSync, existsSync, readdirSync } from "node:fs";
47
+ import { join, dirname } from "node:path";
48
+ import { fileURLToPath } from "node:url";
49
+ import { isEntrypoint } from "../shared/is-entrypoint.mjs";
50
+
51
+ const ROOT = join(dirname(dirname(fileURLToPath(import.meta.url))));
52
+
53
+ /** A note is a `.md` file that is not the directory's own README. */
54
+ export function countNotes(dir) {
55
+ if (!existsSync(dir)) return 0;
56
+ return readdirSync(dir).filter((f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md").length;
57
+ }
58
+
59
+ /**
60
+ * PURE. Whether this cut has to restate its channel for the action's counter, and nothing else.
61
+ *
62
+ * `null` means leave the tree alone, and every `null` here is a case the action already reads correctly
63
+ * — including the two that must keep refusing. Only the one state the action cannot see returns a write.
64
+ *
65
+ * Kept separate from the file it writes so the decision can be driven against every member of the class
66
+ * rather than only against the tree that happens to be checked out.
67
+ */
68
+ export function preGateDecision({ cut, mode, topLevelNotes, preNotes }) {
69
+ // A beta never restates anything. A beta with no pending notes MUST still refuse, and that refusal is
70
+ // this function returning null.
71
+ if (cut !== "stable") return null;
72
+ // Not in pre mode: the action's filter is not engaged, so whatever is there is already counted.
73
+ if (mode !== "pre") return null;
74
+ // The action can see these. Touching the flag here would change the pull request's title for a cut
75
+ // that never needed help.
76
+ if (topLevelNotes > 0) return null;
77
+ // Nothing anywhere. "Nothing to cut" is then the truth, and the refusal downstream is correct.
78
+ if (preNotes === 0) return null;
79
+ return { mode: "exit" };
80
+ }
81
+
82
+ export function main(root = ROOT, argv = process.argv.slice(2)) {
83
+ const cut = (argv.find((a) => a.startsWith("--cut=")) ?? "").slice("--cut=".length);
84
+ // `--root` exists so this can be driven against a built tree rather than only against the checkout it
85
+ // happens to live in. The workflow never passes it: there the script sits in the repository being cut,
86
+ // which is what ROOT already means.
87
+ const rootFlag = argv.find((a) => a.startsWith("--root="));
88
+ if (rootFlag) root = rootFlag.slice("--root=".length);
89
+ const preJson = join(root, ".changeset", "pre.json");
90
+ const flag = existsSync(preJson) ? JSON.parse(readFileSync(preJson, "utf8")) : null;
91
+ const mode = flag?.mode ?? "none";
92
+ const topLevelNotes = countNotes(join(root, ".changeset"));
93
+ const preNotes = countNotes(join(root, ".changeset", "pre"));
94
+
95
+ const decision = preGateDecision({ cut, mode, topLevelNotes, preNotes });
96
+ const state = `cut=${cut || "none"}, mode=${mode}, ${topLevelNotes} note(s) pending, ${preNotes} already consumed by the pre-release`;
97
+ if (!decision) {
98
+ // SAYS SO EITHER WAY. A step that is silent when it does nothing cannot be told from a step that did
99
+ // not run, and this one is skipped far more often than it acts.
100
+ console.log(`release-pre-gate: ${state} — leaving the flag alone.`);
101
+ return 0;
102
+ }
103
+ writeFileSync(preJson, `${JSON.stringify({ ...flag, ...decision }, null, 2)}\n`);
104
+ console.log(`release-pre-gate: ${state} — the notes this stable carries are all under .changeset/pre/,`
105
+ + " where the action does not count them while the flag says pre. Writing mode=exit so it counts them."
106
+ + " The version script performs the real transition after the action rebuilds the tree; this write is"
107
+ + " discarded by that rebuild and changes no version.");
108
+ return 0;
109
+ }
110
+
111
+ if (isEntrypoint(import.meta.url)) process.exit(main());