clearotron 0.3.0-beta.6 → 0.3.0-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/.env.example +4 -3
- package/bin/start.mjs +37 -3
- package/build-info.json +2 -2
- package/demo/full-country-search/run/status.json +1 -1
- package/demo/global-preliminary-search/run/status.json +1 -1
- package/demo/knockout-search/run/email-body.md +1 -1
- package/demo/knockout-search/run/status.json +2 -2
- package/demo/multi-country-focus-search/run/status.json +1 -1
- package/docs/architecture/04-configuration-reference.md +1 -1
- package/driver/CHANGELOG.md +8 -0
- package/driver/demo-container.mjs +42 -7
- package/driver/package.json +1 -1
- package/driver/suite-census.json +17 -5
- package/mcp-server/CHANGELOG.md +4 -0
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/shared/brand.mjs +7 -3
- package/shared/permanent-install.mjs +37 -1
package/.env.example
CHANGED
|
@@ -561,8 +561,9 @@ CLEAROTRON_DEMO_PROFILES=
|
|
|
561
561
|
# Your organisation's name. `npm run setup` asks for it (and, on a local install, not for a sign-in
|
|
562
562
|
# address) and writes it here, quoted, so a name holding a `#` survives. The first `clearotron start` files it as the first
|
|
563
563
|
# organisation in the grants file (CLEAROTRON_ACCESS_FILE) when that file holds none; from then on the
|
|
564
|
-
# grants file holds the name
|
|
565
|
-
# organisation
|
|
566
|
-
#
|
|
564
|
+
# grants file holds the name and renaming is an edit there. The portal also reads it, to name the
|
|
565
|
+
# organisation on its top bar and on its sign-in refusal page. Unset or empty, no organisation is
|
|
566
|
+
# invented. `clearotron start --organisation <name>` supplies it for one start, and a demo never reads
|
|
567
|
+
# it. Read by bin/start.mjs and shared/brand.mjs.
|
|
567
568
|
# effect: deployment
|
|
568
569
|
CLEAROTRON_ORGANISATION_NAME=
|
package/bin/start.mjs
CHANGED
|
@@ -111,6 +111,7 @@ async function runTables() {
|
|
|
111
111
|
import { spawn, spawnSync, execFileSync } from "node:child_process";
|
|
112
112
|
import { storeInRepo, storeOutsideRepoMessage, storeCommitRefusal } from "../shared/store-in-repo.mjs"; //
|
|
113
113
|
import { stdioConnectOffer } from "../shared/stdio-connect.mjs";
|
|
114
|
+
import { demoProgramPlan } from "../shared/permanent-install.mjs"; // — a demo from npx keeps its own copy
|
|
114
115
|
import { mergeEnvFile } from "../shared/env-file-merge.mjs";
|
|
115
116
|
import { mcpOriginFor } from "../shared/lane-address.mjs"; // — one author for the origin
|
|
116
117
|
import { SERVER_INSTALL_SET, unitsToRestartOnRefresh, unitHealthVerdict } from "../shared/server-units.mjs"; // — one authority, two callers
|
|
@@ -756,6 +757,10 @@ export function childEnv({ ports, paths, user, portalSecret, tokenSecret, opsTok
|
|
|
756
757
|
// already wrong for a process that is not the portal.
|
|
757
758
|
...(demo ? {
|
|
758
759
|
CLEAROTRON_DEMO: "1",
|
|
760
|
+
// A DEMO NAMES NO ORGANISATION. The portal reads this to name the one running the install, on its
|
|
761
|
+
// top bar and its sign-in refusal page, and an exported value is the reader's real organisation.
|
|
762
|
+
// The portal's alone: no door names an organisation, and the doors stay as a live install's.
|
|
763
|
+
CLEAROTRON_ORGANISATION_NAME: "",
|
|
759
764
|
// AND THE SIGN-IN CREDENTIAL LIVES IN THE DEMO'S OWN BASE. Without this it defaults to
|
|
760
765
|
// ~/.cordillera/portal-local-credential.json — shared with every install on the box — and the
|
|
761
766
|
// demo then inherits a digest minted for somebody else's address: the portal prints "the
|
|
@@ -1389,8 +1394,9 @@ if (isMain) {
|
|
|
1389
1394
|
const { publishSource, seedDemoRuns } = await import("../driver/demo-container.mjs");
|
|
1390
1395
|
const seed = await seedPool({ pool: paths.pool, examplesDir: publishSource(join(REPO, "demo"), { repoRoot: REPO }), republish: republishRun });
|
|
1391
1396
|
// AND AS RUNS, so the assistant this demo's connect line wires has them to list, brief and open. Under
|
|
1392
|
-
// the demo's own workspace only: nothing of it reaches an install started afterwards.
|
|
1393
|
-
|
|
1397
|
+
// the demo's own workspace only: nothing of it reaches an install started afterwards. Their report
|
|
1398
|
+
// links are stamped with this portal's address, the one the Open line prints.
|
|
1399
|
+
const runs = seedDemoRuns({ workspace: paths.workspace, examplesDir: join(REPO, "demo"), portalOrigin: `http://${HOST}:${ports.portal}` });
|
|
1394
1400
|
if (runs.seeded.length) say(` runs ${runs.seeded.length} sample run(s) your assistant can list, brief and open`);
|
|
1395
1401
|
// WHAT WAS ALREADY THERE IS SAID TOO. This branch used to run only when the pool
|
|
1396
1402
|
// was empty; it now tops a stale pool up to the package's set, so "seeded 1" on an upgrade is a fact
|
|
@@ -1451,6 +1457,33 @@ if (isMain) {
|
|
|
1451
1457
|
// the file the verb resets is the file the portal reads. A demo keeps its own, by layout (childEnv).
|
|
1452
1458
|
const { installCredential } = await import("../driver/portal-local-auth.mjs");
|
|
1453
1459
|
const signIn = DEMO ? null : installCredential({ base: paths.base, env: process.env, firstStart: firstStartOfThisInstall });
|
|
1460
|
+
// ── A DEMO RUN FROM NPX KEEPS ITS OWN COPY OF THE PROGRAM ───────────────────────────────────────────
|
|
1461
|
+
//
|
|
1462
|
+
// Its connect line launched the connector from npm's cache, which npm deletes when it cleans up, so an
|
|
1463
|
+
// assistant registered with it lost the demo without a word (measured on a published beta, 2026-09-11).
|
|
1464
|
+
// The same version goes into `<base>/program` (shared/permanent-install.mjs), BEFORE the services start
|
|
1465
|
+
// so the portal's connect rows name it too, and only when it is not there already. It never stops the
|
|
1466
|
+
// demo: if npm fails, the demo runs as before and says the line will not survive a cache clean.
|
|
1467
|
+
let demoProgramRoot = null;
|
|
1468
|
+
if (DEMO) {
|
|
1469
|
+
const plan = demoProgramPlan({ base: paths.base });
|
|
1470
|
+
if (plan?.current) demoProgramRoot = plan.root;
|
|
1471
|
+
else if (plan && !plan.skip) {
|
|
1472
|
+
say(` program copying clearotron ${plan.version} into ${plan.prefix}, so your assistant's connection survives npm cleaning its cache`);
|
|
1473
|
+
// The npm that launched this, when npm says which: no second npm is guessed at.
|
|
1474
|
+
const npmCli = process.env.npm_execpath;
|
|
1475
|
+
const r = npmCli && existsSync(npmCli)
|
|
1476
|
+
? spawnSync(process.execPath, [npmCli, ...plan.npmArgs], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8", timeout: 180_000 })
|
|
1477
|
+
: spawnSync("npm", plan.npmArgs, { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8", timeout: 180_000 });
|
|
1478
|
+
if (r.status === 0 && existsSync(join(plan.root, "mcp-server", "server.mjs"))) demoProgramRoot = plan.root;
|
|
1479
|
+
else {
|
|
1480
|
+
const why = r.error ? r.error.message : String(r.stderr ?? "").trim().split("\n").pop() || `npm exited ${r.status}`;
|
|
1481
|
+
say(` program could not be copied (${why}). The connect line below runs from npm's temporary cache,`);
|
|
1482
|
+
say(" so it stops working when npm cleans that cache; start the demo again to retry.");
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1454
1487
|
const envs = childEnv({ ports, paths, user, portalSecret, tokenSecret, opsToken,
|
|
1455
1488
|
localWorker: wantWorker, demo: DEMO, clientFence: declaredFence || null,
|
|
1456
1489
|
credential: signIn && signIn.source !== "shared" ? signIn.path : null });
|
|
@@ -2233,7 +2266,8 @@ if (isMain) {
|
|
|
2233
2266
|
// and a line of instruction with more than one author drifts silently.
|
|
2234
2267
|
// THE WORKSPACE AND POOL THE SERVICES WERE HANDED, not this process's environment: a demo reads no env
|
|
2235
2268
|
// file, so its own line named no workspace and the connector fell back to the real install's.
|
|
2236
|
-
|
|
2269
|
+
// A demo run from npx names its own copy of the program, which a cache clean does not remove.
|
|
2270
|
+
const connect = stdioConnectOffer({ workDir: paths.workspace, reportsDir: paths.pool, ...(demoProgramRoot ? { installRoot: demoProgramRoot } : {}) });
|
|
2237
2271
|
say(" Connect your assistant to this install — one line, no address and no sign-in:");
|
|
2238
2272
|
say("");
|
|
2239
2273
|
say(` ${connect.command}`);
|
package/build-info.json
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"stepTotal": 9,
|
|
28
28
|
"lastStage": "publish",
|
|
29
29
|
"verdict": "CONDITIONAL",
|
|
30
|
-
"url": "
|
|
30
|
+
"url": "/portal/report/tmpdemo2014fullcountrysearch-venqori-2026-09-03-sample-capture/",
|
|
31
31
|
"failedStage": null,
|
|
32
32
|
"reason": null,
|
|
33
33
|
"deliveredAt": "2026-09-03T18:06:17.281Z",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"stepTotal": 9,
|
|
28
28
|
"lastStage": "publish",
|
|
29
29
|
"verdict": "CONDITIONAL",
|
|
30
|
-
"url": "
|
|
30
|
+
"url": "/portal/report/tmpdemo2014globalpreliminarysearch-venqori-2026-09-02-sample-capture/",
|
|
31
31
|
"failedStage": null,
|
|
32
32
|
"reason": null,
|
|
33
33
|
"deliveredAt": "2026-09-02T17:33:15.625Z",
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
<p style="font-family:Calibri,sans-serif;font-size:11pt">Knockout trademark review — DEMO-2014-knockout-search: <b>1 mark</b>, worst band <b style="color:#2f55a4">Low</b>. <a href="
|
|
1
|
+
<p style="font-family:Calibri,sans-serif;font-size:11pt">Knockout trademark review — DEMO-2014-knockout-search: <b>1 mark</b>, worst band <b style="color:#2f55a4">Low</b>. <a href="/portal/report/tmpdemo2014knockoutsearch-venqori-2026-09-02-sample-capture/">Open the full report</a> · <a href="/portal/report/tmpdemo2014knockoutsearch-venqori-2026-09-02-sample-capture/audit.xlsx">audit workbook</a>.</p>
|
|
2
2
|
<ul style="font-family:Calibri,sans-serif;font-size:11pt"><li><b>VENQORI</b>: <b style="color:#2f55a4">Low</b> — Classes 9, 42, 41 — Register filings (classes 9, 42, 41): 0 identical, 0 containing, 0 on close variations.</li></ul>
|
|
3
3
|
<p style="font-family:Calibri,sans-serif;font-size:11pt"><i>Ratings reflect our common law assessment. Register analysis may adjust ratings in either direction.</i></p>
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"stepN": 6,
|
|
34
34
|
"stepTotal": 6,
|
|
35
35
|
"verdict": "Low",
|
|
36
|
-
"url": "
|
|
36
|
+
"url": "/portal/report/tmpdemo2014knockoutsearch-venqori-2026-09-02-sample-capture/",
|
|
37
37
|
"failedStage": null,
|
|
38
38
|
"reason": null,
|
|
39
39
|
"deliveredAt": "2026-09-02T21:46:53.238Z",
|
|
@@ -254,7 +254,7 @@
|
|
|
254
254
|
"reports": [
|
|
255
255
|
{
|
|
256
256
|
"mark": "VENQORI",
|
|
257
|
-
"url": "
|
|
257
|
+
"url": "/portal/report/tmpdemo2014knockoutsearch-venqori-2026-09-02-sample-capture/"
|
|
258
258
|
}
|
|
259
259
|
],
|
|
260
260
|
"sendPending": true,
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"stepTotal": 9,
|
|
28
28
|
"lastStage": "publish",
|
|
29
29
|
"verdict": "CONDITIONAL",
|
|
30
|
-
"url": "
|
|
30
|
+
"url": "/portal/report/tmpdemo2014multicountryfocussearch-venqori-2026-09-02-sample-capture/",
|
|
31
31
|
"failedStage": null,
|
|
32
32
|
"reason": null,
|
|
33
33
|
"deliveredAt": "2026-09-02T19:51:48.801Z",
|
|
@@ -285,7 +285,7 @@ because the rule is about what PRODUCT CODE reads, not about what a run reads.
|
|
|
285
285
|
| `CLEAROTRON_DEMO` | unset | `1` puts this install in the DEMO posture. **Ordering is real**: the four products are listed and orderable, the form, the plan and the confirmation are the product's own, and the confirmation resolves to a finished report that already exists rather than dispatching — no engine turn, no register call, no queue entry, no run directory (ruling 2026-08-31, superseding the greyed-control ruling of the same day). A product the demo carries no finished report for refuses and names which one. It also re-aims two boot warnings written for an operator of a real deployment at the visitor who is not one, from one place (`driver/demo-posture.mjs`). **Set by `npx clearotron demo`, not by an operator** — it is passed explicitly to the two processes that have a reason to know (the portal and the MCP door; the worker is not told, because a demo never queues anything for it to drain), and those run with `CLEAROTRON_NO_ENV_FILE=1`, so a stray `.env` can neither put a live install into demo mode nor take a demo out of one. Anything but the literal `1` is not a demo. Replaces `PORTAL_DEMO`, which named only one of the processes that has to know. |
|
|
286
286
|
| `CLEAROTRON_TEST_FIXTURE_PROFILES` | unset | `1` makes the profile loader return the three suite fixtures, which are refused from every roster otherwise. Set by `scripts/test-run.mjs`, never by an operator; an explicit `includeTestFixtures` argument beats it. Effect class `harness`; the full contract is its row in `.env.example`. |
|
|
287
287
|
| `CLEAROTRON_DEMO_PROFILES` | unset | `1` makes the profile loader return the bundled demo account, which a fresh install does not resolve (ruling 2026-09-08). Set by `clearotron demo`, `start --demo` and the suite runner, never by an operator. The gate is on the bundled layer, so a deployment's own configured store keeps its `demoData` accounts either way. Effect class `harness`; the full contract is its row in `.env.example`. |
|
|
288
|
-
| `CLEAROTRON_ORGANISATION_NAME` | unset | Your organisation's name, written quoted by `npx clearotron install`, which asks for it and not for a sign-in address. The first `clearotron start` files it as the first organisation in the grants file (`CLEAROTRON_ACCESS_FILE`) when that file holds none; from then on the grants file holds the name
|
|
288
|
+
| `CLEAROTRON_ORGANISATION_NAME` | unset | Your organisation's name, written quoted by `npx clearotron install`, which asks for it and not for a sign-in address. The first `clearotron start` files it as the first organisation in the grants file (`CLEAROTRON_ACCESS_FILE`) when that file holds none; from then on the grants file holds the name and renaming is an edit there. The portal also reads it, to name the organisation on its top bar and on its sign-in refusal page. Unset ⇒ no organisation is invented. `clearotron start --organisation <name>` supplies it for one start, and a demo never reads it. Effect class `deployment`; the full contract is its row in `.env.example`. |
|
|
289
289
|
| `PORTAL_LOCAL_CREDENTIAL` | `~/.cordillera/portal-local-credential.json` | Where local sign-in keeps its passphrase DIGEST. `clearotron start` points it inside the install's own base directory for a new install, so it mints its own passphrase instead of adopting a digest another install left; an install that has been signing in with the shared file keeps it. `npx clearotron demo` always points it inside the demo's own base directory and mints a new passphrase there on every start, so a demo never inherits a digest minted for another address, and removing the demo stays one `rm -rf`. |
|
|
290
290
|
| `PORTAL_LOCAL_PASSPHRASE` | unset | **NEVER set this in a file.** An internal one-shot handoff, not an operator control: on a first FOREGROUND start the supervisor mints the passphrase and hands it to the portal it spawns *at the spawn call*, so the closing summary can print the value beside the address rather than sending a first-time reader back into eleven startup log lines for the one value in this product that cannot be read back. It is deliberately absent from the composed child environments, because that composition is what `--background` writes into the units' env file — a passphrase there would be a permanent plaintext copy on disk and the product's own sentence, "it is stored only as a digest", would stop being true. Setting it in any env file recreates exactly that. Lost passphrase: `clearotron passphrase --reset`. |
|
|
291
291
|
| `PORTAL_URL` | `http://127.0.0.1:18802`, or built from `PORTAL_SERVICE_HOST`/`PORTAL_SERVICE_PORT` | Where the deploy tick's live-surface check expects to reach the portal. |
|
package/driver/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# clearotron-driver
|
|
2
2
|
|
|
3
|
+
## 0.3.0-beta.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c5744bf: Fixed: A demo started with npx keeps its assistant connection working after npm cleans its cache.
|
|
8
|
+
- c5744bf: Fixed: An assistant connected to the demo now opens each sample report on the demo's own portal, not on another server.
|
|
9
|
+
- c5744bf: Fixed: The portal names the organisation setup recorded, and the product keeps its own name beside it.
|
|
10
|
+
|
|
3
11
|
## 0.3.0-beta.6
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
// stage apart. This module is the single answer. `cut/` cannot import it (that directory does not travel
|
|
24
24
|
// and this one does), so the pack gate restates the disjunction and its own test pins the two together.
|
|
25
25
|
|
|
26
|
-
import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync } from "node:fs";
|
|
26
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
27
27
|
import { tmpdir } from "node:os";
|
|
28
28
|
import { dirname, join, resolve, sep } from "node:path";
|
|
29
29
|
|
|
@@ -89,9 +89,17 @@ export function publishSource(dir, { repoRoot, tmp = tmpdir() } = {}) {
|
|
|
89
89
|
* anywhere else, so a real install started afterwards sees none of it (the demo is its own install).
|
|
90
90
|
*
|
|
91
91
|
* Copied, never linked: a connector reading a run may write beside it, and `demo/` is tracked. A run
|
|
92
|
-
* already in place is
|
|
92
|
+
* already in place is not copied again, so a visitor's second start changes nothing but its links.
|
|
93
|
+
*
|
|
94
|
+
* ITS REPORT LINKS POINT AT THIS DEMO'S PORTAL. Each sample's `status.json` carried the report URL it was
|
|
95
|
+
* stamped with where it was captured, a test instance's address, and the connector hands a run's `url` to
|
|
96
|
+
* the assistant as-is, so an assistant asked to open a demo report sent the person to that host (measured
|
|
97
|
+
* on a published beta, 2026-09-11). The tracked samples now carry the portal's own route,
|
|
98
|
+
* `/portal/report/<runId>/`, with no host, and each copy here is stamped with `portalOrigin`, the demo
|
|
99
|
+
* portal's address. Stamped on EVERY start, the copies already in place included: `--port` moves the
|
|
100
|
+
* portal, and a copy laid down by an earlier version still carries the old host.
|
|
93
101
|
*/
|
|
94
|
-
export function seedDemoRuns({ workspace, examplesDir }) {
|
|
102
|
+
export function seedDemoRuns({ workspace, examplesDir, portalOrigin = null }) {
|
|
95
103
|
const seeded = [], already = [];
|
|
96
104
|
for (const name of demoChildren(examplesDir)) {
|
|
97
105
|
const run = join(examplesDir, name, "run");
|
|
@@ -99,10 +107,37 @@ export function seedDemoRuns({ workspace, examplesDir }) {
|
|
|
99
107
|
try { s = JSON.parse(readFileSync(join(run, "status.json"), "utf8")); } catch { continue; }
|
|
100
108
|
if (!s?.slug || !s?.codename || !s?.date) continue;
|
|
101
109
|
const dir = join(workspace, `workspace-${s.agent || "clawdi"}`, "studio", "prelim-search", s.slug, `${s.date}-${s.codename}`);
|
|
102
|
-
if (existsSync(join(dir, "status.json")))
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
if (existsSync(join(dir, "status.json"))) already.push(s.runId);
|
|
111
|
+
else {
|
|
112
|
+
mkdirSync(dirname(dir), { recursive: true });
|
|
113
|
+
cpSync(run, dir, { recursive: true });
|
|
114
|
+
seeded.push(s.runId);
|
|
115
|
+
}
|
|
116
|
+
if (portalOrigin) stampReportLinks(join(dir, "status.json"), portalOrigin);
|
|
106
117
|
}
|
|
107
118
|
return { seeded, already };
|
|
108
119
|
}
|
|
120
|
+
|
|
121
|
+
/** The portal route that serves a run's report: the one `scanAccountRuns` hands the portal's own list. */
|
|
122
|
+
export const reportRoute = (runId) => `/portal/report/${encodeURIComponent(runId)}/`;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Point a seeded run's report links at `origin`. A link already on the portal's route keeps its path; any
|
|
126
|
+
* other (the host an older sample carried) becomes the run's own route. Written only when something moved.
|
|
127
|
+
*/
|
|
128
|
+
export function stampReportLinks(statusFile, origin) {
|
|
129
|
+
let s;
|
|
130
|
+
try { s = JSON.parse(readFileSync(statusFile, "utf8")); } catch { return false; }
|
|
131
|
+
if (!s?.runId) return false;
|
|
132
|
+
const base = String(origin).replace(/\/+$/, "");
|
|
133
|
+
const local = (url) => {
|
|
134
|
+
const path = String(url ?? "").replace(/^https?:\/\/[^/]+/, "");
|
|
135
|
+
return `${base}${path.startsWith("/portal/report/") ? path : reportRoute(s.runId)}`;
|
|
136
|
+
};
|
|
137
|
+
const next = { ...s, url: local(s.url) };
|
|
138
|
+
if (Array.isArray(s.reports)) next.reports = s.reports.map((r) => (r && typeof r === "object" ? { ...r, url: local(r.url) } : r));
|
|
139
|
+
const text = `${JSON.stringify(next, null, 2)}\n`;
|
|
140
|
+
if (text === `${JSON.stringify(s, null, 2)}\n`) return false;
|
|
141
|
+
writeFileSync(statusFile, text);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
package/driver/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "clearotron-driver",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.3.0-beta.
|
|
5
|
+
"version": "0.3.0-beta.7",
|
|
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/suite-census.json
CHANGED
|
@@ -970,9 +970,9 @@
|
|
|
970
970
|
"todos": 0
|
|
971
971
|
},
|
|
972
972
|
"an-install-from-npx-moves-out-of-the-cache.test.mjs": {
|
|
973
|
-
"tests":
|
|
974
|
-
"asserts":
|
|
975
|
-
"skips":
|
|
973
|
+
"tests": 11,
|
|
974
|
+
"asserts": 61,
|
|
975
|
+
"skips": 6,
|
|
976
976
|
"todos": 0
|
|
977
977
|
},
|
|
978
978
|
"an-mcp-route-says-which-challenge-it-answers-with.test.mjs": {
|
|
@@ -2745,6 +2745,12 @@
|
|
|
2745
2745
|
"skips": 0,
|
|
2746
2746
|
"todos": 0
|
|
2747
2747
|
},
|
|
2748
|
+
"no-internal-hostname-ships-in-the-package.test.mjs": {
|
|
2749
|
+
"tests": 1,
|
|
2750
|
+
"asserts": 3,
|
|
2751
|
+
"skips": 0,
|
|
2752
|
+
"todos": 0
|
|
2753
|
+
},
|
|
2748
2754
|
"no-nul-bytes.test.mjs": {
|
|
2749
2755
|
"tests": 3,
|
|
2750
2756
|
"asserts": 7,
|
|
@@ -4408,8 +4414,8 @@
|
|
|
4408
4414
|
"todos": 0
|
|
4409
4415
|
},
|
|
4410
4416
|
"the-bar-names-no-organisation-nobody-named.test.mjs": {
|
|
4411
|
-
"tests":
|
|
4412
|
-
"asserts":
|
|
4417
|
+
"tests": 6,
|
|
4418
|
+
"asserts": 13,
|
|
4413
4419
|
"skips": 0,
|
|
4414
4420
|
"todos": 0
|
|
4415
4421
|
},
|
|
@@ -4503,6 +4509,12 @@
|
|
|
4503
4509
|
"skips": 0,
|
|
4504
4510
|
"todos": 0
|
|
4505
4511
|
},
|
|
4512
|
+
"the-demo-links-its-reports-to-its-own-portal.test.mjs": {
|
|
4513
|
+
"tests": 3,
|
|
4514
|
+
"asserts": 13,
|
|
4515
|
+
"skips": 0,
|
|
4516
|
+
"todos": 0
|
|
4517
|
+
},
|
|
4506
4518
|
"the-door-answers-the-address-it-advertises.test.mjs": {
|
|
4507
4519
|
"tests": 9,
|
|
4508
4520
|
"asserts": 37,
|
package/mcp-server/CHANGELOG.md
CHANGED
package/mcp-server/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trademark-artifacts-mcp",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-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/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.0-beta.
|
|
5
|
+
"version": "0.3.0-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": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trademark-oauth-mcp-bridge",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-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).",
|
package/shared/brand.mjs
CHANGED
|
@@ -87,10 +87,14 @@ export const BRAND = {
|
|
|
87
87
|
* installation, which on a fresh install has no answer, and answering it with the product's name puts
|
|
88
88
|
* "Organisation: Clearotron" on the top bar of every install that never set it.
|
|
89
89
|
*
|
|
90
|
-
* Null rather than an empty string, so a caller cannot render it by accident.
|
|
91
|
-
*
|
|
90
|
+
* Null rather than an empty string, so a caller cannot render it by accident.
|
|
91
|
+
*
|
|
92
|
+
* ITS OWN SETTING, `CLEAROTRON_ORGANISATION_NAME`, the one setup writes. This read `CLEAROTRON_BRAND_NAME`,
|
|
93
|
+
* which renames the PRODUCT: a wizard-configured organisation was never named, and an operator who set
|
|
94
|
+
* the brand to reach it got "This is Acme's Acme portal" with the title and the logo renamed too
|
|
95
|
+
* (measured on a published beta, 2026-09-11). The two differ on presence as well as on absence now.
|
|
92
96
|
*/
|
|
93
|
-
export const ORGANISATION_NAME = process.env.
|
|
97
|
+
export const ORGANISATION_NAME = process.env.CLEAROTRON_ORGANISATION_NAME?.trim() || null;
|
|
94
98
|
|
|
95
99
|
/**
|
|
96
100
|
* THE CONFIDENTIALITY POSTURE ON A DELIVERED DOCUMENT — one rule, both report templates.
|
|
@@ -161,7 +161,43 @@ export function packagedUpdate({ installDir = INSTALL_DIR, version = ownVersion(
|
|
|
161
161
|
*/
|
|
162
162
|
export function stableInstallRoot({ installRoot = INSTALL_DIR, env = process.env, exists = existsSync } = {}) {
|
|
163
163
|
if (!inNpxCache(installRoot)) return installRoot;
|
|
164
|
+
const has = (root) => !!root && exists(join(root, "mcp-server", "server.mjs"));
|
|
165
|
+
// A DEMO'S OWN COPY FIRST: it is this version, and it lives inside the demo's base. The demo's children
|
|
166
|
+
// are handed `CLEAROTRON_DEMO=1` and their workspace, `<base>/workspace`, which is how they find it.
|
|
167
|
+
const work = String(env?.CLEAROTRON_WORK_DIR ?? "").trim();
|
|
168
|
+
const demoRoot = env?.CLEAROTRON_DEMO === "1" && work ? packageRootUnder(demoProgramPrefix(dirname(work))) : null;
|
|
169
|
+
if (has(demoRoot)) return demoRoot;
|
|
164
170
|
const prefix = permanentPrefix(env);
|
|
165
171
|
const root = prefix ? packageRootUnder(prefix) : null;
|
|
166
|
-
return
|
|
172
|
+
return has(root) ? root : installRoot;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* WHERE A DEMO RUN FROM NPX KEEPS ITS OWN COPY OF THE PROGRAM: inside the demo's base, never `~/.local`.
|
|
177
|
+
*
|
|
178
|
+
* `npx clearotron demo` ran from npm's cache like `install` did, so the connect line it printed launched
|
|
179
|
+
* the connector from `_npx/<hash>/`, which npm deletes when it cleans up (measured on a published beta,
|
|
180
|
+
* 2026-09-11). The install's answer, a copy under `~/.local`, would leave a trace of the demo outside the
|
|
181
|
+
* demo's directory; this copy lives in `<base>/program`, so removing the demo is still one directory.
|
|
182
|
+
*/
|
|
183
|
+
export function demoProgramPrefix(base) {
|
|
184
|
+
return join(base, "program");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* What `demo` does about its program. PURE given its inputs. `null` outside npx's cache; `{ skip }` on
|
|
189
|
+
* Windows, where npm's global layout differs, or with an unreadable version; otherwise a plan whose
|
|
190
|
+
* `current` says the copy is already this version, so a second start runs no npm at all.
|
|
191
|
+
*/
|
|
192
|
+
export function demoProgramPlan({ base, installDir = INSTALL_DIR, platform = process.platform, version = ownVersion(installDir), exists = existsSync, read = readFileSync } = {}) {
|
|
193
|
+
if (!inNpxCache(installDir)) return null;
|
|
194
|
+
if (platform === "win32") return { skip: "windows" };
|
|
195
|
+
if (!version) return { skip: "no-version" };
|
|
196
|
+
const prefix = demoProgramPrefix(base);
|
|
197
|
+
const root = packageRootUnder(prefix);
|
|
198
|
+
const current = exists(join(root, "mcp-server", "server.mjs")) && ownVersion(root, read) === version;
|
|
199
|
+
return {
|
|
200
|
+
prefix, root, version, current,
|
|
201
|
+
npmArgs: ["install", "--global", "--prefix", prefix, "--prefer-offline", "--no-fund", "--no-audit", `clearotron@${version}`],
|
|
202
|
+
};
|
|
167
203
|
}
|