clearotron 0.3.2-beta.6 → 0.3.2-beta.8
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 +24 -23
- package/INSTALL.md +142 -75
- package/README.md +3 -3
- package/bin/onboard.mjs +637 -216
- package/bin/start.mjs +133 -23
- package/bin/update.mjs +82 -11
- package/build-info.json +2 -2
- package/docs/architecture/04-configuration-reference.md +26 -11
- package/docs/architecture/05-config-governance.md +17 -7
- package/driver/CHANGELOG.md +83 -0
- package/driver/band-size.mjs +59 -0
- package/driver/config-inventory.mjs +112 -9
- package/driver/connotation-search.mjs +45 -0
- package/driver/contract-arm2-baseline.json +1 -3
- package/driver/contract-e3-backlog.mjs +29 -29
- package/driver/contract-vocabulary.mjs +59 -25
- package/driver/door-gates.mjs +41 -7
- package/driver/driver.config.mjs +272 -59
- package/driver/engine/CONTRACT.md +10 -3
- package/driver/engine/README.md +2 -2
- package/driver/engine/anthropic-agent.mjs +77 -21
- package/driver/engine/auth.mjs +129 -10
- package/driver/engine/jx-turn.mjs +7 -6
- package/driver/engine/mcp/recording-server.mjs +13 -0
- package/driver/engine/openai-agent.mjs +4 -2
- package/driver/engine/probe.mjs +110 -23
- package/driver/findings-model.mjs +1 -1
- package/driver/flag-snapshot.mjs +28 -5
- package/driver/gateway.mjs +30 -21
- package/driver/jx-lanes.mjs +21 -2
- package/driver/jx-units.mjs +6 -3
- package/driver/jx.mjs +4 -2
- package/driver/matter-frame-record.mjs +90 -1
- package/driver/named-band.mjs +34 -2
- package/driver/package.json +1 -1
- package/driver/pipeline.mjs +391 -26
- package/driver/portal-config-view.mjs +30 -1
- package/driver/portal-report.mjs +15 -1
- package/driver/portal-service.mjs +46 -6
- package/driver/predelivery-lint.mjs +12 -2
- package/driver/publish/index.mjs +46 -5
- package/driver/publish/knockout.mjs +10 -1
- package/driver/publish/render-knockout.mjs +69 -7
- package/driver/publish/render.mjs +170 -59
- package/driver/publish/report-data.mjs +4 -1
- package/driver/publish/report-topbar.mjs +58 -0
- package/driver/publish/templates/report.css +28 -2
- package/driver/publish/xlsx.mjs +13 -1
- package/driver/register-availability.mjs +2 -2
- package/driver/register-coverage.mjs +94 -1
- package/driver/register-digest-record.mjs +236 -11
- package/driver/register-plan.mjs +170 -0
- package/driver/result-noun-fields.mjs +7 -4
- package/driver/run-economics.mjs +41 -10
- package/driver/run-requirements.mjs +173 -9
- package/driver/runner.mjs +3 -3
- package/driver/stages.mjs +12 -8
- package/driver/suite-census.json +162 -72
- package/driver/systemd/README.md +7 -4
- package/driver/terminal-clamp.mjs +107 -1
- package/driver/tokens.mjs +169 -3
- package/driver/unit-environment.mjs +42 -15
- package/driver/unit-inventory.mjs +19 -2
- package/driver/verify.mjs +50 -5
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/http-server.mjs +4 -0
- package/mcp-server/lib/audit.mjs +11 -1
- package/mcp-server/lib/http-handler.mjs +6 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +16 -2
- package/package.json +1 -1
- package/portal-ui/dist/assets/{index-5UyqAyNM.js → index-6jzO9HiX.js} +155 -79
- package/portal-ui/dist/index.html +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/clarivate/src/capabilities.js +5 -5
- package/providers/clarivate/src/core.js +1 -1
- package/providers/corsearch/src/core.js +2 -2
- package/providers/jx/README.md +2 -1
- package/providers/jx/src/turn-envelope.mjs +8 -3
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/perplexity/src/core.js +3 -3
- package/providers/signa/src/capabilities.js +5 -6
- package/providers/signa/src/core.js +1 -1
- package/providers/uspto-local/README.md +1 -1
- package/scripts/authority-boundary-probe.mjs +4 -2
- package/scripts/env-audit.mjs +12 -6
- package/scripts/freeze-example-run.mjs +49 -16
- package/scripts/generated-files-are-current.mjs +69 -4
- package/scripts/release-duplicate-notes.mjs +246 -0
- package/scripts/release-publish-guard.mjs +64 -6
- package/scripts/report-print-check.mjs +194 -0
- package/scripts/settings-render-check.mjs +75 -2
- package/scripts/test-full.mjs +96 -3
- package/scripts/test-run.mjs +10 -0
- package/shared/deployment-box.mjs +7 -2
- package/shared/driver-dir.mjs +1 -1
- package/shared/names-in-force.mjs +1 -1
package/scripts/test-full.mjs
CHANGED
|
@@ -166,10 +166,89 @@ export function plan(root = ROOT, { providerFiles } = {}) {
|
|
|
166
166
|
return { corpora, faults };
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
/**
|
|
170
|
+
* ── SHARDING, AND WHY IT CANNOT SILENTLY DROP A FILE ──────────────────────────────────────────────
|
|
171
|
+
*
|
|
172
|
+
* The offline suite was the whole wall clock of CI: 12.8 minutes against 4.3 for the portal bundle and
|
|
173
|
+
* under one each for the other two jobs, which run beside it. Nearly all of that is `driver`, 925 of the
|
|
174
|
+
* ~980 test files in the tree, executed in one serial job. Agents write faster than that gate opens, so
|
|
175
|
+
* the gate — not the work — set the pace of the day.
|
|
176
|
+
*
|
|
177
|
+
* A shard is a SLICE OF THE SORTED FILE LIST TAKEN BY INDEX: shard i of n takes every file whose
|
|
178
|
+
* position satisfies `index % n === i - 1`. That is exhaustive and disjoint by construction — every
|
|
179
|
+
* index lands in exactly one shard, for any n — so the union of the shards is the whole list as a
|
|
180
|
+
* property of the arithmetic, not as something a check has to confirm afterwards. Round-robin rather
|
|
181
|
+
* than contiguous blocks because neighbouring files in a sorted list tend to be the same subsystem and
|
|
182
|
+
* to cost the same; interleaving spreads the slow ones.
|
|
183
|
+
*
|
|
184
|
+
* WHAT IS NOT SHARDED, and why that is stated rather than assumed. Only a corpus whose own test script
|
|
185
|
+
* is the plain `node --test test/*.test.mjs` shape can be expressed as an explicit file list and cut up
|
|
186
|
+
* this way. `portal-ui` runs a typecheck first and drives `.test.ts` through `--experimental-strip-types`,
|
|
187
|
+
* so it is not that shape: it runs WHOLE, on shard 1, and says so on every other shard rather than
|
|
188
|
+
* disappearing from them. Anything else that stops matching the shape falls into the same branch and is
|
|
189
|
+
* reported, never quietly skipped.
|
|
190
|
+
*/
|
|
191
|
+
const PLAIN_GLOB = /^node \.\.\/scripts\/test-run\.mjs node --test test\/\*\.test\.mjs$/;
|
|
192
|
+
|
|
193
|
+
export function shardSlice(list, i, n) {
|
|
194
|
+
return list.filter((_, idx) => idx % n === i - 1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function workspaceTestFiles(root, ws) {
|
|
198
|
+
const dir = join(root, ws, "test");
|
|
199
|
+
if (!existsSync(dir)) return [];
|
|
200
|
+
return readdirSync(dir).filter((f) => f.endsWith(".test.mjs")).sort().map((f) => `${ws}/test/${f}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Is this workspace's `test:full` the plain shape we can express as a file list? */
|
|
204
|
+
function isShardable(root, ws) {
|
|
205
|
+
try {
|
|
206
|
+
const scripts = JSON.parse(readFileSync(join(root, ws, "package.json"), "utf8")).scripts ?? {};
|
|
207
|
+
let cmd = scripts["test:full"] ?? "";
|
|
208
|
+
// `test:full` is often just `npm run test`; follow that one hop.
|
|
209
|
+
const hop = cmd.match(/^npm run (\S+)$/);
|
|
210
|
+
if (hop) cmd = scripts[hop[1]] ?? "";
|
|
211
|
+
return PLAIN_GLOB.test(cmd.trim());
|
|
212
|
+
} catch { return false; }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Rewrite the plan for one shard. Every corpus stays in the list and is reported; what changes is how
|
|
217
|
+
* many of its files THIS shard runs.
|
|
218
|
+
*/
|
|
219
|
+
export function applyShard(corpora, { i, n }, root = ROOT) {
|
|
220
|
+
return corpora.map((c) => {
|
|
221
|
+
if (c.kind === "covered") return c;
|
|
222
|
+
if (c.kind === "files") {
|
|
223
|
+
const mine = shardSlice(c.files, i, n);
|
|
224
|
+
return { ...c, files: mine, total: c.files.length,
|
|
225
|
+
argv: ["node", "scripts/test-run.mjs", "node", "--test", ...mine] };
|
|
226
|
+
}
|
|
227
|
+
if (c.kind === "workspace" && isShardable(root, c.name)) {
|
|
228
|
+
const all = workspaceTestFiles(root, c.name);
|
|
229
|
+
const mine = shardSlice(all, i, n);
|
|
230
|
+
return { ...c, kind: "files", files: mine, total: all.length,
|
|
231
|
+
argv: ["node", "scripts/test-run.mjs", "node", "--test", ...mine] };
|
|
232
|
+
}
|
|
233
|
+
// Not expressible as a file list: run it whole, once, on shard 1.
|
|
234
|
+
return { ...c, wholeOnShard: 1, skippedHere: i !== 1 };
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
169
238
|
function main() {
|
|
170
239
|
const only = (() => { const i = process.argv.indexOf("--only"); return i === -1 ? null : process.argv[i + 1]; })();
|
|
171
240
|
const listOnly = process.argv.includes("--list");
|
|
172
|
-
const
|
|
241
|
+
const shard = (() => {
|
|
242
|
+
const i = process.argv.indexOf("--shard");
|
|
243
|
+
if (i === -1) return null;
|
|
244
|
+
const m = String(process.argv[i + 1] ?? "").match(/^(\d+)\/(\d+)$/);
|
|
245
|
+
if (!m) { console.error("test-full: --shard wants i/n, e.g. --shard 2/4"); process.exit(2); }
|
|
246
|
+
const [a, b] = [Number(m[1]), Number(m[2])];
|
|
247
|
+
if (a < 1 || b < 1 || a > b) { console.error(`test-full: --shard ${a}/${b} is not a shard of anything`); process.exit(2); }
|
|
248
|
+
return { i: a, n: b };
|
|
249
|
+
})();
|
|
250
|
+
const { corpora: full, faults } = plan();
|
|
251
|
+
const corpora = shard ? applyShard(full, shard, ROOT) : full;
|
|
173
252
|
|
|
174
253
|
// FAULTS BEFORE ANYTHING RUNS. A missing corpus discovered after a green suite reads as an
|
|
175
254
|
// afterthought; discovered first, it is the answer.
|
|
@@ -195,9 +274,13 @@ function main() {
|
|
|
195
274
|
return;
|
|
196
275
|
}
|
|
197
276
|
|
|
277
|
+
if (shard) console.log(`\ntest-full: shard ${shard.i} of ${shard.n} — slices are taken by index from the sorted file list, so the shards are disjoint and their union is the whole list.`);
|
|
278
|
+
|
|
198
279
|
const ran = [];
|
|
199
280
|
for (const c of chosen) {
|
|
200
281
|
if (c.kind === "covered") { ran.push({ ...c, status: "covered" }); continue; }
|
|
282
|
+
if (c.skippedHere) { ran.push({ ...c, status: "elsewhere" }); continue; }
|
|
283
|
+
if (c.kind === "files" && c.files.length === 0) { ran.push({ ...c, status: "empty" }); continue; }
|
|
201
284
|
console.log(`\n──── ${c.name} ────`);
|
|
202
285
|
// STDIO INHERITED, NOT CAPTURED. CI reads the child stream for the corpus-guard markers, and a
|
|
203
286
|
// runner that buffered its children would take those markers out of the log the check greps.
|
|
@@ -213,13 +296,23 @@ function main() {
|
|
|
213
296
|
for (const c of ran) {
|
|
214
297
|
if (c.status === "covered") {
|
|
215
298
|
console.log(` covered ${c.name} — ${c.hasTests} file(s), run by the ${c.cover.corpus} corpus: ${c.cover.why}`);
|
|
299
|
+
} else if (c.status === "elsewhere") {
|
|
300
|
+
console.log(` shard ${c.wholeOnShard} ${c.name} — not expressible as a file list, so it runs WHOLE on shard ${c.wholeOnShard}, not here. It is not missing from this run; it is somewhere else in it.`);
|
|
301
|
+
} else if (c.status === "empty") {
|
|
302
|
+
console.log(` none ${c.name} — 0 of ${c.total} file(s) fell in this shard. Not an absence: the other shards hold them.`);
|
|
216
303
|
} else {
|
|
217
|
-
const n = c.kind === "files"
|
|
304
|
+
const n = c.kind === "files"
|
|
305
|
+
? `${c.files.length}${c.total != null ? ` of ${c.total}` : ""} file(s)`
|
|
306
|
+
: "workspace suite";
|
|
218
307
|
console.log(` ${c.status === "ok" ? "ran " : "FAILED "} ${c.name} — ${n}${c.code ? ` (exit ${c.code})` : ""}`);
|
|
219
308
|
}
|
|
220
309
|
}
|
|
221
310
|
const bad = ran.filter((c) => c.status === "FAILED");
|
|
222
|
-
console.log(bad.length
|
|
311
|
+
console.log(bad.length
|
|
312
|
+
? `\n${bad.length} corpus/corpora failed.`
|
|
313
|
+
: shard
|
|
314
|
+
? `\nEvery corpus above was run, accounted for, or named as belonging to another shard. This shard alone is not the suite: the gate is every shard green.`
|
|
315
|
+
: `\nEvery corpus above was run or accounted for.`);
|
|
223
316
|
if (bad.length) process.exit(1);
|
|
224
317
|
}
|
|
225
318
|
|
package/scripts/test-run.mjs
CHANGED
|
@@ -644,6 +644,16 @@ if (String(process.env[REAL_ENGINE_OVERRIDE] ?? "").trim()) {
|
|
|
644
644
|
catch { copyFileSync(ENGINE_STUB, p); chmodSync(p, 0o755); }
|
|
645
645
|
}
|
|
646
646
|
process.env.PATH = shimDir + delimiter + (process.env.PATH ?? "");
|
|
647
|
+
// AND THE COPY CLEAROTRON INSTALLED, which no PATH can close. The resolver's last step
|
|
648
|
+
// (driver.config.mjs resolveEngineProgram) reads the engines folder under the home directory, where
|
|
649
|
+
// setup installs `@anthropic-ai/claude-code` or `@openai/codex`, never PATH, and on a developer's machine
|
|
650
|
+
// that folder can hold a REAL program. The shim above answers every child that keeps this PATH, because
|
|
651
|
+
// PATH is asked first; a child that composes its own PATH would fall straight through to a real binary.
|
|
652
|
+
// So the folder is pointed at an EMPTY directory for the whole suite: the suite keeps the no-installed-
|
|
653
|
+
// copy world it was written for, and an arm about the installed copy plants its own folder and names it.
|
|
654
|
+
const noEngines = join(root, "no-installed-engines");
|
|
655
|
+
mkdirSync(noEngines, { recursive: true });
|
|
656
|
+
process.env.CLEAROTRON_ENGINES_DIR = noEngines;
|
|
647
657
|
// Names, never values — this line is read by whoever is wondering why a credential-reading test skipped.
|
|
648
658
|
const withheld = Object.keys(process.env)
|
|
649
659
|
.filter((n) => CREDENTIAL_RE.test(n) || CREDENTIAL_NAMES.includes(n))
|
|
@@ -16,10 +16,15 @@
|
|
|
16
16
|
// of the same allowlist agree on the day they are written and drift afterwards; that is the defect
|
|
17
17
|
// was filed about one field over, and the fix there was the same — import the function, never
|
|
18
18
|
// re-derive the answer.
|
|
19
|
-
|
|
19
|
+
// `preprod` JOINED 2026-09-17. It is a packaged install like production, on its own account, which takes
|
|
20
|
+
// every published version before production does. Until it could name itself it ran with the variable
|
|
21
|
+
// UNSET — and an unset box does not fail loudly: `live-surface-check` SKIPS its expected-units check
|
|
22
|
+
// when the box is null, so the one install that most wants that check was the one not getting it.
|
|
23
|
+
// Setting `prod` instead would have made the box lie about which box it is.
|
|
24
|
+
export const DEPLOYMENT_BOXES = ["prod", "preprod", "test"];
|
|
20
25
|
|
|
21
26
|
/**
|
|
22
|
-
* @returns {"prod"|"test"|null} the self-declared box, or null when unset or unrecognised.
|
|
27
|
+
* @returns {"prod"|"preprod"|"test"|null} the self-declared box, or null when unset or unrecognised.
|
|
23
28
|
*
|
|
24
29
|
* Read at CALL time, deliberately. `shared/brand.mjs` builds its value at module scope and that is
|
|
25
30
|
* exactly why a rename reached the report and not the portal — portal-service is outside
|
package/shared/driver-dir.mjs
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
// its own population needs an exception list — which would rebuild this issue's defect inside its fix:
|
|
36
36
|
//
|
|
37
37
|
// · `driver/stage-freshness.mjs` creates a CHILD, `join(runDir, "_driver", STAMP_DIR)`.
|
|
38
|
-
// · `driver/pipeline.mjs
|
|
38
|
+
// · `driver/pipeline.mjs shadowDir` passes a shadow dispatch sandbox under `_experiments/`, not a run
|
|
39
39
|
// directory. It is a run-dir-SHAPED base, which is why the parameter is `base` and not `runDir`.
|
|
40
40
|
//
|
|
41
41
|
// ── WHAT THIS DELIBERATELY DOES NOT DO ────────────────────────────────────────────────────────────
|
|
@@ -23,7 +23,6 @@ export const NAMES_IN_FORCE = Object.freeze([
|
|
|
23
23
|
"CLEAROTRON_AI",
|
|
24
24
|
"CLEAROTRON_AI_BILLING",
|
|
25
25
|
"CLEAROTRON_AI_PATH",
|
|
26
|
-
"CLEAROTRON_AZURE_MODEL",
|
|
27
26
|
"CLEAROTRON_BAND_RESPONSE_CHARS",
|
|
28
27
|
"CLEAROTRON_BAND_RUN_DIR",
|
|
29
28
|
"CLEAROTRON_BAND_TRUTH_GATE",
|
|
@@ -54,6 +53,7 @@ export const NAMES_IN_FORCE = Object.freeze([
|
|
|
54
53
|
"CLEAROTRON_E2E_EVIDENCE_DIR",
|
|
55
54
|
"CLEAROTRON_E2E_EXPECT_DEMO_ROSTER",
|
|
56
55
|
"CLEAROTRON_EDGE_ORIGIN",
|
|
56
|
+
"CLEAROTRON_ENGINES_DIR",
|
|
57
57
|
"CLEAROTRON_ENGINE_CWD",
|
|
58
58
|
"CLEAROTRON_ENGINE_MAX_BUFFER",
|
|
59
59
|
"CLEAROTRON_ENUMERATE_CEILING",
|