clearotron 0.3.2-beta.4 → 0.3.2-beta.5
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/INSTALL.md +1 -0
- package/bin/onboard.mjs +65 -0
- package/bin/start.mjs +14 -0
- package/build-info.json +2 -2
- package/demo/knockout-search/run/_driver/predelivery-lint.json +26 -2
- package/driver/CHANGELOG.md +6 -0
- package/driver/package.json +1 -1
- package/driver/pipeline.mjs +4 -2
- package/driver/publish/render-knockout.mjs +231 -98
- package/driver/publish/render.mjs +399 -152
- package/driver/publish/templates/report.css +103 -3
- package/driver/registry-fidelity.mjs +28 -0
- package/driver/suite-census.json +17 -17
- package/mcp-server/CHANGELOG.md +4 -0
- package/mcp-server/lib/audit.mjs +14 -2
- package/mcp-server/lib/http-handler.mjs +52 -13
- package/mcp-server/lib/scrub.mjs +1 -1
- 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/driver-dir.mjs +1 -1
package/INSTALL.md
CHANGED
|
@@ -331,6 +331,7 @@ terminal and is rewritten on every start.
|
|
|
331
331
|
~/trademark/config/profiles the company profiles
|
|
332
332
|
~/trademark/grants.json who may use this install
|
|
333
333
|
~/trademark/portal-audit.log what the portal was asked to do
|
|
334
|
+
~/trademark/telemetry/trademark-mcp-access.jsonl what a connected assistant asked for, and what it was told
|
|
334
335
|
~/trademark/portal-local-credential.json the sign-in this install minted for you
|
|
335
336
|
```
|
|
336
337
|
|
package/bin/onboard.mjs
CHANGED
|
@@ -2702,6 +2702,71 @@ export async function runCheck() {
|
|
|
2702
2702
|
+ `Run \`${invoke("start")}\` — it creates the list.`);
|
|
2703
2703
|
}
|
|
2704
2704
|
}
|
|
2705
|
+
// ── IS THIS DOOR'S ACCESS LOG BEING WRITTEN, AND WHERE? ───────────────────────────────────────────
|
|
2706
|
+
//
|
|
2707
|
+
// A client's assistant can list runs, read reports and pull artifacts through this door. The record of
|
|
2708
|
+
// what a key did is the only way to answer a client, a lawyer or an incident review afterwards, and a
|
|
2709
|
+
// log nobody can find is a log nobody checks.
|
|
2710
|
+
//
|
|
2711
|
+
// THE OVERRIDE IS READ FROM THE UNIT, NOT FROM THIS PROCESS. `TRADEMARK_MCP_AUDIT_LOG` decides where
|
|
2712
|
+
// the writer appends, and the writer is the service — so this command's own environment is not the
|
|
2713
|
+
// answer and using it would produce a confident report about a file the door never touches. Where the
|
|
2714
|
+
// two disagree, that disagreement is the finding and is said out loud rather than resolved silently.
|
|
2715
|
+
//
|
|
2716
|
+
// WITH NO OVERRIDE THERE ARE TWO CANDIDATES, and which one the writer picked depends on what existed
|
|
2717
|
+
// when IT started — a legacy path is kept if it is already there, so that a compliance trail is not
|
|
2718
|
+
// split in two by a moved default. `auditPaths()` is the module's own reader for exactly this and is
|
|
2719
|
+
// called rather than re-derived; re-deriving that branch here is the defect its source warns about.
|
|
2720
|
+
try {
|
|
2721
|
+
const { auditPaths: auditCandidates } = await import(pathToFileURL(join(REPO, "mcp-server", "lib", "audit.mjs")).href);
|
|
2722
|
+
const seen = hosted ? unitValue(unitEnv, "TRADEMARK_MCP_AUDIT_LOG") : { state: "unset", value: null };
|
|
2723
|
+
if (seen.state === "unknown") {
|
|
2724
|
+
info(couldNotDetermine("TRADEMARK_MCP_AUDIT_LOG", unitEnv) + " The access log's location cannot be named.");
|
|
2725
|
+
} else {
|
|
2726
|
+
const mine = String(process.env.TRADEMARK_MCP_AUDIT_LOG ?? "").trim();
|
|
2727
|
+
if (seen.state === "unset" && mine) {
|
|
2728
|
+
warn(`this shell sets TRADEMARK_MCP_AUDIT_LOG and the units do not, so the door is NOT writing where `
|
|
2729
|
+
+ `this command would look. The door's log is at its default; ${mine} is this shell's answer only.`);
|
|
2730
|
+
}
|
|
2731
|
+
const paths = seen.state === "set" ? [seen.value] : auditCandidates();
|
|
2732
|
+
let wrote = null;
|
|
2733
|
+
for (const p of paths) {
|
|
2734
|
+
try { const s = statSync(p); if (s.size > 0) { wrote = p; break; } } catch { /* not this one */ }
|
|
2735
|
+
}
|
|
2736
|
+
if (wrote) {
|
|
2737
|
+
ok(`the client door's access log is being written: ${wrote}`);
|
|
2738
|
+
} else {
|
|
2739
|
+
// NOT-YET-WRITTEN IS NOT UNWRITABLE. A door nobody has called through has no log, and saying it
|
|
2740
|
+
// is broken would be the same lie as calling an unreachable unit inactive.
|
|
2741
|
+
//
|
|
2742
|
+
// NAME THE ONE THE WRITER WOULD PICK, WHICH IS NOT SIMPLY THE FIRST. With no override there are
|
|
2743
|
+
// two candidates and the older is kept only when it is ALREADY THERE, so that a compliance trail
|
|
2744
|
+
// is not split by a moved default. Naming the older one unconditionally would print the
|
|
2745
|
+
// integrator platform's folder at setup on installs that have no such file — a folder this engine
|
|
2746
|
+
// does not require and whose mention was deliberately removed from this command. Where it does
|
|
2747
|
+
// exist it IS the log and is named, because that is then the true answer.
|
|
2748
|
+
const existing = paths.find((c) => { try { statSync(c); return true; } catch { return false; } });
|
|
2749
|
+
const willUse = existing ?? paths[paths.length - 1];
|
|
2750
|
+
const dir = dirname(willUse);
|
|
2751
|
+
let writable = null;
|
|
2752
|
+
try { accessSync(dir, constants.W_OK); writable = true; } catch (e) { writable = e?.code === "ENOENT" ? null : false; }
|
|
2753
|
+
if (writable === false) {
|
|
2754
|
+
blocking(`the client door's access log cannot be written: ${dir} is not writable by this user. `
|
|
2755
|
+
+ "Every call through this door would go unrecorded, and an absent record reads as nothing happened.");
|
|
2756
|
+
} else if (writable === null) {
|
|
2757
|
+
info(`no access log yet at ${willUse} and its directory does not exist — the door creates it on the `
|
|
2758
|
+
+ "first call. Nothing has come through it, or nothing has since the path last changed.");
|
|
2759
|
+
} else {
|
|
2760
|
+
info(`no access log yet at ${willUse}, and its directory is writable — nothing has come through `
|
|
2761
|
+
+ "this door yet. The first call creates it.");
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
} catch (e) {
|
|
2766
|
+
info(`could not check the client door's access log (${e?.code ?? e?.message ?? e}) — this is a report `
|
|
2767
|
+
+ "that this command could not look, not that the log is missing.");
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2705
2770
|
try {
|
|
2706
2771
|
const { clientDoorState: doorState, describeDoorState, connectKeyReport, CLIENT_DOOR_UNIT: doorUnit } = await import(pathToFileURL(join(REPO, "shared", "client-door.mjs")).href);
|
|
2707
2772
|
const { loadGrants: readGrantsFile, isRevoked: revokedCheck } = await import(pathToFileURL(join(REPO, "shared", "scope.mjs")).href);
|
package/bin/start.mjs
CHANGED
|
@@ -449,6 +449,10 @@ export function installPaths(base) {
|
|
|
449
449
|
locks: join(base, "locks"),
|
|
450
450
|
grants: join(base, "grants.json"),
|
|
451
451
|
audit: join(base, "portal-audit.log"),
|
|
452
|
+
// The connector doors' access log — the record of what a client key did, which is what answers a
|
|
453
|
+
// client or a review afterwards. NAMED IN THE LAYOUT rather than built at the call site, for the
|
|
454
|
+
// reason the store below gives: a demo's isolation must be a property of where things live.
|
|
455
|
+
accessLog: join(base, "telemetry", "trademark-mcp-access.jsonl"),
|
|
452
456
|
// The saved-search store is a git repository because recipe-service commits every save through
|
|
453
457
|
// `git add` + `git commit` — that is the product's config-store model, not something invented here.
|
|
454
458
|
// It is its own directory, NOT `base`, so the commit path never sees the pool, the queue or the
|
|
@@ -680,6 +684,16 @@ export function childEnv({ ports, paths, user, portalSecret, tokenSecret, opsTok
|
|
|
680
684
|
"PROFILE_AUDIT": "",
|
|
681
685
|
"RECIPE_AUDIT": "",
|
|
682
686
|
"CLEAROTRON_FEEDBACK_DIR": "",
|
|
687
|
+
// AN EXPLICIT PATH, NOT "" LIKE THE TWO ABOVE. Empty is unset, and unset is what sends this one to
|
|
688
|
+
// the REAL install's home: its default is computed from `homedir()`, not from any directory a demo
|
|
689
|
+
// controls. So a demo left to fall back writes its access lines into the install beside it, and the
|
|
690
|
+
// demo stops being one directory a reader can delete. Measured as exactly that: a demo run left
|
|
691
|
+
// `telemetry/trademark-mcp-access.jsonl` in the install it booted next to.
|
|
692
|
+
//
|
|
693
|
+
// ONLY IN A DEMO. A real install must NOT have this pinned: with no override the writer keeps an
|
|
694
|
+
// older log where one already exists, so that moving the default does not split a compliance trail
|
|
695
|
+
// in two. Pinning the name here would override that and do the splitting.
|
|
696
|
+
"TRADEMARK_MCP_AUDIT_LOG": paths.accessLog,
|
|
683
697
|
} : {}),
|
|
684
698
|
});
|
|
685
699
|
return {
|
package/build-info.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"ts": "2026-09-
|
|
2
|
+
"ts": "2026-09-16T16:48:57.832Z",
|
|
3
3
|
"lane": "knockout",
|
|
4
4
|
"checks": [
|
|
5
5
|
{
|
|
@@ -43,9 +43,33 @@
|
|
|
43
43
|
"surface": "findings",
|
|
44
44
|
"pass": true,
|
|
45
45
|
"detail": ""
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "reviewer-note-subject",
|
|
49
|
+
"family": "voice",
|
|
50
|
+
"surface": "findings",
|
|
51
|
+
"pass": true,
|
|
52
|
+
"detail": ""
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "plain-language-vocabulary",
|
|
56
|
+
"family": "voice",
|
|
57
|
+
"surface": "findings",
|
|
58
|
+
"pass": false,
|
|
59
|
+
"detail": "the lawyer's vocabulary on lines a reader meets before opening anything — rewrite the sentence in the words the reader already owns, do not swap the word: the batch summary: \"belt-and-braces\" → \"extra\"; the batch summary: \"chunk\" (an engine word — cut it)"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "plain-language-sentence-length",
|
|
63
|
+
"family": "voice",
|
|
64
|
+
"surface": "findings",
|
|
65
|
+
"pass": false,
|
|
66
|
+
"detail": "a default-visible sentence carrying more than one idea (over 25 words) — split it, conclusion first: the batch summary: 28 words; the batch summary: 28 words; the batch summary: 27 words; the batch summary: 39 words; the batch summary: 26 words; +4 more"
|
|
46
67
|
}
|
|
47
68
|
],
|
|
48
|
-
"failures": [
|
|
69
|
+
"failures": [
|
|
70
|
+
"plain-language-vocabulary",
|
|
71
|
+
"plain-language-sentence-length"
|
|
72
|
+
],
|
|
49
73
|
"notApplicable": [
|
|
50
74
|
{
|
|
51
75
|
"id": "names-cell-populated",
|
package/driver/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# clearotron-driver
|
|
2
2
|
|
|
3
|
+
## 0.3.2-beta.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a4ea94d: For operators: The connector access log now records what happened to each call, and records calls that were refused as well as calls that got through. A line also names which door took the call, so a client key and a staff session can be told apart. Before, a call was recorded only as having been made, and a refused one left no line at all. `doctor` now names where that log is being written. It reads that from the service's own settings, not from the shell you are typing in.
|
|
8
|
+
|
|
3
9
|
## 0.3.2-beta.4
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
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.2-beta.
|
|
5
|
+
"version": "0.3.2-beta.5",
|
|
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/pipeline.mjs
CHANGED
|
@@ -6437,8 +6437,10 @@ function injectFrontMatter(path, fields, runDir = null) {
|
|
|
6437
6437
|
// spec 62 — the per-field CONFIGURATION PROVENANCE rows for a project run: for each PROJECT_KEY, its effective
|
|
6438
6438
|
// value (compact) and where it came from (project | customer | house). Read off the frozen sidecar `pr` (both
|
|
6439
6439
|
// origins and the effective values live there), serialised to a single-line JSON string for the report
|
|
6440
|
-
// front-matter
|
|
6441
|
-
//
|
|
6440
|
+
// front-matter. It was rendered internal-only in the scope fold; the 2026-09-16 redesign takes that fold
|
|
6441
|
+
// off the page, and this row set went with it — it was the one block in there labelled internal, which is
|
|
6442
|
+
// the class that ruling removed. IT IS STILL RECORDED, here and in the workbook, which is where a reader
|
|
6443
|
+
// who wants to know which layer set a knob now finds out. Absent origins ⇒ [] ⇒ nothing injected.
|
|
6442
6444
|
function originRowsForReport(pr) {
|
|
6443
6445
|
if (!pr?.origins) return [];
|
|
6444
6446
|
const short = (s, n = 48) => { const t = String(s ?? "").trim(); return t.length > n ? `${t.slice(0, n - 1)}…` : t; };
|