clearotron 0.4.1-beta.5 → 0.4.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/.env.example +7 -0
- package/build-info.json +2 -2
- package/docs/architecture/04-configuration-reference.md +1 -0
- package/driver/CHANGELOG.md +42 -0
- package/driver/ask-ledger.mjs +6 -1
- package/driver/band-shape.mjs +12 -4
- package/driver/close-verify.mjs +27 -8
- package/driver/engine/anthropic-agent.mjs +7 -3
- package/driver/engine/common.mjs +10 -3
- package/driver/engine/mcp/band-server.mjs +55 -12
- package/driver/engine/mcp/codex-config.mjs +24 -4
- package/driver/engine/mcp/gather-config.mjs +4 -3
- package/driver/gateway.mjs +13 -5
- package/driver/package.json +1 -1
- package/driver/pipeline.mjs +87 -31
- package/driver/predelivery-lint.mjs +1 -1
- package/driver/record-goods.mjs +30 -0
- package/driver/remedy-accounting.mjs +2 -6
- package/driver/skills/knockout-assess/SKILL.md +2 -2
- package/driver/stages-knockout.mjs +0 -13
- package/driver/stages.mjs +11 -2
- package/driver/suite-census.json +81 -3
- package/driver/verify-knockout.mjs +27 -10
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/http-smoke.mjs +2 -0
- package/mcp-server/lib/audit-view.mjs +10 -3
- package/mcp-server/lib/events.mjs +7 -0
- package/mcp-server/lib/knockout.mjs +7 -5
- 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 +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/scripts/ask-ai-render-check.mjs +1 -0
- package/scripts/authority-boundary-probe.mjs +2 -0
- package/scripts/report-header-render-check.mjs +1 -0
- package/scripts/report-offline-render-check.mjs +1 -0
- package/scripts/report-screenshot.mjs +1 -0
- package/scripts/report-sections-render-check.mjs +1 -0
- package/scripts/report-theme-render-check.mjs +1 -0
- package/scripts/revisit-render-check.mjs +1 -0
- package/scripts/settings-render-check.mjs +1 -0
- package/scripts/undeclared-key-census.mjs +1 -0
- package/scripts/verify-publishable.mjs +2 -0
- package/shared/names-in-force.mjs +1 -0
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
// So `scrubBlocks` below keeps the drop, and the raw list a client account reads is the LIVE chain.
|
|
59
59
|
|
|
60
60
|
import { scrubBody, scrubCards } from "./scrub.mjs";
|
|
61
|
+
import { INTERPRETED_EVENT_KINDS } from "./events.mjs";
|
|
61
62
|
import { accountMayReadArtifact } from "../../shared/scope.mjs";
|
|
62
63
|
import { clientFailureNote } from "../../shared/client-failure-note.mjs";
|
|
63
64
|
|
|
@@ -279,8 +280,14 @@ export function accountTrace(result) {
|
|
|
279
280
|
//
|
|
280
281
|
// The entry shape is a UNION over ~15 event kinds and classify() ends in a generic passthrough, so an
|
|
281
282
|
// event nobody has projected yet arrives here with whatever keys it was written with. Hence an allowlist
|
|
282
|
-
// over the union rather than a delete-list
|
|
283
|
-
//
|
|
283
|
+
// over the union rather than a delete-list.
|
|
284
|
+
//
|
|
285
|
+
// AND THE PASSTHROUGH ROWS DO NOT TRAVEL AT ALL. Thinned to its decision word, a passthrough row still
|
|
286
|
+
// carried the engine's own name for the event as both `kind` and `decision`: on one test run, 133 of the
|
|
287
|
+
// 158 rows a client account read were 73 such names (measured 2026-10-14). The owner's standing rule for
|
|
288
|
+
// engineering text on a client surface is to take it out and write nothing in its place, so a client
|
|
289
|
+
// account keeps only the kinds classify() interprets (INTERPRETED_EVENT_KINDS). `seq` was assigned over
|
|
290
|
+
// every event before this drop, so a poller's cursor never renumbers; the engineering view is unchanged.
|
|
284
291
|
const TIMELINE_FIELDS = ["ts", "seq", "kind", "phase", "stage", "decision", "trigger", "ok", "outputSha",
|
|
285
292
|
"changedFromPrevious", "attempt", "axes", "axis", "escalated", "verdict", "display", "cause",
|
|
286
293
|
"recovered", "count", "uris", "findings", "negatives", "audit", "snapshot", "resume"];
|
|
@@ -306,7 +313,7 @@ export function accountTimeline(result, { brandName = "The firm" } = {}) {
|
|
|
306
313
|
};
|
|
307
314
|
return {
|
|
308
315
|
...pick(result, ["runId", "state", "tier", "_note"]),
|
|
309
|
-
timeline: Array.isArray(result.timeline) ? result.timeline.map(entry) : [],
|
|
316
|
+
timeline: Array.isArray(result.timeline) ? result.timeline.filter((t) => INTERPRETED_EVENT_KINDS.has(t.kind)).map(entry) : [],
|
|
310
317
|
verdictHistory: Array.isArray(result.verdictHistory)
|
|
311
318
|
? result.verdictHistory.map((v) => pick(v, ["ts", "kind", "verdict", "stage"]))
|
|
312
319
|
: result.verdictHistory ?? null,
|
|
@@ -157,6 +157,13 @@ function classify(e, st, rat) {
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
// THE KINDS classify() INTERPRETS — one per `case` above, pinned to them by events.test.mjs. Every other event
|
|
161
|
+
// reaches the timeline through the passthrough, under the engine's own name for it. That is the engineering
|
|
162
|
+
// view's to keep; a client account's timeline keeps these kinds alone (audit-view.mjs accountTimeline).
|
|
163
|
+
export const INTERPRETED_EVENT_KINDS = new Set(["start", "axes", "stage", "skip", "failover", "attempt", "skeptic-escalation",
|
|
164
|
+
"escalation-skipped", "escalation-noop", "verdict", "verdict-2", "delivered-with-open-questions", "screen-gate-clean",
|
|
165
|
+
"screen-gate-violation", "audit", "provider-usage", "manual-rerun", "failed"]);
|
|
166
|
+
|
|
160
167
|
// A SUCCEEDED attempt is not a decision. put one `attempt` row on run.jsonl per model dispatch, and
|
|
161
168
|
// with no kind filter here every stage began emitting BOTH `attempt-succeeded` and `stage-completed` —
|
|
162
169
|
// +61 entries on 269 (+23%) on the 2026-07-29 delivered run, roughly double on a correction-heavy one, on
|
|
@@ -361,9 +361,10 @@ export function knockoutCoverage(run) {
|
|
|
361
361
|
* enumerated fifteen stages and none of them was a knockout stage — the tool could resolve nothing on this
|
|
362
362
|
* product, including the word "verdict".
|
|
363
363
|
*
|
|
364
|
-
* `knockout-assess` is chunked (`knockout-assess#N`), so a caller may name the bare stage or one chunk.
|
|
364
|
+
* `knockout-assess` is chunked (`knockout-assess#N`), so a caller may name the bare stage or one chunk. The
|
|
365
|
+
* reviewing pass that follows it is a stage of its own, and its rewrites reach the record a reader receives.
|
|
365
366
|
*/
|
|
366
|
-
export const KNOCKOUT_STAGES = ["knockout-frame", "knockout-sweep", "knockout-register", "knockout-assess"];
|
|
367
|
+
export const KNOCKOUT_STAGES = ["knockout-frame", "knockout-sweep", "knockout-register", "knockout-assess", "knockout-review"];
|
|
367
368
|
|
|
368
369
|
export function knockoutStageFor(target) {
|
|
369
370
|
const t = String(target ?? "").trim();
|
|
@@ -404,7 +405,7 @@ export function traceKnockout(run, target, events = []) {
|
|
|
404
405
|
events: rows,
|
|
405
406
|
// What the stage wrote, by presence — the honest answer to "did this stage produce anything".
|
|
406
407
|
produced: knockoutArtifacts(run).filter((a) => a.exists && STAGE_OUTPUTS[bare]?.includes(a.name)),
|
|
407
|
-
note:
|
|
408
|
+
note: KNOCKOUT_STAGE_NOTES[bare] ?? null,
|
|
408
409
|
};
|
|
409
410
|
}
|
|
410
411
|
|
|
@@ -436,9 +437,10 @@ const STAGE_OUTPUTS = {
|
|
|
436
437
|
"knockout-assess": ["findings", "assessment"],
|
|
437
438
|
};
|
|
438
439
|
|
|
439
|
-
|
|
440
|
+
// A stage's note is read by the connector's model, which answers clients from it. A stage with no note says
|
|
441
|
+
// nothing rather than something untrue: the sweep's note said two research calls per mark, and it makes one.
|
|
442
|
+
export const KNOCKOUT_STAGE_NOTES = {
|
|
440
443
|
"knockout-frame": "Reads the order and writes the plan: which names, which classes, at what depth.",
|
|
441
|
-
"knockout-sweep": "Two research calls per mark against the marketplace/common-law provider: the broad question, and the kinds of use the frame named for the client's field; the payloads land in research/.",
|
|
442
444
|
"knockout-register": "Fetches and counts register filings for the searched names. Absent on a run with no register component.",
|
|
443
445
|
"knockout-assess": "Rates each mark from its own payload and the fetched filings, and writes knockout-findings.json.",
|
|
444
446
|
};
|
package/mcp-server/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trademark-artifacts-mcp",
|
|
3
|
-
"version": "0.4.1
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"private": true,
|
|
6
6
|
"description": "MCP server to interrogate clearotron trademark-clearance runs — list/read artifacts, trace the full decision flow, telemetry/cost, coverage, single-run search, and a gated single-step what-if. Imports the clearotron-driver read-only; touches no driver/template/deploy files.",
|
package/package.json
CHANGED
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.4.1
|
|
5
|
+
"version": "0.4.1",
|
|
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": {
|
|
@@ -202,6 +202,7 @@ const wsUrl = await new Promise((resolve, reject) => {
|
|
|
202
202
|
() => reject(new Error(`chrome did not report a devtools endpoint within 60s. Its stderr was:\n${devtools || '(nothing)'}`)),
|
|
203
203
|
60000,
|
|
204
204
|
)
|
|
205
|
+
chrome.stderr.setEncoding("utf8");
|
|
205
206
|
chrome.stderr.on('data', (c) => {
|
|
206
207
|
devtools += c
|
|
207
208
|
const m = devtools.match(/ws:\/\/[^\s]+/)
|
|
@@ -78,7 +78,9 @@ const program = resolveEngineProgram("anthropic-agent");
|
|
|
78
78
|
const child = spawn(program.resolved ?? program.bin, [...args, "--include-hook-events"],
|
|
79
79
|
{ stdio: ["pipe", "pipe", "pipe"], cwd: runDir, env: spawnEnv() });
|
|
80
80
|
let out = "", err = "";
|
|
81
|
+
child.stdout.setEncoding("utf8");
|
|
81
82
|
child.stdout.on("data", (d) => { out += d; });
|
|
83
|
+
child.stderr.setEncoding("utf8");
|
|
82
84
|
child.stderr.on("data", (d) => { err += d; });
|
|
83
85
|
child.stdin.on("error", () => {});
|
|
84
86
|
child.stdin.write(input); child.stdin.end();
|
|
@@ -85,6 +85,7 @@ reapOnExit(chrome)
|
|
|
85
85
|
const wsUrl = await new Promise((resolve, reject) => {
|
|
86
86
|
let devtools = ''
|
|
87
87
|
const t = setTimeout(() => reject(new Error(`chrome reported no devtools endpoint in 60s:\n${devtools || '(nothing)'}`)), 60000)
|
|
88
|
+
chrome.stderr.setEncoding("utf8");
|
|
88
89
|
chrome.stderr.on('data', (c) => { devtools += c; const m = devtools.match(/ws:\/\/[^\s]+/); if (m) { clearTimeout(t); resolve(m[0]) } })
|
|
89
90
|
})
|
|
90
91
|
const ws = new WebSocket(wsUrl)
|
|
@@ -81,6 +81,7 @@ reapOnExit(chrome);
|
|
|
81
81
|
const wsUrl = await new Promise((resolve, reject) => {
|
|
82
82
|
let devtools = "";
|
|
83
83
|
const t = setTimeout(() => reject(new Error(`chrome reported no devtools endpoint in 60s:\n${devtools || "(nothing)"}`)), 60000);
|
|
84
|
+
chrome.stderr.setEncoding("utf8");
|
|
84
85
|
chrome.stderr.on("data", (c) => { devtools += c; const m = devtools.match(/ws:\/\/[^\s]+/); if (m) { clearTimeout(t); resolve(m[0]); } });
|
|
85
86
|
});
|
|
86
87
|
const ws = new WebSocket(wsUrl);
|
|
@@ -56,6 +56,7 @@ const chrome = spawn("google-chrome", [
|
|
|
56
56
|
let stderr = "";
|
|
57
57
|
const wsUrl = await new Promise((res, rej) => {
|
|
58
58
|
const t = setTimeout(() => rej(new Error(`chrome reported no devtools endpoint in 60s. Its stderr:\n${stderr || "(nothing)"}`)), 60000);
|
|
59
|
+
chrome.stderr.setEncoding("utf8");
|
|
59
60
|
chrome.stderr.on("data", (c) => { stderr += c; const m = stderr.match(/ws:\/\/[^\s]+/); if (m) { clearTimeout(t); res(m[0]); } });
|
|
60
61
|
});
|
|
61
62
|
|
|
@@ -108,6 +108,7 @@ reapOnExit(chrome)
|
|
|
108
108
|
const wsUrl = await new Promise((resolve, reject) => {
|
|
109
109
|
let devtools = ''
|
|
110
110
|
const t = setTimeout(() => reject(new Error(`chrome reported no devtools endpoint in 60s:\n${devtools || '(nothing)'}`)), 60000)
|
|
111
|
+
chrome.stderr.setEncoding("utf8");
|
|
111
112
|
chrome.stderr.on('data', (c) => { devtools += c; const m = devtools.match(/ws:\/\/[^\s]+/); if (m) { clearTimeout(t); resolve(m[0]) } })
|
|
112
113
|
})
|
|
113
114
|
const ws = new WebSocket(wsUrl)
|
|
@@ -116,6 +116,7 @@ reapOnExit(chrome)
|
|
|
116
116
|
const wsUrl = await new Promise((resolve, reject) => {
|
|
117
117
|
let devtools = ''
|
|
118
118
|
const t = setTimeout(() => reject(new Error(`chrome reported no devtools endpoint in 60s:\n${devtools || '(nothing)'}`)), 60000)
|
|
119
|
+
chrome.stderr.setEncoding("utf8");
|
|
119
120
|
chrome.stderr.on('data', (c) => { devtools += c; const m = devtools.match(/ws:\/\/[^\s]+/); if (m) { clearTimeout(t); resolve(m[0]) } })
|
|
120
121
|
})
|
|
121
122
|
const ws = new WebSocket(wsUrl)
|
|
@@ -210,6 +210,7 @@ const wsUrl = await new Promise((resolve, reject) => {
|
|
|
210
210
|
() => reject(new Error(`chrome did not report a devtools endpoint within 60s. Its stderr was:\n${devtools || '(nothing)'}`)),
|
|
211
211
|
60000,
|
|
212
212
|
)
|
|
213
|
+
chrome.stderr.setEncoding("utf8");
|
|
213
214
|
chrome.stderr.on('data', (c) => {
|
|
214
215
|
devtools += c
|
|
215
216
|
const m = devtools.match(/ws:\/\/[^\s]+/)
|
|
@@ -129,6 +129,7 @@ reapOnExit(chrome)
|
|
|
129
129
|
let devtools = ''
|
|
130
130
|
const wsUrl = await new Promise((resolve, reject) => {
|
|
131
131
|
const t = setTimeout(() => reject(new Error(`chrome did not report a devtools endpoint within 60s. Its stderr was:\n${devtools || '(nothing)'}`)), 60000)
|
|
132
|
+
chrome.stderr.setEncoding("utf8");
|
|
132
133
|
chrome.stderr.on('data', (c) => {
|
|
133
134
|
devtools += c
|
|
134
135
|
const m = devtools.match(/ws:\/\/[^\s]+/)
|
|
@@ -26,6 +26,7 @@ const MCP = new URL("../driver/engine/mcp/", import.meta.url).pathname;
|
|
|
26
26
|
const SERVERS=["recording-server.mjs","coverage-server.mjs","dispositions-server.mjs","declination-server.mjs","unit-note-server.mjs"];
|
|
27
27
|
const ask=(s)=>new Promise((res)=>{const c=spawn(process.execPath,[join(MCP,s)],{stdio:["pipe","pipe","pipe"],env:{...process.env,PERPLEXITY_API_KEY:"x"}});
|
|
28
28
|
let b="",o=null;const d=()=>{try{c.kill("SIGKILL")}catch{};res(o||[])};const t=setTimeout(d,8000);
|
|
29
|
+
c.stdout.setEncoding("utf8");
|
|
29
30
|
c.stdout.on("data",x=>{b+=x;let n;while((n=b.indexOf("\n"))>=0){const l=b.slice(0,n).trim();b=b.slice(n+1);if(!l)continue;
|
|
30
31
|
try{const m=JSON.parse(l);if(m.id===2){o=m.result?.tools??[];clearTimeout(t);d();}}catch{}}});
|
|
31
32
|
c.on("error",()=>d());
|
|
@@ -174,7 +174,9 @@ try {
|
|
|
174
174
|
// this deliberately no longer starts.
|
|
175
175
|
if (/report:\s+\S/.test(out) && /published:/.test(out)) { clearTimeout(t); finish(true); }
|
|
176
176
|
};
|
|
177
|
+
child.stdout.setEncoding("utf8");
|
|
177
178
|
child.stdout.on("data", read);
|
|
179
|
+
child.stderr.setEncoding("utf8");
|
|
178
180
|
child.stderr.on("data", read);
|
|
179
181
|
child.on("error", (e) => { clearTimeout(t); finish(false, `demo could not start: ${e.message}`); });
|
|
180
182
|
});
|