clearotron 0.4.0 → 0.4.1-beta.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.
- package/THIRD-PARTY-NOTICES.md +2 -2
- package/bin/README.md +1 -1
- package/bin/clearotron.mjs +2 -0
- package/bin/mcp.mjs +80 -0
- package/bin/start.mjs +14 -1
- package/build-info.json +2 -2
- package/demo/README.md +1 -1
- package/docs/SECURITY-OWASP.md +21 -13
- package/docs/SECURITY.md +20 -14
- package/docs/architecture/04-configuration-reference.md +3 -3
- package/docs/architecture/05-config-governance.md +1 -1
- package/docs/architecture/06-operations-runbook.md +5 -4
- package/docs/architecture/08-development-guide.md +3 -1
- package/docs/architecture/09-security-and-data.md +16 -11
- package/driver/CHANGELOG.md +29 -0
- package/driver/binding-layers.mjs +28 -6
- package/driver/citation-census.json +3 -3
- package/driver/common-law-coverage-status.mjs +52 -0
- package/driver/common-law-receipts.mjs +1 -1
- package/driver/compare.mjs +1 -1
- package/driver/connotation-search.mjs +5 -5
- package/driver/contract-audit.mjs +6 -6
- package/driver/contract-dictation-registry.mjs +1 -1
- package/driver/contract-e3-backlog.mjs +18 -17
- package/driver/contract-vocabulary.mjs +145 -132
- package/driver/declination-call.mjs +5 -1
- package/driver/degraded-parts.mjs +10 -3
- package/driver/dev-portal.mjs +2 -1
- package/driver/dispatch-record.mjs +19 -0
- package/driver/doubt-closure-tool.mjs +2 -2
- package/driver/driver.config.mjs +39 -6
- package/driver/engagement-receipt.mjs +1 -1
- package/driver/engine/CONTRACT.md +41 -1
- package/driver/engine/anthropic-agent.mjs +56 -4
- package/driver/engine/mcp/codex-config.mjs +8 -3
- package/driver/engine/mcp/coverage-server.mjs +1 -1
- package/driver/engine/mcp/gather-config.mjs +1 -1
- package/driver/engine/mcp/recording-server.mjs +32 -5
- package/driver/engine/mcp/stdio-server.mjs +45 -7
- package/driver/engine/mcp/supplemental.mjs +26 -1
- package/driver/engine/openai-agent.mjs +113 -20
- package/driver/engine/session-record.mjs +223 -0
- package/driver/enqueue-schema.mjs +2 -2
- package/driver/findings-model.mjs +4 -4
- package/driver/flag-snapshot.mjs +2 -1
- package/driver/form-neighbourhood.mjs +7 -7
- package/driver/frame-diff-model.mjs +3 -3
- package/driver/framework.mjs +22 -0
- package/driver/gateway.mjs +96 -14
- package/driver/jx.mjs +1 -1
- package/driver/knockout-review-record.mjs +55 -1
- package/driver/named-band.mjs +4 -0
- package/driver/owner-screen.mjs +7 -4
- package/driver/package.json +1 -1
- package/driver/phase0.mjs +13 -2
- package/driver/pipeline-knockout.mjs +15 -13
- package/driver/pipeline.mjs +132 -48
- package/driver/portal-families.mjs +3 -3
- package/driver/portal-service.mjs +6 -5
- package/driver/predelivery-lint.mjs +19 -19
- package/driver/products.mjs +1 -1
- package/driver/profiles.mjs +4 -3
- package/driver/publish/index.mjs +1 -1
- package/driver/publish/knockout.mjs +5 -4
- package/driver/publish/office-record-links.mjs +17 -2
- package/driver/publish/publish-inputs.mjs +3 -3
- package/driver/publish/render-knockout.mjs +17 -5
- package/driver/publish/render.mjs +4 -2
- package/driver/publish/xlsx.mjs +1 -1
- package/driver/record-fold.mjs +177 -0
- package/driver/register-count.mjs +20 -3
- package/driver/register-coverage.mjs +1 -1
- package/driver/register-plan.mjs +83 -18
- package/driver/register-records.mjs +7 -0
- package/driver/registry-fidelity.mjs +2 -2
- package/driver/repairs.mjs +1 -1
- package/driver/report-card-record.mjs +4 -4
- package/driver/result-noun-fields.mjs +3 -3
- package/driver/scope-facts.mjs +11 -3
- package/driver/scope-rules.mjs +2 -2
- package/driver/search-policy.mjs +20 -1
- package/driver/skills/clearance-common-law/SKILL.md +22 -22
- package/driver/skills/clearance-register/digest.md +3 -3
- package/driver/skills/clearance-register/register-recipes.md +11 -11
- package/driver/skills/clearance-register/status-rules.md +1 -1
- package/driver/skills/clearance-register/unit.md +1 -1
- package/driver/skills/clearance-search/firm-wide-reasoning.md +3 -2
- package/driver/skills/clearance-search/phase2-execution.md +6 -5
- package/driver/skills/clearance-search/synthesis-rules.md +4 -4
- package/driver/skills/clearance-search/worked-examples.md +39 -39
- package/driver/skills/clearance-variants/SKILL.md +16 -16
- package/driver/skills/frame-diff/SKILL.md +2 -2
- package/driver/stage-context.mjs +36 -1
- package/driver/stages-knockout.mjs +6 -5
- package/driver/stages.mjs +30 -30
- package/driver/suite-census.json +259 -55
- package/driver/synthesis-record.mjs +5 -0
- package/driver/unit-inventory.mjs +17 -28
- package/driver/verify.mjs +7 -7
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/CONNECT.md +9 -3
- package/mcp-server/demo-mode.mjs +10 -0
- package/mcp-server/lib/evidence.mjs +8 -8
- package/mcp-server/lib/knockout.mjs +1 -1
- package/mcp-server/lib/runs.mjs +7 -1
- package/mcp-server/lib/scrub.mjs +1 -1
- package/mcp-server/package.json +2 -2
- package/mcp-server/packs/README.md +1 -1
- package/mcp-server/server.mjs +16 -2
- package/package.json +4 -3
- package/portal-ui/package.json +3 -3
- package/providers/_shared/count.mjs +9 -2
- package/providers/_shared/enumerate.mjs +125 -41
- package/providers/_shared/execute-plan.mjs +15 -4
- package/providers/corsearch/src/capabilities.js +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +2 -2
- package/providers/signa/src/capabilities.js +32 -2
- package/providers/signa/src/core.js +262 -36
- package/scripts/band-shape-probe.mjs +2 -2
- package/scripts/deprecate-below.mjs +28 -4
- package/scripts/e2e-first-time.mjs +17 -2
- package/scripts/env-audit.mjs +3 -2
- package/scripts/freeze-example-run.mjs +10 -10
- package/scripts/mcp-registry-listing.mjs +199 -0
- package/scripts/mint-plugin-folder.mjs +65 -0
- package/scripts/release-version.mjs +44 -0
- package/scripts/score.mjs +1 -1
- package/shared/register-floor.mjs +19 -0
- package/shared/scope.mjs +18 -2
- package/shared/trigger-lane.mjs +1 -1
- package/skills/clearotron-ops/SKILL.md +81 -24
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -110,7 +110,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
110
110
|
SOFTWARE.
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
## @modelcontextprotocol/sdk@1.
|
|
113
|
+
## @modelcontextprotocol/sdk@1.32.1
|
|
114
114
|
|
|
115
115
|
- **Licence declared:** `MIT`
|
|
116
116
|
- **Repository:** https://github.com/modelcontextprotocol/typescript-sdk
|
|
@@ -4896,7 +4896,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
4896
4896
|
SOFTWARE.**
|
|
4897
4897
|
```
|
|
4898
4898
|
|
|
4899
|
-
## proxy-addr@2.0.
|
|
4899
|
+
## proxy-addr@2.0.8
|
|
4900
4900
|
|
|
4901
4901
|
- **Licence declared:** `MIT`
|
|
4902
4902
|
- **Repository:** jshttp/proxy-addr
|
package/bin/README.md
CHANGED
|
@@ -20,7 +20,7 @@ and is what you want when you are working in this tree.
|
|
|
20
20
|
| `signa-sync.mjs` | — | `node bin/signa-sync.mjs` | One vendor read (`GET /v1/offices`, paged). Needs `SIGNA_API_KEY`. |
|
|
21
21
|
|
|
22
22
|
`example.mjs` replays `demo` through the ordinary publisher into `~/trademark-demo/pool` and
|
|
23
|
-
serves it on `127.0.0.1:
|
|
23
|
+
serves it on `127.0.0.1:18860`. Every path the demo pool could collide with is checked by realpath and by
|
|
24
24
|
containment before anything is written, because a demo runs on deployed machines too.
|
|
25
25
|
|
|
26
26
|
`onboard.mjs` validates each answer through the door the engine itself uses before it persists anything,
|
package/bin/clearotron.mjs
CHANGED
|
@@ -65,6 +65,7 @@ export const VERBS = {
|
|
|
65
65
|
passphrase: ["bin/passphrase.mjs"],
|
|
66
66
|
sync: ["bin/uspto-sync.mjs"],
|
|
67
67
|
update: ["bin/update.mjs"],
|
|
68
|
+
mcp: ["bin/mcp.mjs"],
|
|
68
69
|
};
|
|
69
70
|
|
|
70
71
|
export const SUMMARY = {
|
|
@@ -87,6 +88,7 @@ export const SUMMARY = {
|
|
|
87
88
|
passphrase: "report or RESET the portal's local sign-in — the recovery for a lost passphrase",
|
|
88
89
|
sync: "build or update the free US register index (a large download, and hours of ingest)",
|
|
89
90
|
update: "bring this install up to date — and REFUSE to do it over the top of your own configuration",
|
|
91
|
+
mcp: "serve this install's searches to an assistant over MCP, or the demo's where there is no install",
|
|
90
92
|
};
|
|
91
93
|
|
|
92
94
|
/**
|
package/bin/mcp.mjs
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
// Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
|
|
4
|
+
//
|
|
5
|
+
// mcp.mjs — `clearotron mcp`: the MCP server over stdio, started from the package itself.
|
|
6
|
+
//
|
|
7
|
+
// An install's own connect lines run the server from that install's copy (shared/stdio-connect.mjs), so
|
|
8
|
+
// an assistant never keeps an old server after an upgrade, and they stay as they are. This verb is for a
|
|
9
|
+
// machine with no install, and for a listing that launches the published package with `npx`.
|
|
10
|
+
//
|
|
11
|
+
// WHICH RUNS IT READS. An install's, wherever one is found; the demo's four sample runs where none is.
|
|
12
|
+
// Standard output belongs to the protocol from its first byte, so nothing here prints to it.
|
|
13
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { parseEnv } from "node:util";
|
|
17
|
+
import { isEntrypoint } from "../shared/is-entrypoint.mjs";
|
|
18
|
+
import { demoMode } from "../mcp-server/demo-mode.mjs";
|
|
19
|
+
|
|
20
|
+
/** The two settings the install's own connect lines hand the server: where runs are, and where reports are. */
|
|
21
|
+
export const INSTALL_FOLDERS = Object.freeze(["CLEAROTRON_WORK_DIR", "CLEAROTRON_REPORTS_DIR"]);
|
|
22
|
+
|
|
23
|
+
/** The demo's own base, and where it keeps its runs and its access log: the layout `installPaths` gives it. */
|
|
24
|
+
export const demoBase = (home = homedir()) => join(home, "trademark-demo");
|
|
25
|
+
export const demoWorkspace = (home = homedir()) => join(demoBase(home), "workspace");
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* WHAT COUNTS AS AN INSTALL, and so whether this server reads an install's runs or the demo's. PURE.
|
|
29
|
+
*
|
|
30
|
+
* An install is any one of these:
|
|
31
|
+
* - a work folder or a reports folder named in this process's environment, in the settings file a
|
|
32
|
+
* packaged install writes (`~/.config/clearotron/.env`), or in the file a background install's
|
|
33
|
+
* services read (`~/.env`);
|
|
34
|
+
* - either of those files carrying any Clearotron setting at all, which is an install configured and
|
|
35
|
+
* not yet pointed at its folders: the server then reads its default and names what is missing;
|
|
36
|
+
* - the default workspace, `~/trademark/workspace`, on disk.
|
|
37
|
+
* The demo writes neither file and keeps its runs under its own base, so a machine that has only run the
|
|
38
|
+
* demo still reads as no install. An install never gets the demo: a misconfigured one is refused by name.
|
|
39
|
+
*
|
|
40
|
+
* Returns the folders to hand the server (only what is set, and never over the environment's own value),
|
|
41
|
+
* or `demo: true` where there is no install.
|
|
42
|
+
*/
|
|
43
|
+
export function mcpTarget({ env = process.env, home = homedir(), exists = existsSync, read = (p) => readFileSync(p, "utf8") } = {}) {
|
|
44
|
+
const parsed = (p) => { try { return parseEnv(read(p)); } catch { return null; } };
|
|
45
|
+
const settings = parsed(join(home, ".config", "clearotron", ".env"));
|
|
46
|
+
const services = parsed(join(home, ".env"));
|
|
47
|
+
const set = (v) => (typeof v === "string" && v.trim() ? v.trim() : null);
|
|
48
|
+
const folders = {};
|
|
49
|
+
for (const k of INSTALL_FOLDERS) {
|
|
50
|
+
const v = set(env[k]) ?? set(settings?.[k]) ?? set(services?.[k]);
|
|
51
|
+
if (v) folders[k] = v;
|
|
52
|
+
}
|
|
53
|
+
const configures = (file) => Boolean(file) && Object.keys(file).some((k) => /^(CLEAROTRON|TRADEMARK|PORTAL)_/.test(k));
|
|
54
|
+
const install = Object.keys(folders).length > 0 || configures(settings) || configures(services)
|
|
55
|
+
|| exists(join(home, "trademark", "workspace"));
|
|
56
|
+
return install ? { demo: false, env: folders } : { demo: true, env: {} };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function main() {
|
|
60
|
+
const target = mcpTarget();
|
|
61
|
+
if (target.demo) {
|
|
62
|
+
// The four sample runs, copied where the connector walks, under the demo's own base: nothing is
|
|
63
|
+
// written anywhere an install reads (driver/demo-container.mjs).
|
|
64
|
+
const { seedDemoRuns } = await import("../driver/demo-container.mjs");
|
|
65
|
+
const workspace = demoWorkspace();
|
|
66
|
+
seedDemoRuns({ workspace, examplesDir: join(import.meta.dirname, "..", "demo") });
|
|
67
|
+
process.env.CLEAROTRON_WORK_DIR = workspace;
|
|
68
|
+
// The door's access log too, which would otherwise open the install's own telemetry folder.
|
|
69
|
+
if (!set(process.env.TRADEMARK_MCP_AUDIT_LOG)) process.env.TRADEMARK_MCP_AUDIT_LOG = join(demoBase(), "telemetry", "trademark-mcp-access.jsonl");
|
|
70
|
+
demoMode.on = true;
|
|
71
|
+
} else {
|
|
72
|
+
for (const [k, v] of Object.entries(target.env)) if (!set(process.env[k])) process.env[k] = v;
|
|
73
|
+
}
|
|
74
|
+
// The server starts through the entry that refuses an old Node in one plain line before it loads.
|
|
75
|
+
await import("../mcp-server/serve.mjs");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const set = (v) => typeof v === "string" && v.trim() !== "";
|
|
79
|
+
|
|
80
|
+
if (isEntrypoint(import.meta.url)) await main();
|
package/bin/start.mjs
CHANGED
|
@@ -2445,6 +2445,18 @@ if (isMain) {
|
|
|
2445
2445
|
const clientDoor = adoptedClientDoor
|
|
2446
2446
|
? null
|
|
2447
2447
|
: start("the client door", "mcp-server/http-server-client.mjs", envs.client, { fatal: false });
|
|
2448
|
+
// ── THE BANNER NAMES THIS DOOR ONLY ONCE IT ANSWERS ─────────────────────────────────────────────────
|
|
2449
|
+
//
|
|
2450
|
+
// The engine door and the portal are each awaited above before anything is printed. This door was
|
|
2451
|
+
// not: the banner below was composed the moment it was spawned, while it was still loading, and
|
|
2452
|
+
// announced an address that refused connections. Measured 2026-10-09, in a container with no network
|
|
2453
|
+
// running as root and on a host as a normal user: a request sent as the banner printed got no
|
|
2454
|
+
// connection, and the door's first answer came about half a second later. Its own output said nothing
|
|
2455
|
+
// was wrong, because nothing was; it had not finished starting.
|
|
2456
|
+
//
|
|
2457
|
+
// Same probe and same budget as the other two, and non-fatal like the door itself. A door that exits,
|
|
2458
|
+
// or does not answer within the budget, takes the NOT RUNNING branch below and the portal keeps serving.
|
|
2459
|
+
const clientDoorAnswered = clientDoor ? await healthy(`http://${HOST}:${ports.client}/healthz`, clientDoor) : false;
|
|
2448
2460
|
|
|
2449
2461
|
// ── 6. one URL ─────────────────────────────────────────────────────────────────────────────────────
|
|
2450
2462
|
|
|
@@ -2471,7 +2483,8 @@ if (isMain) {
|
|
|
2471
2483
|
// AN ADOPTED DOOR IS RUNNING. Without this the banner reported "NOT RUNNING …
|
|
2472
2484
|
// its output above says why" about a door that is up and serving, and pointed the reader at output
|
|
2473
2485
|
// that does not exist — the one sentence on this screen a reader would act on, and false.
|
|
2474
|
-
|
|
2486
|
+
// AND A SPAWNED DOOR IS RUNNING ONLY ONCE IT HAS ANSWERED: alive is not listening (see the probe above).
|
|
2487
|
+
const doorRunning = adoptedClientDoor || (clientDoor?.child?.exitCode === null && clientDoorAnswered);
|
|
2475
2488
|
if (doorRunning) {
|
|
2476
2489
|
say(` Client door http://${HOST}:${ports.client}/mcp — a client's assistant connects here.`);
|
|
2477
2490
|
if (adoptedClientDoor)
|
package/build-info.json
CHANGED
package/demo/README.md
CHANGED
|
@@ -16,7 +16,7 @@ carries no `report.md` at all, because for that lane the markdown is an output o
|
|
|
16
16
|
an input to it.
|
|
17
17
|
|
|
18
18
|
Each directory is named by the product that produced it — the id in the run's own frozen search policy,
|
|
19
|
-
not a label chosen afterwards. `clearotron demo` replays
|
|
19
|
+
not a label chosen afterwards. `clearotron demo` replays all four; `clearotron demo --product <id>`
|
|
20
20
|
replays a named one; `clearotron demo --run-dir <dir>` replays any frozen run from anywhere.
|
|
21
21
|
|
|
22
22
|
## The marks are invented; the data behind them is real
|
package/docs/SECURITY-OWASP.md
CHANGED
|
@@ -45,10 +45,11 @@ shows the engine's internals to a company's people.
|
|
|
45
45
|
**What Clearotron does.** Every read passes one authorization check, and a person sees only what their
|
|
46
46
|
grant names. A key bound to one run reads that run and writes nothing. The copy of a report a company
|
|
47
47
|
receives drops the staff-only notes. No real company's data is in this repository; run data lives
|
|
48
|
-
in folders the operator owns. CI scans the tree and the built bundle for secrets.
|
|
48
|
+
in folders the operator owns. CI scans the tree and the built bundle for secrets. A report published
|
|
49
|
+
from 0.4.0 on carries its fonts inside itself, and opening it contacts no other server.
|
|
49
50
|
|
|
50
51
|
**Where.** [SECURITY.md](SECURITY.md) (the access model), `shared/scope.mjs`, `driver/portal-report.mjs`,
|
|
51
|
-
`.gitleaks.toml`, `.github/workflows/ci.yml`.
|
|
52
|
+
`.gitleaks.toml`, `.github/workflows/ci.yml`, `scripts/report-offline-render-check.mjs`.
|
|
52
53
|
|
|
53
54
|
**Not covered.** A company's clearances are kept until the operator deletes them: Clearotron sets no
|
|
54
55
|
retention period, because how long they are held is the operator's policy.
|
|
@@ -57,14 +58,15 @@ retention period, because how long they are held is the operator's policy.
|
|
|
57
58
|
|
|
58
59
|
**Here.** A compromised npm package, GitHub Action or engine program.
|
|
59
60
|
|
|
60
|
-
**What Clearotron does.** Dependabot updates the npm packages and the GitHub Actions.
|
|
61
|
-
published to npm through trusted publishing, with provenance that ties the package
|
|
62
|
-
build that produced it.
|
|
61
|
+
**What Clearotron does.** Dependabot updates the npm packages and the GitHub Actions. Every GitHub Action is pinned to a commit
|
|
62
|
+
hash. Each release is published to npm through trusted publishing, with provenance that ties the package
|
|
63
|
+
to the commit and the build that produced it. Each release carries a software bill of materials. On an install from a git
|
|
64
|
+
checkout, `clearotron update` installs with install scripts off. A stable release is not published while a code-scanning alert is open. Setup
|
|
63
65
|
installs, and `clearotron doctor` accepts, only an engine program at or above the version this release
|
|
64
66
|
needs.
|
|
65
67
|
|
|
66
|
-
**Where.** `.github/dependabot.yml`, `.github/workflows/release.yml`,
|
|
67
|
-
`scripts/release-code-scanning-check.mjs`, `driver/driver.config.mjs` (`ENGINE_BINARIES`, each program's
|
|
68
|
+
**Where.** `.github/dependabot.yml`, `.github/workflows/`, `.github/workflows/release.yml`,
|
|
69
|
+
`scripts/release-sbom.mjs`, `bin/update.mjs`, `scripts/release-code-scanning-check.mjs`, `driver/driver.config.mjs` (`ENGINE_BINARIES`, each program's
|
|
68
70
|
`floor`).
|
|
69
71
|
|
|
70
72
|
### LLM04:2025 Data and Model Poisoning
|
|
@@ -99,12 +101,14 @@ The common-law search asks the research service to run a search program, on that
|
|
|
99
101
|
|
|
100
102
|
**What Clearotron does.** Tools are granted per stage, by name. The case-law stage is the exception: it is granted every tool its two case-law services offer. A Claude
|
|
101
103
|
stage is offered no command tool.
|
|
102
|
-
Register searches beyond the fixed plan go through a proposal that code checks and runs. A key issued for
|
|
103
|
-
automation
|
|
104
|
+
Register searches beyond the fixed plan go through a proposal that code checks and runs. A new key issued for
|
|
105
|
+
automation must name the actions it may take; keys issued before that rule keep working, and each use of
|
|
106
|
+
one is recorded. No key for automation reaches a what-if over the network. A what-if requested by a company's people is queued for a
|
|
104
107
|
separate process rather than run by the door that received it.
|
|
105
108
|
|
|
106
109
|
**Where.** `driver/engine/mcp/gather-config.mjs`, `driver/engine/anthropic-agent.mjs`,
|
|
107
|
-
`driver/engine/mcp/supplemental.mjs`, `shared/scope.mjs`, `driver/whatif-worker.mjs
|
|
110
|
+
`driver/engine/mcp/supplemental.mjs`, `shared/scope.mjs`, `driver/whatif-worker.mjs`,
|
|
111
|
+
`mcp-server/mint-token.mjs`, `mcp-server/server.mjs`.
|
|
108
112
|
|
|
109
113
|
**Not covered.** As ASI02: the Claude program also offers a stage built-in tools that act without asking.
|
|
110
114
|
|
|
@@ -189,7 +193,7 @@ remove them.
|
|
|
189
193
|
**What Clearotron does.** The AI program starts with a named list of settings, and the key that signs
|
|
190
194
|
access keys is not on it. The worker that starts the AI program does not hold that key either, whether
|
|
191
195
|
the install runs in a terminal or under systemd. Access keys are scoped to a run, a company or named
|
|
192
|
-
actions, and one check enforces the scope. On Claude, a stage's file tools can read nothing outside its run folder, its
|
|
196
|
+
actions, and one check enforces the scope. A new key for automation must name its actions. On Claude, a stage's file tools can read nothing outside its run folder, its
|
|
193
197
|
instruction folders and any folder the machine's own Claude settings add. On Codex with its sandbox on, a stage's commands can read nothing outside its run folder, its instruction folders, the temporary
|
|
194
198
|
folders, and the system and program files a command needs to run.
|
|
195
199
|
|
|
@@ -276,10 +280,12 @@ could not run is listed in the report.
|
|
|
276
280
|
|
|
277
281
|
**What Clearotron does.** No agent outlives its stage. Each stage is one process under a watchdog and a
|
|
278
282
|
hard time limit, and the driver stops its whole process group. The driver records every program it
|
|
279
|
-
starts. On the Claude engine a stage cannot write into the instructions it runs from.
|
|
283
|
+
starts. On the Claude engine a stage cannot write into the instructions it runs from. The systemd units
|
|
284
|
+
an install can run in the background start with `NoNewPrivileges`, so nothing a stage starts there can
|
|
285
|
+
gain more than the install's account holds.
|
|
280
286
|
|
|
281
287
|
**Where.** `driver/engine/anthropic-agent.mjs`, `driver/engine/common.mjs`,
|
|
282
|
-
`driver/engine/child-record.mjs`, `driver/engine/deny-authority-write.mjs
|
|
288
|
+
`driver/engine/child-record.mjs`, `driver/engine/deny-authority-write.mjs`, `driver/systemd/`.
|
|
283
289
|
|
|
284
290
|
**Not covered.** As LLM04: on Codex, no check refuses a stage's write inside its run folder.
|
|
285
291
|
|
|
@@ -296,3 +302,5 @@ Measured on 2026-09-23 with the GitHub API.
|
|
|
296
302
|
| Private vulnerability reporting | On |
|
|
297
303
|
| npm provenance | Every release, through trusted publishing |
|
|
298
304
|
| Required checks on `main` | Six; force pushes and branch deletion refused |
|
|
305
|
+
| Actions pinned to a commit hash | All 42, read from the workflow files on 2026-09-24 |
|
|
306
|
+
| Software bill of materials | CycloneDX, attached to every release, read from the release workflow on 2026-09-24 |
|
package/docs/SECURITY.md
CHANGED
|
@@ -12,7 +12,7 @@ Each risk on OWASP's two lists for AI systems, and what Clearotron does about it
|
|
|
12
12
|
|
|
13
13
|
| Surface | Trust | Guard |
|
|
14
14
|
|---|---|---|
|
|
15
|
-
| stdio MCP (`mcp-server/server.mjs`) | local/full ("ops") | OS user boundary — run it AS the operator account; it is the only surface on which `what_if_run` EXECUTES (`visibleTools` keeps what-if out of the HTTP listing for ops,
|
|
15
|
+
| stdio MCP (`mcp-server/server.mjs`) | local/full ("ops") | OS user boundary — run it AS the operator account; it is the only surface on which `what_if_run` EXECUTES (`visibleTools` keeps what-if out of the HTTP listing for ops, and the CallTool chokepoint refuses any tool the listing hides, so no ops token reaches it over HTTP) |
|
|
16
16
|
| Client MCP (`mcp-server/http-server-client.mjs`) | a company's signed-in person / their access key | `what_if_run` from an `account` principal ENQUEUES rather than executes (ruling 2026-08-27) — it never imports the engine, and `driver/whatif-worker.mjs` spawns the sandbox from an OS service process. A confirmation token is unsigned, so the call must ALSO name its `runId`: the grant check keys on it, and `whatIfEnqueue` refuses a token naming a different run. The `model` argument is refused on this face. |
|
|
17
17
|
| HTTP MCP (`mcp-server/http-server.mjs`) | authenticated remote | auth-BEFORE-data; fail-closed construction; inner scoped tokens |
|
|
18
18
|
| Run-bound keys | a report recipient an operator issues one to | a `user` key from `mint-token.mjs`, read-only and bound to one run; the plain-language report tools (`clientSafe`) only |
|
|
@@ -98,9 +98,9 @@ with access to everything.
|
|
|
98
98
|
on (`get_coverage`, `search`, `diff_artifact`, `run_changes`, what-if), which are denied by the
|
|
99
99
|
default that an undecided tool is denied.
|
|
100
100
|
- HMAC-signed tokens (`v1.<payload>.<sig>`, node:crypto only). `authorize()` is the single
|
|
101
|
-
enforcement point for every tool call; `visibleTools()`
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
enforcement point for every tool call; `visibleTools()` decides what a session is offered, and a
|
|
102
|
+
tool it hides is refused by name at the call, after `authorize()`. `authorize()` never sees `local`,
|
|
103
|
+
and `visibleTools()` is the only per-call gate that uses it.
|
|
104
104
|
- **User tokens cannot**: enumerate runs, cross to another run (explicit mismatching runId refused;
|
|
105
105
|
omitted runId pinned), reach any write/spend tool, or read internal artifacts — `read_artifact`
|
|
106
106
|
is name-gated to the report alone (`USER_ARTIFACTS`), and `list_findings` to the curated
|
|
@@ -110,13 +110,13 @@ with access to everything.
|
|
|
110
110
|
rides inside a delivered PDF and can be forwarded to anyone, and the ruling was about people the
|
|
111
111
|
operator enrolled. Both sets are gated at the read_artifact tool AND at the Resources surface
|
|
112
112
|
(`resources/list` / `resources/read`), kind for kind — two doors to the same bytes, one rule.
|
|
113
|
-
- **Ops tokens are least-privilege**:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
113
|
+
- **Ops tokens are least-privilege**: a new ops token must name its `verbs[]`, the write tools it may
|
|
114
|
+
call (an intake connector physically cannot `stop_run`), and `mint-token` refuses one without
|
|
115
|
+
`--verbs`. Tokens issued before that rule keep working, and the audit line marks every call they
|
|
116
|
+
make. `what_if_plan` and `what_if_run` are left out of the HTTP tool listing for every ops scope
|
|
117
|
+
(`visibleTools`, keyed on `local`), and a tool the listing hides is refused at the call, so no ops
|
|
118
|
+
token plans or runs a what-if over HTTP. An `account` principal returns above that branch: its
|
|
119
|
+
what-if is queued for a separate process, never run by the door.
|
|
120
120
|
|
|
121
121
|
## Token lifecycle
|
|
122
122
|
|
|
@@ -127,15 +127,21 @@ what the mechanism guarantees.*
|
|
|
127
127
|
principal in every audit line; the `jti` printed at mint time is the revocation handle). Three other callers share the same `mintToken`: `clearotron start` mints the portal's verb-scoped,
|
|
128
128
|
company-capped ops token in memory at every start, and neither prints nor stores it; `clearotron connect`
|
|
129
129
|
and the portal's connect screen each mint a person's key for their own assistant.
|
|
130
|
-
- **Revocation**: denylist file checked on every verification
|
|
131
|
-
|
|
130
|
+
- **Revocation**: a denylist file is checked on every verification, always. With
|
|
131
|
+
`TRADEMARK_MCP_TOKEN_DENYLIST` unset it is `~/.config/clearotron/token-denylist`, the file every revoker
|
|
132
|
+
writes in that case, and that file being absent means nothing has been revoked. A list named in the
|
|
133
|
+
setting that is missing or unreadable, or a default list that exists but cannot be read, refuses every
|
|
134
|
+
key. **Rotation**: two-secret window, flag-day-free.
|
|
132
135
|
- **Rate limits**: per-identity bucket on every request plus a separate lower per-principal bucket
|
|
133
136
|
for ops sessions.
|
|
134
137
|
|
|
135
138
|
## Audit
|
|
136
139
|
|
|
137
140
|
Every HTTP tool call appends one line to an append-only JSONL (`TRADEMARK_MCP_AUDIT_LOG`) when the call
|
|
138
|
-
finishes: `{ts, email, sub, method, tool, runId, status, door}`, where `status` is the outcome
|
|
141
|
+
finishes: `{ts, email, sub, kind, method, tool, runId, status, door}`, where `status` is the outcome, plus `query` (its
|
|
142
|
+
first 120 characters) on a search, `uri` on a resource read and `transport` on the local route, and for an
|
|
143
|
+
ops token `namesVerbs`, which says whether the token names its tools (never which tools, and never the
|
|
144
|
+
token). It is
|
|
139
145
|
written after scope resolution, so it names the principal. A request refused before any tool runs is
|
|
140
146
|
written too, with the status `refused`. Writing it is best-effort and never blocks a request.
|
|
141
147
|
|
|
@@ -156,7 +156,8 @@ the attempt row records the id the program reports it served.
|
|
|
156
156
|
**The CLI must be new enough for the model.** Each release carries its own list of accepted models,
|
|
157
157
|
and one that predates a model refuses it outright while the tier alias goes on serving the previous
|
|
158
158
|
generation — a 400 at the first turn, or a run that quietly used an older model. Setup installs
|
|
159
|
-
`2.1.
|
|
159
|
+
`2.1.293` or newer for this reason, the first release whose `haiku` reaches Claude Haiku 5.5 (`opus` and
|
|
160
|
+
`sonnet` reached 5.5 from `2.1.284`); a copy already on the machine is used as it is, so `doctor`'s
|
|
160
161
|
version is the one to read before assuming which model a tier reaches.
|
|
161
162
|
|
|
162
163
|
**Anything else throws.** There is no regex fall-through to sonnet and no cross-provider
|
|
@@ -190,7 +191,7 @@ timeout shot, warm patch, backoff), the lane-wedge re-dispatch, and the rate-lim
|
|
|
190
191
|
| `CLEAROTRON_AI_BILLING` | `subscription` (default) \| `api-key` \| `cloud` | Billing mode for the selected engine. ONE variable for both, and only the LIVE engine's setting is read — it fills each engine's billing knob, and the engine that is not selected is never consulted. **`anthropic-agent`:** subscription **deletes `ANTHROPIC_API_KEY` from the child env** so `claude -p` uses OAuth subscription credentials (a present key would override them); `api-key` keeps the key — the scale setting and standing fallback; **`cloud`** bills Claude per use through the reader's own cloud account, named by the Claude program's own switch — `CLAUDE_CODE_USE_VERTEX`, `CLAUDE_CODE_USE_FOUNDRY` or `CLAUDE_CODE_USE_BEDROCK`, or `ANTHROPIC_BASE_URL` alone for a gateway (see the credentials table below) — and deletes `ANTHROPIC_API_KEY` as subscription does. **`openai-agent`:** subscription seeds `auth.json` into the per-run `CODEX_HOME` from `CLEAROTRON_OPENAI_AUTH_FILE` (default `~/.codex/auth.json`) and strips API keys; `api-key` keeps `CODEX_API_KEY`; `cloud` is refused. **Fail-loud, never a guess, never the subscription in its place:** `api-key` with no `ANTHROPIC_API_KEY` / `CODEX_API_KEY` throws; on `anthropic-agent`, `cloud` with two switches on, or with none and no `ANTHROPIC_BASE_URL`, throws, and so does a switch left on under `subscription` or `api-key`, because the program bills that cloud whatever this says; a value that is none of the three throws on both engines. `clearotron start` and `clearotron doctor` report each refusal against the settings the services read, naming a value that is not a billing mode by its setting and never quoting it, and the runner refuses the search when it is ordered, before anything is spent. The resolved mode is stamped on every stage telemetry row (`engine`, `authMode`, `apiBilled`, and `cloud`, null when no cloud bills), and the attempt row also carries `providerReported`, the program's own word for who served the turn. |
|
|
191
192
|
| `CLEAROTRON_CLAUDE_PATH` · `CLEAROTRON_CODEX_PATH` | `claude` / `codex` on `PATH`, then the copy Clearotron installed | The engine binary to spawn, ONE PER ENGINE: `CLEAROTRON_CLAUDE_PATH` under `anthropic-agent`, `CLEAROTRON_CODEX_PATH` under `openai-agent`. Only the live engine's is read, and a machine that runs both sets both — they were one variable until it met a machine needing two different paths. A path set here must be **absolute**: stage subprocesses run with cwd set to the run directory, so a relative path does not resolve there, and `clearotron doctor` refuses one. **Unset**, or set to the bare word `claude` / `codex` (which means the same), the engine uses the program on `PATH` when the machine has one, and otherwise the copy Clearotron installed in `~/.local/share/clearotron/engines`: `clearotron install` offers to put the chosen engine's program there (`@anthropic-ai/claude-code` or `@openai/codex`, this platform's build only), and `clearotron update` refreshes it. Nothing is bundled into the package. A value naming anything else is used as given and never falls back. `clearotron doctor` says which copy it found, and its version when npm installed that copy. |
|
|
192
193
|
| `CLEAROTRON_OPENAI_AUTH_FILE` | `~/.codex/auth.json` | The credentials file seeded into the per-run `CODEX_HOME` under subscription billing. Was exempted from the August 2026 rename as an `openai-agent` internal rather than an install-surface name; a decision of 2026-09-04 **reversed that exemption** and renamed the whole namespace, so it carries the house prefix like everything else. One spelling, no exceptions. |
|
|
193
|
-
| `CLEAROTRON_OPENAI_MODEL_JUDGMENT` / `CLEAROTRON_OPENAI_MODEL_SWEEP` / `CLEAROTRON_OPENAI_MODEL_CHEAP` | `gpt-5.6-sol` / `gpt-5.6-terra` / `gpt-5.6-luna
|
|
194
|
+
| `CLEAROTRON_OPENAI_MODEL_JUDGMENT` / `CLEAROTRON_OPENAI_MODEL_SWEEP` / `CLEAROTRON_OPENAI_MODEL_CHEAP` | `gpt-6.1-sol` / `gpt-6.1-sol` / `gpt-6-luna` (from 2026-10-09; before that `gpt-5.6-sol` / `gpt-5.6-terra` / `gpt-5.6-luna`). Codex `0.159.0` or newer is required on a ChatGPT sign-in: older releases refuse `gpt-6.1-sol` with a 400. | Maps the driver's abstract `opus`/`sonnet`/`haiku` tiers onto the codex ladder, the way the anthropic engine maps onto its own (ruling 2026-09-02, superseding the single-model mapping). A tier that cannot hold its stage contract announces itself at that stage — `missing:negative-results`, `no_coverage_status_row`, `named_band_missing` in `_driver/<stage>.jsonl` — not at delivery. |
|
|
194
195
|
| ↳ why they are three, and what the old measurement still says | superseded 2026-09-02, not erased | The three defaulted to `sol` alone on a MEASUREMENT, never a placeholder: on 2026-08-11/12, with `SWEEP=gpt-5.6-terra` / `CHEAP=gpt-5.6-luna`, every codex clearance died on structural-output gates — `missing:negative-results`, `no_coverage_status_row`, `named_band_missing` — over 2 scenarios × 3 stage families, **byte-identically on retry**, so no retry budget rescued it. That measurement stands for the code and the codex CLI **of that date**, and it is why the judgment tier keeps `sol`. Three weeks of stage-contract work and several codex minor versions sit between it and the 2026-09-02 ruling that split the three. **The symptom to match against this cause is unchanged**: those same three gates, as a fail row in`_driver/<stage>.jsonl`, inside the first few dispatches of a clearance and identical on every retry — a model incapable of a contract is not flaky at it. It reads as an engine bug when the only fault is this configuration. A cheap-tier experiment still belongs in the three constants in `driver/engine/openai-agent.mjs` where a reviewer sees it, not in an untraceable env line. |
|
|
195
196
|
| `CLEAROTRON_DATABASE` | **REQUIRED — no default.** `corsearch` \| `clarivate` \| `signa` \| `euipo` \| `uspto-local` \| `free-tier` | Active register provider — one per run, set in the environment the deploy carries, in every environment including production. There is **no default**: unset resolves to `null` and every use of it throws, so a run refuses at start rather than calling a vendor nobody chose (the removed `corsearch` fallback named a vendor the deployment did not choose). Three are paid global sweeps; `euipo` (EU) and `uspto-local` (US) are free single-office sources, and `free-tier` composes those two as one register. Choosing a free value makes every territory outside its coverage a disclosed *deferred* row. Unknown ids throw loudly, and the gather layer throws at stage time for any provider without a built MCP server. |
|
|
196
197
|
| `CLEAROTRON_CODEX_SANDBOX_BYPASS` | unset | `1` runs `codex exec` with its own sandbox bypassed, for hosts where that sandbox cannot run (setup and `doctor --probe-engine` report it). It removes a defence: with it set, a stage's commands run with every permission of the account Clearotron runs as, not only in the run folder. Codex has no guard against a stage writing into the run's `_driver/` folder, with the sandbox or without it. Set it because the host forces it, never for convenience. |
|
|
@@ -300,7 +301,6 @@ because the rule is about what PRODUCT CODE reads, not about what a run reads.
|
|
|
300
301
|
|
|
301
302
|
| Var | Default | Meaning |
|
|
302
303
|
|---|---|---|
|
|
303
|
-
| `DEMO_PORT` | `18900` | Port `npx clearotron demo` serves the replayed report on. `--port` overrides it. |
|
|
304
304
|
| `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. |
|
|
305
305
|
| `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`. |
|
|
306
306
|
| `CLEAROTRON_DEMO_PROFILES` | unset | `1` makes the profile loader return the bundled demo company, 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` companies either way. Effect class `harness`; the full contract is its row in `.env.example`. |
|
|
@@ -161,7 +161,7 @@ structural, or dev seam); [dev] = dev/test seam, never set in prod.
|
|
|
161
161
|
`CLEAROTRON_AI` (anthropic-agent | openai-agent; default anthropic-agent), `CLEAROTRON_AI_BILLING`
|
|
162
162
|
(subscription|api-key|cloud; cloud is Claude only), `CLEAROTRON_CODEX_PATH`,
|
|
163
163
|
`CLEAROTRON_OPENAI_AUTH_FILE`, `CLEAROTRON_OPENAI_MODEL_JUDGMENT` / `CLEAROTRON_OPENAI_MODEL_SWEEP` /
|
|
164
|
-
`CLEAROTRON_OPENAI_MODEL_CHEAP` (
|
|
164
|
+
`CLEAROTRON_OPENAI_MODEL_CHEAP` (gpt-6.1-sol / gpt-6.1-sol / gpt-6-luna),
|
|
165
165
|
`CLEAROTRON_CLAUDE_PATH` (claude on PATH, then the copy Clearotron installed),
|
|
166
166
|
`CLEAROTRON_SYNTHESIS_MODEL` (opus), `CLEAROTRON_KNOCKOUT_MODEL` (opus),
|
|
167
167
|
`CLEAROTRON_MAX_BUDGET_USD` (unset).
|
|
@@ -294,16 +294,17 @@ node mcp-server/mint-token.mjs --scope ops --sub <principal-name> \
|
|
|
294
294
|
|
|
295
295
|
- `--sub` names the principal, and it rides into every audit line. Two integrations holding ops
|
|
296
296
|
tokens are then distinguishable in the log; one shared token makes them permanently indistinguishable.
|
|
297
|
-
- `--verbs` is
|
|
297
|
+
- `--verbs` is required for an ops token: it lists the write tools the token may call. A connector minted without `stop_run`
|
|
298
298
|
cannot call it — the check sits at the one chokepoint every tool call passes, not in each tool.
|
|
299
299
|
- `--accounts` caps the token to a set of company keys, so a trial integration can start demo
|
|
300
300
|
searches and never a real company's.
|
|
301
301
|
- The token is printed once and stored nowhere. Losing it means minting another.
|
|
302
302
|
|
|
303
303
|
**Revoking one.** Every mint prints a `jti`. Write that line into the file named by
|
|
304
|
-
`TRADEMARK_MCP_TOKEN_DENYLIST
|
|
305
|
-
|
|
306
|
-
|
|
304
|
+
`TRADEMARK_MCP_TOKEN_DENYLIST`, or into `~/.config/clearotron/token-denylist` when the setting is unset
|
|
305
|
+
(one `jti` per line, `#` comments allowed), and the next verification refuses the token. A named list
|
|
306
|
+
that is missing or unreadable refuses every key, and so does a default list that exists but cannot be
|
|
307
|
+
read. An absent default list means nothing has been revoked.
|
|
307
308
|
|
|
308
309
|
**Rotating the signing secret.** Verification accepts `TRADEMARK_MCP_TOKEN_SECRET` or
|
|
309
310
|
`TRADEMARK_MCP_TOKEN_SECRET_PREVIOUS`; minting always signs with the current one. Move the old value
|
|
@@ -10,7 +10,9 @@ How to work on the driver without breaking the properties that make it trustwort
|
|
|
10
10
|
|
|
11
11
|
- **Node ≥ 22** (every workspace `package.json` declares it; CI pins 22.23.2). The repo is an npm
|
|
12
12
|
workspace — `npm ci` at the root installs `driver`, `mcp-server`, `providers/oauth-mcp-bridge`
|
|
13
|
-
and `portal-ui` together.
|
|
13
|
+
and `portal-ui` together. An install from a git checkout updates with `npm ci --ignore-scripts` and
|
|
14
|
+
keeps development packages, because the portal is rebuilt from them. A packaged install updates
|
|
15
|
+
through `npm install --global`.
|
|
14
16
|
- **Tests**: `npm test` in the driver dir runs the *contributor* tier
|
|
15
17
|
(`scripts/test-run.mjs` → `scripts/test-fast.mjs` over `test/*.test.mjs`), which leaves out the
|
|
16
18
|
slowest end-to-end files so a first run finishes in minutes. `npm run test:full` is the
|
|
@@ -105,9 +105,12 @@ are no root units; everything is `systemd --user`.
|
|
|
105
105
|
The API-key door is a fourth process (loopback :18812) with **no Access in front** — the trade is
|
|
106
106
|
explicit: a mandatory key replaces the browser sign-in, and the mode refuses to start if anything
|
|
107
107
|
that would weaken that (the dev auth-disable knob, a missing signing secret) is also set.
|
|
108
|
-
- The staff unit is systemd-hardened (`NoNewPrivileges`,
|
|
109
|
-
audit-log path writable, `PrivateTmp`)
|
|
110
|
-
|
|
108
|
+
- The remote deployment's staff unit (`mcp-server/remote/`) is systemd-hardened (`NoNewPrivileges`,
|
|
109
|
+
`ProtectSystem=strict` with only the audit-log path writable, `PrivateTmp`). The portal, worker and two
|
|
110
|
+
assistant-door units an install runs (`driver/systemd/`) set `NoNewPrivileges`, `RestrictSUIDSGID`,
|
|
111
|
+
`LockPersonality` and `UMask=0007`, and nothing that limits where they write: they can write wherever
|
|
112
|
+
the install's account can. Both HTTP faces enforce host allowlists (DNS-rebinding protection),
|
|
113
|
+
per-identity rate limits, body caps, and auth-before-body-read.
|
|
111
114
|
- **Outbound** traffic is enumerated in [What leaves the machine](#what-leaves-the-machine) below.
|
|
112
115
|
No outbound service is given filesystem access: register, research, and case-law calls carry query
|
|
113
116
|
arguments only, and the pool and run dirs are never exposed to them.
|
|
@@ -165,17 +168,17 @@ CLI's built-in `WebFetch` (`engine/mcp/gather-config.mjs`), or the driver's stan
|
|
|
165
168
|
moment of the call, so no list here can be complete, and a deployment that needs one must enforce it at
|
|
166
169
|
the network layer.
|
|
167
170
|
|
|
168
|
-
**A
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
**A report carries its own fonts.** A report published from 0.4.0 on holds Plus Jakarta Sans and Fira
|
|
172
|
+
Code inside itself (`shared/fonts/`), and opening it contacts no other server, from disk, from an email
|
|
173
|
+
or in the portal. CI records every request a rendered report makes and fails on one that leaves
|
|
174
|
+
(`scripts/report-offline-render-check.mjs`). A report published before 0.4.0 still links its two font
|
|
175
|
+
services when opened from disk. The request carries no matter data. Registry links in the report resolve only when a reader
|
|
173
176
|
clicks one.
|
|
174
177
|
|
|
175
178
|
**The demo contacts nothing.** `bin/example.mjs` re-publishes artifacts already on disk: no `fetch` in its
|
|
176
179
|
module graph, no MCP server, no engine binary. Its two subprocesses are a `git rev-parse` build stamp
|
|
177
|
-
and the browser opener, and the portal binds `127.0.0.1`. The report it opens loads
|
|
178
|
-
|
|
180
|
+
and the browser opener, and the portal binds `127.0.0.1`. The report it opens loads nothing from
|
|
181
|
+
another server.
|
|
179
182
|
|
|
180
183
|
## Secrets
|
|
181
184
|
|
|
@@ -228,7 +231,9 @@ today — flag for the buyer's compliance review.
|
|
|
228
231
|
and the engine-local gather MCP servers add none: each is ~130 lines over a shared 120-line stdio
|
|
229
232
|
scaffolding (`driver/engine/mcp/stdio-server.mjs`) with no MCP SDK. The artifacts read layer does
|
|
230
233
|
use the SDK (`@modelcontextprotocol/sdk`, `jose`). Small surface by design.
|
|
231
|
-
-
|
|
234
|
+
- On an install from a git checkout, `clearotron update` installs with `npm ci --ignore-scripts`, so no
|
|
235
|
+
package's install script runs on the host. Development packages are kept there, because the portal is
|
|
236
|
+
rebuilt from them. If your deploy then rebuilds a native addon,
|
|
232
237
|
make that a named, reviewed step: it is the only place a lifecycle script should run on the host.
|
|
233
238
|
- Run a skill scanner over the skills tree at deploy time and abort on high/critical findings. Two
|
|
234
239
|
things to check on any new host: that the scanner is actually installed (a scanner that is absent
|
package/driver/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# clearotron-driver
|
|
2
2
|
|
|
3
|
+
## 0.4.1-beta.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fixed: A report repaired just before delivery keeps its coverage notes, such as a script that was not searched, in the report and the audit workbook.
|
|
8
|
+
- Fixed: A search too broad for the register to count is no longer reported as one that could not be run. The report shows it as too large to read, with the register's own figure.
|
|
9
|
+
- Fixed: On Signa, a crowded search over several spellings now counts each spelling alone, so a rare spelling is no longer reported as crowded.
|
|
10
|
+
- New: `npx clearotron mcp` starts Clearotron's MCP server for an assistant; with no install it answers from the demo's sample runs, and says so.
|
|
11
|
+
- Fixed: The address `clearotron start` and `clearotron demo` print for a client's assistant now answers as soon as it is printed.
|
|
12
|
+
- Fixed: The audit workbook's coverage summary names each area in words, not the engine's identifiers.
|
|
13
|
+
|
|
14
|
+
## 0.4.1-beta.0
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Fixed: A connection library is updated to close a reported weakness in how sign-in details could be sent.
|
|
19
|
+
- Fixed: A clearance started from a connected assistant under the Generic default now appears on Clearances when you choose Generic. Before, it showed only under All companies.
|
|
20
|
+
- Fixed: A register count or record that could not be fetched no longer shows the program's error message to a client. This covers the knockout workbook's count notes, the knockout report and a clearance finding card. Each shows the line the report already uses for something left open.
|
|
21
|
+
- Fixed: A knockout's completion message and email now give its rating, or the highest rating when several names were screened. They no longer use the term 'worst band'.
|
|
22
|
+
- Fixed: A client whose mark is written in another script, such as Greek, Cyrillic or Chinese, now receives the link to the report. Before, the email arrived without one.
|
|
23
|
+
- Fixed: In rare cases, a report repaired just before delivery listed one conflict as two findings. Its last finding also lost its full write-up. The conflict now stays one finding, and every finding keeps its write-up.
|
|
24
|
+
- Fixed: Reports no longer say a search "did not finish and must be re-run" when a name's non-Latin form was in fact searched. They no longer make a conclusion depend on reviewing records the search had already read in full. Knockout reports now list every territory you ordered.
|
|
25
|
+
- Fixed: The audit workbook no longer warns that the depth rules were applied to nothing on a clearance whose narrative is written in sections. Those rules govern per-finding write-ups, and such a narrative has none.
|
|
26
|
+
- Fixed: An order for the European Union now also searches each member state's own trade mark register, at no extra search cost. A national registration — in France, for example — is now found by an EU search.
|
|
27
|
+
- Before you upgrade: if you installed Codex yourself, update it to 0.159.0 or newer first. Older copies cannot use GPT-6.1 Sol, and searches on the OpenAI engine would stop at their first step.
|
|
28
|
+
- New: Searches now use the latest models: Claude Haiku 5.5, Sonnet 5.5 and Opus 5.5, and OpenAI's GPT-6.1 Sol and GPT-6 Luna. Installing or updating Clearotron brings the Claude and Codex programs up to versions that can use them.
|
|
29
|
+
- Fixed: Two different names written in Greek, Cyrillic or Chinese characters, or differing only in an accented letter, can now be screened in one knockout. Before, they were refused as duplicates.
|
|
30
|
+
- Fixed: Findings rated medium risk or higher are now checked for the owner's actual use in the same way on every company's risk ladder. That same search looks for the owner's record of oppositions and disputes, so "Likely to enforce" carries a rating instead of "not checked".
|
|
31
|
+
|
|
3
32
|
## 0.4.0
|
|
4
33
|
|
|
5
34
|
### Minor Changes
|
|
@@ -100,19 +100,39 @@ export function bindingLayersFor(territory) {
|
|
|
100
100
|
layers.push({ layer: "national", office: BX_OFFICE,
|
|
101
101
|
why: `${t} has no separate national register — Benelux (BX) is the national register for it` });
|
|
102
102
|
} else if (t === WO_OFFICE) {
|
|
103
|
-
// The international register ORDERED AS a territory.
|
|
104
|
-
//
|
|
103
|
+
// The international register ORDERED AS a territory. There is no national layer to miss, because
|
|
104
|
+
// the Madrid register IS the order. Without this a matter listing
|
|
105
105
|
// "International" (territory-codes.mjs normalizes it to WO) discloses a missing national register
|
|
106
106
|
// that does not exist — a limitation on a register the plan searched, which is the kind a reader
|
|
107
107
|
// learns to skim.
|
|
108
108
|
layers.push({ layer: "international", office: WO_OFFICE, why: "the international register, ordered directly" });
|
|
109
109
|
return Object.freeze(layers.map(Object.freeze));
|
|
110
|
-
} else if (t === EU_OFFICE
|
|
111
|
-
// The EU
|
|
112
|
-
|
|
110
|
+
} else if (t === EU_OFFICE) {
|
|
111
|
+
// The EU-wide register named by its OFFICE code, which is how a plan that is already resolved
|
|
112
|
+
// carries it. It names that register, not the territory: resolving a France plan's ["FR", "EM",
|
|
113
|
+
// "WO"] again must not turn it into a search of every member state. An EU ORDER arrives as "EU"
|
|
114
|
+
// (normalizeTerritory folds "European Union", EUTM and EUIPO there) and takes the branch below.
|
|
115
|
+
layers.push({ layer: "regional", office: EU_OFFICE, why: "the EU-wide register, named by its office code" });
|
|
113
116
|
layers.push({ layer: "international", office: WO_OFFICE,
|
|
114
117
|
why: "international registrations designating the EU bind it without a separate EU filing" });
|
|
115
118
|
return Object.freeze(layers.map(Object.freeze));
|
|
119
|
+
} else if (t === "EU") {
|
|
120
|
+
// The EU ordered AS a territory: the EU-wide register, EVERY member state's national register, and
|
|
121
|
+
// international registrations designating the EU or a member. A national right blocks use in its own
|
|
122
|
+
// member state without appearing in the EU-wide register, so an EU order that searched only EUIPO and
|
|
123
|
+
// Madrid cleared none of the countries inside it — measured 2026-10-09 on both live providers: an
|
|
124
|
+
// EU-scoped search returns no member state's national filings, and listing the members in the same
|
|
125
|
+
// request reaches them. The owner's ruling of the same day: an EU order searches the national
|
|
126
|
+
// registers too, on every provider. Benelux is one national register for three members, listed once.
|
|
127
|
+
layers.push({ layer: "regional", office: EU_OFFICE, why: "the EU-wide register, ordered directly" });
|
|
128
|
+
for (const office of new Set(EU_MEMBERS.map((m) => (BENELUX_MEMBERS.includes(m) ? BX_OFFICE : m)))) {
|
|
129
|
+
layers.push({ layer: "national", office, why: office === BX_OFFICE
|
|
130
|
+
? "Benelux (BX) is the national register of three EU members"
|
|
131
|
+
: `a national right in ${office} blocks use there without appearing in the EU-wide register` });
|
|
132
|
+
}
|
|
133
|
+
layers.push({ layer: "international", office: WO_OFFICE,
|
|
134
|
+
why: "an international registration designating the EU or a member state binds it without a separate filing" });
|
|
135
|
+
return Object.freeze(layers.map(Object.freeze));
|
|
116
136
|
} else {
|
|
117
137
|
layers.push({ layer: "national", office: t, why: `the national register of ${t}` });
|
|
118
138
|
}
|
|
@@ -215,7 +235,9 @@ export function unsearchedLayerReason(report) {
|
|
|
215
235
|
regional: "EU-wide rights",
|
|
216
236
|
international: "international registrations designating it",
|
|
217
237
|
};
|
|
218
|
-
|
|
238
|
+
// One name per KIND of register: an EU order carries a national layer per member state, and a provider
|
|
239
|
+
// that reaches only some of them would otherwise list "the national register" once per member missed.
|
|
240
|
+
const missing = [...new Set(report.unsearched.map((u) => names[u.layer] ?? u.layer))];
|
|
219
241
|
const list = missing.length === 1 ? missing[0]
|
|
220
242
|
: `${missing.slice(0, -1).join(", ")} and ${missing[missing.length - 1]}`;
|
|
221
243
|
return `Not a complete clearance for ${report.territory}: ${list} ${missing.length === 1 ? "was" : "were"} `
|