clearotron 0.3.2-beta.7 → 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 +58 -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 +76 -0
- package/driver/band-size.mjs +59 -0
- package/driver/config-inventory.mjs +112 -9
- package/driver/contract-arm2-baseline.json +1 -3
- package/driver/contract-e3-backlog.mjs +26 -26
- package/driver/contract-vocabulary.mjs +44 -10
- 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 +24 -18
- 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 +200 -23
- 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 +18 -1
- 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 +2 -2
- 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 +142 -64
- 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 +27 -0
- package/mcp-server/CHANGELOG.md +4 -0
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +15 -1
- 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/jx/README.md +2 -1
- package/providers/jx/src/turn-envelope.mjs +8 -3
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +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/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/bin/start.mjs
CHANGED
|
@@ -84,8 +84,10 @@ import { writeSecretFile } from "../shared/secret-file.mjs"; // one atomic wri
|
|
|
84
84
|
// below: to COMPOSE the units' environment and to GUARD it before this command reports success. The
|
|
85
85
|
// tables are handed in rather than imported by it, because the register table lives in a CLI entry and
|
|
86
86
|
// the driver must not point at `bin/`.
|
|
87
|
-
import { runRequiredNames, missingRequirements } from "../driver/run-requirements.mjs";
|
|
88
|
-
import {
|
|
87
|
+
import { runRequiredNames, missingRequirements, CLOUD_ROUTES } from "../driver/run-requirements.mjs";
|
|
88
|
+
import { CLOUD_SWITCH, cloudsSwitchedOn } from "../driver/engine/auth.mjs"; // a switch compared the way the program reads it: on or off
|
|
89
|
+
import { ENGINE_BINARIES, DEFAULT_ENGINE_ID as RUN_DEFAULT_ENGINE, resolveEngineProgram } from "../driver/driver.config.mjs";
|
|
90
|
+
import { unitEnvironment, unitValue } from "../driver/unit-environment.mjs"; // the PATH the worker unit will run with, read the way doctor reads it
|
|
89
91
|
|
|
90
92
|
/**
|
|
91
93
|
* The tables the requirements authority needs — resolved at CALL time, never at module scope.
|
|
@@ -104,9 +106,9 @@ import { ENGINE_BINARIES, DEFAULT_ENGINE_ID as RUN_DEFAULT_ENGINE } from "../dri
|
|
|
104
106
|
* driver/test/a-backgrounded-install-can-actually-run-a-clearance.test.mjs refuses a static import of
|
|
105
107
|
* onboard from this file, so the cycle cannot come back quietly.
|
|
106
108
|
*/
|
|
107
|
-
async function runTables() {
|
|
109
|
+
export async function runTables() {
|
|
108
110
|
const { PROVIDERS } = await import("./onboard.mjs");
|
|
109
|
-
return { registers: PROVIDERS, engines: ENGINE_BINARIES, defaultEngine: RUN_DEFAULT_ENGINE };
|
|
111
|
+
return { registers: PROVIDERS, engines: ENGINE_BINARIES, defaultEngine: RUN_DEFAULT_ENGINE, resolveEngine: resolveEngineProgram };
|
|
110
112
|
}
|
|
111
113
|
import { spawn, spawnSync, execFileSync } from "node:child_process";
|
|
112
114
|
import { storeInRepo, storeOutsideRepoMessage, storeCommitRefusal } from "../shared/store-in-repo.mjs"; //
|
|
@@ -188,6 +190,89 @@ export function homeEnvUpdate(homeText, union) {
|
|
|
188
190
|
return mergeEnvFile(homeText, union, { refresh: LAUNCHER_MINTED });
|
|
189
191
|
}
|
|
190
192
|
|
|
193
|
+
/**
|
|
194
|
+
* The services' settings file as this start will leave it, and the run's settings in it that differ from
|
|
195
|
+
* the configuration this command holds. PURE, and the one reading `--background` uses for both its guard
|
|
196
|
+
* and its write, so a test drives start's own answer rather than a copy of it.
|
|
197
|
+
*
|
|
198
|
+
* THE GUARD CHECKED A MERGE THE FILE DOES NOT PERFORM. It read `{ ...file, ...union }`, where this
|
|
199
|
+
* command's value wins, while the add-only merge keeps the file's line, an empty one included. So a file
|
|
200
|
+
* holding `CLAUDE_CODE_USE_FOUNDRY=` passed the guard on this command's `=1`, kept its empty line, and
|
|
201
|
+
* every search was refused at the order wall. `reads` is the merged text, parsed: what the units read.
|
|
202
|
+
*
|
|
203
|
+
* AND WHAT THE FILE KEEPS IS SAID, never replaced. The merge adds only what the file lacks, and that is
|
|
204
|
+
* deliberate: the file is the running product's configuration, an operator edits it to change what the
|
|
205
|
+
* services do, and a start that rewrote it from this command's configuration would undo that edit. What
|
|
206
|
+
* was wrong was the silence. A machine moved from one cloud to another, or holding a rotated key, started
|
|
207
|
+
* again and reported the file complete while the services kept the old account.
|
|
208
|
+
* `differ` names the run settings on which the two disagree, so start can say which. Names only, never a
|
|
209
|
+
* value.
|
|
210
|
+
*
|
|
211
|
+
* WHICH DISAGREEMENTS, BY THE KIND OF SETTING, and never by which side holds the value. A healthy install
|
|
212
|
+
* can keep its register key, its engine's path and its research key in the units' file alone, because
|
|
213
|
+
* that file is where start tells an operator to change what the services use; this command's shell then
|
|
214
|
+
* holds none of them, and naming them on every start would warn about a working install. So a setting
|
|
215
|
+
* counts only when this command holds a value the file contradicts: a rotated key, or a line kept empty.
|
|
216
|
+
* The billing word and the settings that pick a cloud are the exception, and count in either direction,
|
|
217
|
+
* because the file holding one this command does not is exactly the machine billing an account nobody
|
|
218
|
+
* chose any more. They are compared as the program reads them: an unset billing word is the subscription,
|
|
219
|
+
* and a switch is on or off, so `1` beside `true` is not a difference.
|
|
220
|
+
*
|
|
221
|
+
* BUT ONLY WHERE THIS COMMAND HOLDS AN OPINION. A configuration that sets no billing word says nothing
|
|
222
|
+
* about how the services pay: an api-key, Codex api-key or cloud install whose billing lives in the units'
|
|
223
|
+
* file alone, where start's own remedy offers to put it, was warned about its billing word and its switch
|
|
224
|
+
* on every start. So with no billing word here, neither the word nor the cloud settings are compared. Under
|
|
225
|
+
* `cloud` with no cloud named here, which cloud is not compared either. And under `subscription` or
|
|
226
|
+
* `api-key` a cloud setting this command holds counts as unset, because the run door refuses a switch
|
|
227
|
+
* beside either word and start never carries one: naming it sent the operator to put into the file a
|
|
228
|
+
* switch every search would then be refused over. A cloud the FILE holds under those words still counts.
|
|
229
|
+
*/
|
|
230
|
+
export function unitsFileAfterStart(homeText, union, { config = {}, tables = {} } = {}) {
|
|
231
|
+
const merged = homeEnvUpdate(homeText, union);
|
|
232
|
+
const reads = parseEnvFile(merged.text);
|
|
233
|
+
const ours = (k) => String(union[k] ?? config[k] ?? "").trim();
|
|
234
|
+
const theirs = (k) => String(reads[k] ?? "").trim();
|
|
235
|
+
const words = new Set(Object.values(tables.engines ?? {}).map((e) => e?.authEnv).filter(Boolean));
|
|
236
|
+
const switches = Object.values(CLOUD_SWITCH);
|
|
237
|
+
const as = (k, v) => words.has(k) ? (v.toLowerCase() || "subscription")
|
|
238
|
+
: switches.includes(k) ? cloudsSwitchedOn({ [k]: v }).length > 0 : v;
|
|
239
|
+
const ourWord = ([...words].map(ours).find(Boolean) ?? "").toLowerCase();
|
|
240
|
+
const ourRoutes = Object.fromEntries(CLOUD_ROUTES.map((k) => [k, ours(k)]));
|
|
241
|
+
const ourCloud = cloudsSwitchedOn(ourRoutes).length > 0 || ourRoutes.ANTHROPIC_BASE_URL !== "";
|
|
242
|
+
const route = (k) => CLOUD_ROUTES.includes(k);
|
|
243
|
+
const opinion = (k) => ourWord !== "" && (!route(k) || ourWord !== "cloud" || ourCloud);
|
|
244
|
+
const oursAs = (k) => as(k, route(k) && ourWord !== "cloud" ? "" : ours(k));
|
|
245
|
+
const eitherWay = (k) => words.has(k) || route(k);
|
|
246
|
+
const names = new Set([...runRequiredNames(config, tables), ...runRequiredNames(reads, tables), ...CLOUD_ROUTES]);
|
|
247
|
+
const differ = [...names].filter((k) => !LAUNCHER_MINTED.includes(k)
|
|
248
|
+
&& (eitherWay(k) ? opinion(k) && oursAs(k) !== as(k, theirs(k)) : ours(k) !== "" && ours(k) !== theirs(k)));
|
|
249
|
+
return { merged, reads, differ };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* What start says about `differ`, the settings on which the units' file and this command's configuration
|
|
254
|
+
* disagree. PURE, so the words are driven rather than read from source. Names only, never a value.
|
|
255
|
+
*
|
|
256
|
+
* BOTH PLACES, because the add-only merge makes one of them a trap. This said to edit the units' file and
|
|
257
|
+
* restart. An operator who moved the services from one cloud to another that way, with this command's
|
|
258
|
+
* configuration still naming the first, had the first cloud's switch added back on the next start, since
|
|
259
|
+
* the file no longer held its line: two clouds on, and every search refused. Deleting that line, as the
|
|
260
|
+
* notice said, only repeated it. So it names the file the units read and the configuration start adds from,
|
|
261
|
+
* `cliEnv` (envFileRead(), or null when this command read no file and its shell is the configuration).
|
|
262
|
+
*/
|
|
263
|
+
export function keptSettingsNotice(differ, { homeEnv, cliEnv = null } = {}) {
|
|
264
|
+
if (!differ?.length) return [];
|
|
265
|
+
const one = differ.length === 1;
|
|
266
|
+
return [
|
|
267
|
+
` ⚠ ${homeEnv} and this command's configuration differ on ${one ? "this setting" : "these settings"}, and the units use what the file says:`,
|
|
268
|
+
` ${differ.join(", ")}`,
|
|
269
|
+
` This command only adds a setting the file has no line for; it never replaces one. To change what the units`,
|
|
270
|
+
` use, change ${one ? "it" : "them"} in both places, then restart them:`,
|
|
271
|
+
` ${homeEnv}\n the file the units read`,
|
|
272
|
+
` ${cliEnv ?? "this command's environment"}\n what this command adds from: a line the file above lacks is added back from here on the next start`,
|
|
273
|
+
];
|
|
274
|
+
}
|
|
275
|
+
|
|
191
276
|
/**
|
|
192
277
|
* What a `--background` run has ALREADY DONE when systemd refuses to enable a unit.
|
|
193
278
|
*
|
|
@@ -1709,22 +1794,19 @@ if (isMain) {
|
|
|
1709
1794
|
// said everything was fine, and a lawyer's search that died at its first stage with a stack trace,
|
|
1710
1795
|
// delivering "nothing was delivered. Clearotron has been notified" on a box that notified nobody.
|
|
1711
1796
|
//
|
|
1712
|
-
// CHECKED AGAINST
|
|
1713
|
-
// `mergeEnvFile` is add-only and an
|
|
1797
|
+
// CHECKED AGAINST WHAT THE FILE WILL SAY: the file's own lines, with `union` added where the file has
|
|
1798
|
+
// no line, because `mergeEnvFile` is add-only and an existing line wins, an empty one included. That
|
|
1799
|
+
// is `unitsFileAfterStart`, the same reading the write below uses. Checking `process.env` here would
|
|
1714
1800
|
// measure this shell rather than the units, and pass on exactly the box that fails.
|
|
1801
|
+
let homeText = "";
|
|
1802
|
+
try { homeText = readFileSync(HOME_ENV, "utf8"); } catch (e) { if (e.code !== "ENOENT") fatal(`${HOME_ENV} exists but could not be read (${e.code}).`); }
|
|
1803
|
+
const unitsFile = unitsFileAfterStart(homeText, union, { config: process.env, tables: RUN_TABLES });
|
|
1715
1804
|
//
|
|
1716
1805
|
// BLOCKING REFUSES; NARROWING IS SAID OUT LOUD AND STARTS ANYWAY. A research key this box does not
|
|
1717
1806
|
// hold means the three clearance searches refuse at preflight and a Knockout search still runs and
|
|
1718
1807
|
// discloses what it skipped — so refusing to start over it would take a box that can serve a real
|
|
1719
1808
|
// product and make it serve none. The operator is told which products this install can fill.
|
|
1720
1809
|
{
|
|
1721
|
-
const already = {};
|
|
1722
|
-
try {
|
|
1723
|
-
for (const line of readFileSync(HOME_ENV, "utf8").split("\n")) {
|
|
1724
|
-
const m = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/.exec(line);
|
|
1725
|
-
if (m) already[m[1]] = m[2];
|
|
1726
|
-
}
|
|
1727
|
-
} catch { /* no file yet — the union is the whole of it */ }
|
|
1728
1810
|
// ONE COMPOSER for "where do I set these", used by the start-time refusal and by the order-time
|
|
1729
1811
|
// announcement below it. Two copies of this sentence is how one of them comes to name a file the
|
|
1730
1812
|
// reader cannot use.
|
|
@@ -1739,7 +1821,29 @@ if (isMain) {
|
|
|
1739
1821
|
: `Set them in ${homeEnv} — the file the units read. This command read no environment file of `
|
|
1740
1822
|
+ `its own, so that is the address. \`${invoke("install")}\` writes them for you IN A TERMINAL.`;
|
|
1741
1823
|
};
|
|
1742
|
-
const willRead = { ...
|
|
1824
|
+
const willRead = { ...unitsFile.reads };
|
|
1825
|
+
// ── AND THE PATH THE WORKER WILL SEARCH FOR THE ENGINE'S PROGRAM ─────────────────────────────
|
|
1826
|
+
//
|
|
1827
|
+
// The check asks the engine resolver, and a resolver handed no PATH finds only a path setting or
|
|
1828
|
+
// the copy setup installed. So a `claude` on the units' PATH was announced as "every run is
|
|
1829
|
+
// refused", and the run found it and ran. The PATH is the worker unit's own, read from the file
|
|
1830
|
+
// placed below: the worker runs the clearance, and every shipped unit sets
|
|
1831
|
+
// `Environment=PATH=%h/…`, which the unit reader expands to this home. Never this shell's PATH,
|
|
1832
|
+
// which the units do not inherit.
|
|
1833
|
+
//
|
|
1834
|
+
// THE SETTINGS FILE IS READ WITH IT, because on systemd a PATH in that file wins over the unit's own
|
|
1835
|
+
// `Environment=PATH=` line, wherever the two lines sit (systemd.exec(5)), and the unit reader
|
|
1836
|
+
// applies that rule. Nothing Clearotron writes puts a PATH there, but a hand-edited file can, and
|
|
1837
|
+
// then the worker searches that PATH and not the unit's. A file that is not there yet reads as
|
|
1838
|
+
// empty: this command is about to write it, and the unit's PATH is then the one in force.
|
|
1839
|
+
{
|
|
1840
|
+
let text = null;
|
|
1841
|
+
try { text = readFileSync(join(REPO, "driver", "systemd", "clearotron-worker.service"), "utf8"); } catch { /* the install loop below names a missing unit */ }
|
|
1842
|
+
const settingsFile = (p) => { if (p !== HOME_ENV) return ""; try { return readFileSync(p, "utf8"); } catch { return ""; } };
|
|
1843
|
+
const workerPath = unitValue(unitEnvironment({ units: [{ name: "clearotron-worker.service", text }],
|
|
1844
|
+
readEnvFile: settingsFile, home: homedir() }), "PATH").value;
|
|
1845
|
+
if (workerPath) willRead.PATH = workerPath;
|
|
1846
|
+
}
|
|
1743
1847
|
const miss = missingRequirements(willRead, RUN_TABLES);
|
|
1744
1848
|
// ── — WHICH HALF OF `blocking` MAY REFUSE A START ─────────────────────────
|
|
1745
1849
|
//
|
|
@@ -1769,9 +1873,9 @@ if (isMain) {
|
|
|
1769
1873
|
//
|
|
1770
1874
|
// BOTH FILES, and that is the difference from the port refusals, which say `~/.env` "is NOT read
|
|
1771
1875
|
// here". They are right: nothing in that file reaches a port decision. Here both are true. The
|
|
1772
|
-
// block above reads HOME_ENV into `
|
|
1773
|
-
// satisfies this check on the next run; and the CLI's own file reaches it too,
|
|
1774
|
-
// `runRequiredNames(process.env, …)` loop that copies its values into `union`. Driven rather than
|
|
1876
|
+
// block above reads HOME_ENV into `willRead`, with its lines winning as they do in the file, so a
|
|
1877
|
+
// value set there satisfies this check on the next run; and the CLI's own file reaches it too,
|
|
1878
|
+
// through the `runRequiredNames(process.env, …)` loop that copies its values into `union`. Driven rather than
|
|
1775
1879
|
// read: three blocking names cleared from the CLI's file alone, and the refusal came back naming
|
|
1776
1880
|
// a fourth that the first three had newly required.
|
|
1777
1881
|
//
|
|
@@ -1817,26 +1921,32 @@ if (isMain) {
|
|
|
1817
1921
|
// it"), not about which gate happened to print it, and the arms in
|
|
1818
1922
|
// `a-refusal-names-the-file-when-the-command-it-offers-cannot-be-run.test.mjs` measure it here now.
|
|
1819
1923
|
//
|
|
1820
|
-
// BOTH FILES, because both genuinely reach the check: `~/.env` is
|
|
1821
|
-
// and this command's own file reaches it through the carry loop
|
|
1822
|
-
// never a path composed here — null means this process read no file of its own (a systemd start,
|
|
1924
|
+
// BOTH FILES, because both genuinely reach the check: `~/.env` is read into `willRead` above,
|
|
1925
|
+
// and this command's own file reaches it through the carry loop where `~/.env` has no line.
|
|
1926
|
+
// `envFileRead()` for the second, never a path composed here — null means this process read no file of its own (a systemd start,
|
|
1823
1927
|
// or CLEAROTRON_NO_ENV_FILE=1) and then HOME_ENV is the only honest address there is.
|
|
1824
1928
|
say(` Nothing has been installed that cannot run, and nothing has been spent.`);
|
|
1825
1929
|
say(` ${orderRemedy(HOME_ENV)}`);
|
|
1826
1930
|
}
|
|
1827
1931
|
for (const r of miss.narrowing)
|
|
1828
1932
|
say(` ⚠ ${r.name} is not set — ${r.why}`);
|
|
1933
|
+
// ── WHAT THE FILE KEEPS, SAID BY NAME ──────────────────────────────────────────────────────────
|
|
1934
|
+
//
|
|
1935
|
+
// The merge never replaces a line (see `unitsFileAfterStart`), so a setting changed in this
|
|
1936
|
+
// command's configuration after the first start does not reach the units. A machine moved from one
|
|
1937
|
+
// cloud to another went on billing the first, and a rotated key stayed at its first value, while this
|
|
1938
|
+
// command reported the file complete. Said here, by name and never by value, naming both places the
|
|
1939
|
+
// setting lives (see `keptSettingsNotice` for why one is not enough).
|
|
1940
|
+
for (const line of keptSettingsNotice(unitsFile.differ, { homeEnv: HOME_ENV, cliEnv: envFileRead() })) say(line);
|
|
1829
1941
|
}
|
|
1830
1942
|
|
|
1831
|
-
let homeText = "";
|
|
1832
|
-
try { homeText = readFileSync(HOME_ENV, "utf8"); } catch (e) { if (e.code !== "ENOENT") fatal(`${HOME_ENV} exists but could not be read (${e.code}).`); }
|
|
1833
1943
|
// THE TRIGGER KEY IS MINTED HERE, SO IT IS REWRITTEN HERE. Everything else in this union is
|
|
1834
1944
|
// collected — the operator's credentials, their paths — and add-only is what stops a launcher from
|
|
1835
1945
|
// losing them. The trigger key is the opposite: this process mints it, thirty days at a time, and a
|
|
1836
1946
|
// value written once and never again runs down to expiry on a server nobody has touched. When it
|
|
1837
1947
|
// lapses every Start stops, and the failure arrives as an upstream refusal that reads like an engine
|
|
1838
1948
|
// fault rather than an expired key card.
|
|
1839
|
-
const merged =
|
|
1949
|
+
const { merged } = unitsFile;
|
|
1840
1950
|
if (merged.added.length || merged.refreshed.length) {
|
|
1841
1951
|
writeSecretFile(HOME_ENV, merged.text);
|
|
1842
1952
|
const parts = [];
|
package/bin/update.mjs
CHANGED
|
@@ -51,7 +51,7 @@ import { existsSync } from "node:fs";
|
|
|
51
51
|
import { join, dirname, resolve } from "node:path";
|
|
52
52
|
import { fileURLToPath } from "node:url";
|
|
53
53
|
|
|
54
|
-
import { config } from "../driver/driver.config.mjs";
|
|
54
|
+
import { config, ENGINE_BINARIES, enginesFolder, engineInstallArgs } from "../driver/driver.config.mjs";
|
|
55
55
|
import { isInsideCheckout } from "../shared/inside-checkout.mjs"; // — one copy of the rule
|
|
56
56
|
import { overlayReport, renderOverlayReport, treeFiles } from "../shared/doctrine-overlay.mjs";
|
|
57
57
|
import { liveRunHolds } from "../driver/deploy-live-run-guard.mjs"; // — one live-run test, shared with deploy-preflight
|
|
@@ -180,6 +180,34 @@ export function isGitCheckout(repo = REPO, exists = existsSync) {
|
|
|
180
180
|
return exists(join(repo, ".git"));
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
/**
|
|
184
|
+
* The engine programs setup installed for this user, which `clearotron update` refreshes: every engine
|
|
185
|
+
* whose package is in the engines folder. Setup installs only the one the reader chose, so this is usually
|
|
186
|
+
* one, and none where the machine's own copy was found first or the reader declined the install.
|
|
187
|
+
*/
|
|
188
|
+
export function enginesToRefresh({ dir = enginesFolder(), exists = existsSync } = {}) {
|
|
189
|
+
return Object.values(ENGINE_BINARIES)
|
|
190
|
+
.filter((e) => e.package && exists(join(dir, "node_modules", ...e.package.split("/"), "package.json")));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Run setup's install again for each of them, in the same folder. The same command, because re-running an
|
|
195
|
+
* install with a `>=` range moves the program to the newest its vendor publishes (driver.config.mjs, above
|
|
196
|
+
* engineInstallArgs). Returns 0, or the first failing exit code.
|
|
197
|
+
*/
|
|
198
|
+
function refreshEngines(engines, dir = enginesFolder()) {
|
|
199
|
+
if (!engines.length) return 0;
|
|
200
|
+
say(`\n Refreshing the engine program Clearotron installed in ${dir}.`);
|
|
201
|
+
for (const e of engines) {
|
|
202
|
+
const rc = runInCheckout("npm", engineInstallArgs(e, dir));
|
|
203
|
+
if (rc !== 0) {
|
|
204
|
+
console.error(`\n npm could not refresh ${e.package}. \`clearotron doctor\` says which copy a run would use now.`);
|
|
205
|
+
return rc;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return 0;
|
|
209
|
+
}
|
|
210
|
+
|
|
183
211
|
function runInCheckout(cmd, args) {
|
|
184
212
|
say(`\n $ ${cmd} ${args.join(" ")}`);
|
|
185
213
|
const r = spawnSync(cmd, args, { cwd: REPO, stdio: "inherit" });
|
|
@@ -301,22 +329,38 @@ export async function update(argv = process.argv.slice(2)) {
|
|
|
301
329
|
}
|
|
302
330
|
|
|
303
331
|
if (packaged) {
|
|
332
|
+
// ONE NPM RUN, WITH THE LAUNCHER PUT BACK AFTER IT, and both branches below install through it, so
|
|
333
|
+
// neither can run npm and forget the second half. npm puts its own link back at
|
|
334
|
+
// `<prefix>/bin/clearotron` on every install, over the launcher the install wrote, and that link runs
|
|
335
|
+
// whichever `node` is first on PATH. Put the launcher back, but only over npm's link or our own:
|
|
336
|
+
// anything else there was not ours before this update either.
|
|
337
|
+
const reinstall = () => {
|
|
338
|
+
const rc = runInCheckout("npm", packaged.npmArgs);
|
|
339
|
+
if (rc !== 0) return rc;
|
|
340
|
+
const kind = inspectShim(shimPath()).kind;
|
|
341
|
+
if (kind === "npm-link" || kind === "ours" || kind === "ours-other-install") {
|
|
342
|
+
const shim = installShim();
|
|
343
|
+
if (!shim.ok) console.error(`\n The update worked, but the launcher at ${shim.path ?? "~/.local/bin/clearotron"} could not be written back: ${shim.detail}.`);
|
|
344
|
+
}
|
|
345
|
+
return 0;
|
|
346
|
+
};
|
|
304
347
|
if (packaged.current) {
|
|
305
|
-
say(`\n This install is ${packaged.installed}, and nothing newer is published (${packaged.tag}: ${packaged.version})
|
|
348
|
+
say(`\n This install is ${packaged.installed}, and nothing newer is published (${packaged.tag}: ${packaged.version}).`);
|
|
349
|
+
// CURRENT STILL REFRESHES THE ENGINE PROGRAM setup installed: it moves on its vendor's schedule, not
|
|
350
|
+
// this package's. A machine's own copy on PATH updates itself and is used first.
|
|
351
|
+
const engines = enginesToRefresh();
|
|
352
|
+
if (!engines.length) { say(" Nothing was touched.\n"); return 0; }
|
|
353
|
+
const rc = refreshEngines(engines);
|
|
354
|
+
if (rc !== 0) return rc;
|
|
355
|
+
say("\n Clearotron itself was already current; restart the services so they use the refreshed program.\n");
|
|
306
356
|
return 0;
|
|
307
357
|
}
|
|
308
358
|
if (packaged.unread) say(`\n npm did not say which versions are published, so this follows the ${packaged.tag} channel.`);
|
|
309
359
|
say(`\n Updating this install at ${packaged.prefix} from ${packaged.installed ?? "an unreadable version"} to clearotron@${packaged.spec}.`);
|
|
310
|
-
const rc =
|
|
360
|
+
const rc = reinstall();
|
|
311
361
|
if (rc !== 0) return rc;
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
// over npm's link or our own: anything else there was not ours before this update either.
|
|
315
|
-
const kind = inspectShim(shimPath()).kind;
|
|
316
|
-
if (kind === "npm-link" || kind === "ours" || kind === "ours-other-install") {
|
|
317
|
-
const shim = installShim();
|
|
318
|
-
if (!shim.ok) console.error(`\n The update worked, but the launcher at ${shim.path ?? "~/.local/bin/clearotron"} could not be written back: ${shim.detail}.`);
|
|
319
|
-
}
|
|
362
|
+
const refreshed = refreshEngines(enginesToRefresh());
|
|
363
|
+
if (refreshed !== 0) return refreshed;
|
|
320
364
|
say("\n Updated. An assistant starts the new version the next time it launches Clearotron; restart the services for the portal.\n");
|
|
321
365
|
return 0;
|
|
322
366
|
}
|
|
@@ -393,6 +437,9 @@ export async function update(argv = process.argv.slice(2)) {
|
|
|
393
437
|
console.error(` the custom instructions could not be read — ${e.message}`);
|
|
394
438
|
}
|
|
395
439
|
|
|
440
|
+
const refreshed = refreshEngines(enginesToRefresh());
|
|
441
|
+
if (refreshed !== 0) return refreshed;
|
|
442
|
+
|
|
396
443
|
say("\n Up to date.\n");
|
|
397
444
|
return 0;
|
|
398
445
|
}
|
package/build-info.json
CHANGED
|
@@ -29,7 +29,7 @@ time, so all of them honour a mid-process env flip — the file's own NOTE besid
|
|
|
29
29
|
`./driver.config.mjs` resolves to ONE cached module instance across the offline test fleet, so
|
|
30
30
|
import-time captures silently pinned every test to the first test's env. What IS frozen at first import
|
|
31
31
|
is the module-level declarations beside it — the consts `REGISTER_PROVIDER` and
|
|
32
|
-
`UNREACHABLE_SENIOR_POLICY
|
|
32
|
+
`UNREACHABLE_SENIOR_POLICY`.
|
|
33
33
|
Because the driver runs as a systemd **oneshot** (a fresh process per activation), editing the
|
|
34
34
|
deployment's `.env` takes effect on the next queue-triggered run with no deploy and no restart — that is the
|
|
35
35
|
supported way to change caps and A/B toggles. One trap: `synthesis.model` reads
|
|
@@ -130,17 +130,22 @@ through anything containing `/`):
|
|
|
130
130
|
| gemini | `google/gemini-3.1-pro-preview` |
|
|
131
131
|
| gemini-flash | `google/gemini-3-flash-preview` |
|
|
132
132
|
| deepseek-v4-pro | `together/deepseek-ai/DeepSeek-V4-Pro` |
|
|
133
|
-
| azure | `
|
|
133
|
+
| azure | `azure-openai/gpt-5.4` |
|
|
134
134
|
|
|
135
135
|
The bottom four are **legacy names that no stage declares and no engine can run** — they resolve at
|
|
136
136
|
level 1 and then throw at level 2 (below). They are catalogue entries, not available tiers.
|
|
137
137
|
|
|
138
138
|
**Level 2 — the active engine** maps aliases to the CLI's own model names. On `anthropic-agent`
|
|
139
|
-
(`CLAUDE_MODEL` in `engine/anthropic-agent.mjs`):
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
(`CLAUDE_MODEL` in `engine/anthropic-agent.mjs`): `opus`, `sonnet`, `haiku` and `fable` pass through
|
|
140
|
+
as aliases, so each tier follows the vendor's newest model; to hold one still, set
|
|
141
|
+
`ANTHROPIC_DEFAULT_OPUS_MODEL` (or `ANTHROPIC_DEFAULT_SONNET_MODEL`, `ANTHROPIC_DEFAULT_HAIKU_MODEL`,
|
|
142
|
+
`ANTHROPIC_DEFAULT_FABLE_MODEL`).
|
|
143
|
+
The catalog ids `anthropic/claude-opus-5` and `anthropic/claude-sonnet-5` are passed as those
|
|
144
|
+
concrete models; `anthropic/claude-haiku-4-5` goes over as the `haiku` alias, so it follows the
|
|
145
|
+
vendor the same way. A bare or dated Anthropic id (`claude-haiku-4-5-20251001`) still resolves to
|
|
146
|
+
its family — that is a naming form of a model the CLI can run, not a substitution of a different
|
|
147
|
+
one. Telemetry keeps the level-1 catalog id as the model asked for, and the attempt row records the
|
|
148
|
+
id the program reports it served.
|
|
144
149
|
|
|
145
150
|
**Anything else throws.** There is no regex fall-through to sonnet and no cross-provider
|
|
146
151
|
substitution: the `gemini`/`gemini-flash`/`deepseek-v4-pro`/`azure` mappings are gone with the
|
|
@@ -170,8 +175,8 @@ timeout shot, warm patch, backoff), the lane-wedge re-dispatch, and the rate-lim
|
|
|
170
175
|
| Setting | Values | Effect |
|
|
171
176
|
|---|---|---|
|
|
172
177
|
| `CLEAROTRON_AI` | `anthropic-agent` (default) \| `openai-agent` | Compute engine (a registered provider adapter). `anthropic-agent` = `claude -p`; `openai-agent` = `codex exec` (both off-gateway, same normalized contract). An **unregistered** value — a typo, or the gateway-runtime adapter removed in the extraction — **fails loud**: no silent wrong-provider run. Production default is unchanged. |
|
|
173
|
-
| `CLEAROTRON_AI_BILLING` | `subscription` (default) \| `api-key` | 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. **`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
|
|
174
|
-
| `CLEAROTRON_CLAUDE_PATH` · `CLEAROTRON_CODEX_PATH` | `claude` / `codex` on `PATH
|
|
178
|
+
| `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. |
|
|
179
|
+
| `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 `npx 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. |
|
|
175
180
|
| `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; the owner's 2026-09-04 ruling **reversed that exemption** and renamed the whole namespace, so it carries the house prefix like everything else. One spelling, no exceptions. |
|
|
176
181
|
| `CLEAROTRON_OPENAI_MODEL_JUDGMENT` / `CLEAROTRON_OPENAI_MODEL_SWEEP` / `CLEAROTRON_OPENAI_MODEL_CHEAP` | `gpt-5.6-sol` / `gpt-5.6-terra` / `gpt-5.6-luna`. | 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. |
|
|
177
182
|
| ↳ 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. |
|
|
@@ -207,6 +212,7 @@ deployment may override (verify live values per deployment).
|
|
|
207
212
|
| `USPTO_LOCAL_DB` | **none — set it to use `uspto-local`** | The local USPTO index (`node:sqlite` + FTS5) that `bin/uspto-sync.mjs` builds and the free US register reads. Named in `.env.example`; this is the reference row. |
|
|
208
213
|
| `CLEAROTRON_JX_SUBCLASS_DB` | unset ⇒ the lane refuses by name | Path to the built similar-group database the JX subclass lane reads, produced by `providers/jx-subclass/load-public.mjs` from the committed public tables. It names WHERE the table lives; what the table says is the build's. Absence is a refusal rather than an empty answer — a clearance that silently found no similar groups is indistinguishable from one where the file was missing. |
|
|
209
214
|
| `CLEAROTRON_SUITE_TELEMETRY_DIR` | unset ⇒ the box's ledger | Redirects the provider telemetry ledger into a suite run's own temp root, so a suite never writes the box's ledger and never inherits it. Sits BELOW an explicitly-named ledger file: a test naming its own path is being deliberate, and this exists for the runs that name nothing. |
|
|
215
|
+
| `CLEAROTRON_ENGINES_DIR` | unset ⇒ `~/.local/share/clearotron/engines` | The folder `clearotron install` installs the chosen engine's program into, `clearotron update` refreshes, and the engine resolver reads as its last step after the explicit setting and `PATH`: an npm project holding `node_modules/@anthropic-ai/claude-code` or `node_modules/@openai/codex`, where an empty directory means there is none. The test suite points it at an empty directory, so a test never reaches a program the developer's own setup installed. |
|
|
210
216
|
| `FEEDBACK_GH_TOKEN` | unset ⇒ `gh`'s own auth | GitHub token for the unattended feedback minter (`scripts/feedback-mint.mjs`), for a service account with no `gh auth` login. Present ⇒ exported to `gh` as `GH_TOKEN` for that call only. A credential: it is not a placeholder value and must not be committed. |
|
|
211
217
|
| `USPTO_API_KEY` | unset ⇒ the download path refuses | API key for the USPTO bulk download path in `bin/uspto-sync.mjs`. The INGEST path needs no key and says so; only the download half reads this. A credential. |
|
|
212
218
|
|
|
@@ -317,7 +323,13 @@ cannot be read as one list.
|
|
|
317
323
|
|
|
318
324
|
| Var | Consumer |
|
|
319
325
|
|---|---|
|
|
320
|
-
| `ANTHROPIC_API_KEY` | Engine child env in `api-key` mode only (deleted in subscription
|
|
326
|
+
| `ANTHROPIC_API_KEY` | Engine child env in `api-key` mode only (deleted in subscription and cloud modes). |
|
|
327
|
+
| `CLAUDE_CODE_USE_VERTEX` / `CLAUDE_CODE_USE_FOUNDRY` / `CLAUDE_CODE_USE_BEDROCK` | **Read by the Claude program**, which sends every turn to that cloud; `1`, `true`, `yes` or `on` switches one on. Clearotron reads them to name the cloud under `CLEAROTRON_AI_BILLING=cloud` and to refuse a switch left on under `subscription` or `api-key`. Under `CLEAROTRON_AI_BILLING=cloud`, `clearotron start --background` carries the switch that is on, and every setting in the five rows below that is set, the model pins included, into `~/.env` (a switch that is set and not on stays behind); under `subscription` or `api-key` it carries none. It adds only a line `~/.env` lacks, and names any of these on which that file and its own configuration differ. A missing switch is reported by `clearotron start` and `clearotron doctor`, and refuses a search when it is ordered. Doctor and setup's proof turn carry these and the five rows below from the settings file a search reads, which is the one at the old location while an install is still configured there (`CLOUD_SETTINGS` in `driver/engine/auth.mjs`). A value in the shell wins over the file's: setup's proof turn keeps a blank one, as a run does, where doctor passes over it and takes the file's. In setup, an answer given wins over both. A run takes the whole file. |
|
|
328
|
+
| `ANTHROPIC_VERTEX_PROJECT_ID` / `CLOUD_ML_REGION` / `GOOGLE_APPLICATION_CREDENTIALS` | Vertex AI's project, region and service-account key, read by the Claude program. Without the key file it uses gcloud's sign-in. |
|
|
329
|
+
| `ANTHROPIC_FOUNDRY_RESOURCE` / `ANTHROPIC_FOUNDRY_API_KEY` | The Foundry resource and its key, read by the Claude program. Without the key it uses the machine's Azure sign-in. |
|
|
330
|
+
| `AWS_REGION` / `AWS_PROFILE` / `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` | Bedrock's region, optionally the AWS profile, and the standard AWS key variables (the session token only for temporary credentials), read by the Claude program. Without the keys it uses the machine's other AWS credentials, such as an instance role. |
|
|
331
|
+
| `ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN` | A gateway in front of a cloud, and its token, read by the Claude program. With no cloud switch, `ANTHROPIC_BASE_URL` is the gateway form of `CLEAROTRON_AI_BILLING=cloud`; the gateway's credential must then be the token, because `ANTHROPIC_API_KEY` is deleted. |
|
|
332
|
+
| `ANTHROPIC_DEFAULT_OPUS_MODEL` / `ANTHROPIC_DEFAULT_SONNET_MODEL` / `ANTHROPIC_DEFAULT_HAIKU_MODEL` / `ANTHROPIC_DEFAULT_FABLE_MODEL` | The model each tier's alias resolves to, read by the Claude program: on Foundry, the deployment names; anywhere, a way to hold a tier still. Set the fable one to your Fable deployment's name if you set `CLEAROTRON_SYNTHESIS_MODEL=fable`; setup does not ask for it. |
|
|
321
333
|
| `OPENAI_API_KEY` | **Read only to be DELETED.** The `openai-agent` adapter strips it from the `codex exec` environment under subscription billing, so an unrelated key exported on the box cannot spoil a clean subscription bill. It is never the api-key credential for this product — that is `CODEX_API_KEY`. Listed because a reader who has one set needs to know it is removed. |
|
|
322
334
|
| `CORSEARCH_SESSION_KEY` | Register provider auth (session cookie). **Preflighted at run start — a missing credential fails fast before any model spend.** |
|
|
323
335
|
| `CLARIVATE_API_KEY` / `CLARIVATE_API_BASE` | Clarivate adapter — on the engine path: `clarivate-server.mjs` in `gather-config.mjs`'s stage-grant table, plus driver-side `recordFetch` / `countHits` / `listRecords` / `executePlan`. `CLARIVATE_API_BASE` overrides the core's `DEFAULT_BASE`. |
|
|
@@ -341,7 +353,6 @@ cannot be read as one list.
|
|
|
341
353
|
| `CLEAROTRON_SYNTHESIS_MODEL` | unset (⇒ opus) | Stage-specific synthesis model override — the live A/B toggle (e.g. `fable`). Alias must be registered in the engine map or the dispatch REFUSES by name (; it used to run sonnet silently and log the alias asked for). Read at module load; effective per fresh oneshot process. |
|
|
342
354
|
| `CLEAROTRON_MEANING_SEAT_MODEL` | unset (⇒ `haiku`) | The common-law MEANING seat's model (`COMMON_LAW_SEAT_TIER[MEANING_SEAT]`, `stages.mjs`). The default moved sonnet → haiku on measured evidence: 2 attempts / 423 s against 1 attempt / 1674 s for the same outcome. **Margin:** sufficient at every load the test suite exercises, but at its densest scenario haiku used the last rung of the retry ladder — suspect this variable first if a dense matter's meaning seat goes terminal. Set`sonnet` to roll back with no code change. The thinking budget is NOT overridable (one variable, by design). |
|
|
343
355
|
| `CLEAROTRON_STAGE_THINKING` | unset (⇒ each stage's declared tier) | Per-stage thinking-tier override, `<stage>=<tier>[,…]` (e.g. `register-digest=high`) — the A/B instrument, so a suite arm needs no code fork or redeploy between runs. Thinking only: models are deliberately not overridable here, so one arm can never move two variables. **The env override is a dev/test instrument**; a permanent change edits the tier in `stages.mjs` and ships. Unknown stage or tier **throws** — `effortFor()` falls back to `medium`, so a typo would otherwise run a stage at a tier nobody chose and every number measured against it would be wrong. Read per call, so an arm can flip mid-process. |
|
|
344
|
-
| `CLEAROTRON_AZURE_MODEL` | `azure-openai/gpt-5.4` | Target of the `azure` alias — a legacy catalogue entry no engine can run (see model tiers above). |
|
|
345
356
|
| `CLEAROTRON_DUMP_JSON` | unset | Dump each attempt's raw engine envelope to `_driver/<stage>.attempt<N>.rawjson.json`. Opt-in: any value except `0`/`off`/`false`/`no`/empty arms it. |
|
|
346
357
|
| `CLEAROTRON_DISPATCH_RECORD` | **on** | Write the verbatim message of every stage dispatch to `_driver/<stage>.attempt<N>[.repair<M>].dispatch.txt`, with `{file, sha, bytes, chars, kind}` on the attempt row. **Default ON** — `0`/`off`/`false`/`no` disarms it. Unlike `CLEAROTRON_DUMP_JSON` beside it, this is opt-OUT: the question it answers ("was the model given this?") is asked *after* the run that raised it, so a flag someone had to remember would be off on exactly the run that needed it. The files carry the company's identity verbatim and are deliberately not in the artifact table. |
|
|
347
358
|
| `CLEAROTRON_GATHER_SESSION_KEY` / `CLEAROTRON_GATHER_AGENT` / `CLEAROTRON_GATHER_SESSION_ID` | set per stage | Telemetry attribution into the provider-call ledger (set by the gather config; not operator-set). |
|
|
@@ -356,8 +367,12 @@ The settings below were deleted. Nothing in any environment set them, so each be
|
|
|
356
367
|
had always resolved to. **They are listed because an operator whose `.env` still carries one needs to
|
|
357
368
|
know it is inert** — an unread setting is indistinguishable from a setting that works.
|
|
358
369
|
|
|
370
|
+
`CLEAROTRON_AZURE_MODEL` left for a different reason, on 2026-09-15: it retargeted the `azure` alias,
|
|
371
|
+
which no stage names and no engine runs, so no run ever used its value.
|
|
372
|
+
|
|
359
373
|
| Was | Now fixed at |
|
|
360
374
|
|---|---|
|
|
375
|
+
| `CLEAROTRON_AZURE_MODEL` | `azure-openai/gpt-5.4`, the target of the `azure` alias (see model tiers above) |
|
|
361
376
|
| `CLEAROTRON_BAND_SHAPE_PART_CHARS` | 70000 characters per shape part |
|
|
362
377
|
| `CLEAROTRON_DEDUP_WINDOW_HOURS` | 24 hours, and the window can no longer be disabled |
|
|
363
378
|
| `CLEAROTRON_FETCH_MAX_CHARS` | 200000 characters per fetched page |
|
|
@@ -159,13 +159,19 @@ structural, or dev seam); [dev] = dev/test seam, never set in prod.
|
|
|
159
159
|
### 5.2 Engine & models — T3
|
|
160
160
|
|
|
161
161
|
`CLEAROTRON_AI` (anthropic-agent | openai-agent; default anthropic-agent), `CLEAROTRON_AI_BILLING`
|
|
162
|
-
(subscription|api-key
|
|
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
164
|
`CLEAROTRON_OPENAI_MODEL_CHEAP` (all gpt-5.6-sol),
|
|
165
|
-
`CLEAROTRON_CLAUDE_PATH` (claude),
|
|
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_KNOCKOUT_PRESET` (pro-search), `CLEAROTRON_MAX_BUDGET_USD` (unset).
|
|
168
168
|
|
|
169
|
+
The Claude program's own cloud settings — `CLAUDE_CODE_USE_FOUNDRY`, `CLAUDE_CODE_USE_VERTEX` and
|
|
170
|
+
`CLAUDE_CODE_USE_BEDROCK`, each cloud's own settings, the gateway pair and the model pins — are the
|
|
171
|
+
vendor's names, not this tier's; the configuration reference's credentials table lists them. Written out
|
|
172
|
+
rather than as one wildcard: a guard reads these documents for the names they govern, and a trailing `*`
|
|
173
|
+
matches nothing it can check, so a name hidden behind one reads as governed while being invisible.
|
|
174
|
+
|
|
169
175
|
### 5.3 Concurrency, admission, retries, walls — T3 (walls are load-bearing; change deliberately)
|
|
170
176
|
|
|
171
177
|
`CLEAROTRON_MAX_CONCURRENT_RUNS` (2), `CLEAROTRON_GATHER_CONCURRENCY` (7), `CLEAROTRON_CARD_CONCURRENCY` (8),
|
|
@@ -472,6 +478,12 @@ It must be **asked for**: a real deployment behind its upstream still reports be
|
|
|
472
478
|
blank value is not a declaration, and the doctor names this variable in its output when it obeys it. Set
|
|
473
479
|
it in CI and nowhere else — on a deployed box it silences the one check that notices the box is stale.
|
|
474
480
|
|
|
481
|
+
Engines folder: `CLEAROTRON_ENGINES_DIR` — where setup installs the chosen engine's program, where
|
|
482
|
+
`clearotron update` refreshes it, and where the engine resolver in `driver/driver.config.mjs` looks for it
|
|
483
|
+
after the explicit setting and `PATH`. Unset on a deployment, where it is
|
|
484
|
+
`~/.local/share/clearotron/engines`. The suite runner points it at an empty directory, so a test never
|
|
485
|
+
reaches a program the developer's own setup installed.
|
|
486
|
+
|
|
475
487
|
> **Write every name out. No `*`, no `{A,B}`, no `/_SUFFIX`.** The enforcement test matches a name on
|
|
476
488
|
> a word boundary, so shorthand documents a variable to a human and hides it from the guard. This row
|
|
477
489
|
> is where that was found: `*_AUTH_DISABLED`, `*_DEV` and `CLEAROTRON_REPLAY_SNAPSHOT`/`_ROOTS` left five
|
|
@@ -483,11 +495,9 @@ it in CI and nowhere else — on a deployed box it silences the one check that n
|
|
|
483
495
|
> see `providers/oauth-mcp-bridge/README.md`) were listed there for years and read by nothing, which
|
|
484
496
|
> made a reader configure a variable and get no behaviour.
|
|
485
497
|
>
|
|
486
|
-
>
|
|
487
|
-
>
|
|
488
|
-
>
|
|
489
|
-
> defect as inviting someone to set a variable this product reads — the rule above is about the
|
|
490
|
-
> second.
|
|
498
|
+
> Both are gone. The four `AZURE_OPENAI_*` names left `.env.example` on 2026-09-15: on a page that
|
|
499
|
+
> explains paying for Claude through an Azure account, four Azure variables nothing in Clearotron reads
|
|
500
|
+
> looked like that setup's settings, and they are not.
|
|
491
501
|
|
|
492
502
|
`portal-ui/` has **zero** env config (no `VITE_*`, no `import.meta.env`) — the SPA talks to its
|
|
493
503
|
origin; all portal config lives server-side in portal-service.
|
package/driver/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,81 @@
|
|
|
1
1
|
# clearotron-driver
|
|
2
2
|
|
|
3
|
+
## 0.3.2-beta.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d194d41: Fixed: A knockout search now covers the territories the form is showing you, rather than searching the whole world instead of them.
|
|
8
|
+
|
|
9
|
+
Fixed: A new clearance recommends and preselects a search when the form is showing your company's own territories. It previously offered none and said no search was picked, beside a summary naming those same countries.
|
|
10
|
+
- d194d41: Fixed: A report opened on a phone fits the screen instead of scrolling sideways.
|
|
11
|
+
- d194d41: Fixed: A knockout search that is running says it usually takes 5 to 10 minutes. It previously showed 1.5 to 2.5 hours, which is how long a full clearance takes.
|
|
12
|
+
- d194d41: Fixed: A running search says which step it is on, such as "Register sweeps", rather than "Register sweeps · 3 of 9". How many steps a search has varies with what it needs to do, so the number did not mean what it looked like.
|
|
13
|
+
- d194d41: New: Each search in your list now says which of the four searches it was.
|
|
14
|
+
- d194d41: Fixed: A new clearance now offers only the territories your register can search. Before, it offered countries your register cannot reach, and choosing one stopped the search from starting.
|
|
15
|
+
|
|
16
|
+
Fixed: You can now remove one of your company's default territories on the clearance form. Before, if your register could not search one of them, nothing on that screen let you take it off and carry on.
|
|
17
|
+
- d194d41: Fixed: A report now gives the specific reason each name was set aside. Before, every such name carried the same general sentence, and the reason the search actually recorded for it was not shown.
|
|
18
|
+
- d194d41: Fixed: A search naming a territory your trademark register does not cover is now refused before it starts, and says which territory to remove. Before, the search ran and that territory was reported as not searched at the end.
|
|
19
|
+
- d194d41: New: Pay for Claude through your own Google Cloud, Microsoft Azure or Amazon Bedrock account with `CLEAROTRON_AI_BILLING=cloud`. Tested on Microsoft Azure; Google Cloud and Amazon Bedrock use the Claude program's own settings.
|
|
20
|
+
|
|
21
|
+
New: Each run records which cloud account paid for it, and `clearotron doctor` names the cloud account it charges.
|
|
22
|
+
|
|
23
|
+
New: Setup asks how Claude is paid for, and for a cloud account asks which cloud and checks it with one turn.
|
|
24
|
+
|
|
25
|
+
New: `clearotron start`, when no billing is set, names a cloud account for Claude beside a subscription and an API key.
|
|
26
|
+
|
|
27
|
+
New: `clearotron start --background` carries the cloud account's settings to the background services.
|
|
28
|
+
|
|
29
|
+
New: `clearotron doctor` says how the background services pay, and warns when your own configuration sets a different way of paying.
|
|
30
|
+
|
|
31
|
+
New: Global config's Engine row names the cloud account that pays, and turns red, naming the setting to change, when searches would be refused.
|
|
32
|
+
|
|
33
|
+
Fixed: An install that pays with an API key and runs as background services now hands the services its key. Before, every search stopped after it was ordered.
|
|
34
|
+
|
|
35
|
+
Fixed: A subscription install signed in with a long-lived token from `claude setup-token` now hands that token to its background services.
|
|
36
|
+
|
|
37
|
+
Fixed: `clearotron start` now reports a billing setting that would stop every search, such as an API key that is not set. `clearotron doctor` also checks the settings the background services read.
|
|
38
|
+
|
|
39
|
+
For operators: `clearotron start --background` names each setting on which `~/.env` and Clearotron's settings disagree, such as a rotated key, without printing values. It adds only settings `~/.env` lacks and never replaces one, so change a setting in both files.
|
|
40
|
+
|
|
41
|
+
Before you upgrade: A billing setting Clearotron does not recognise now stops a search before it starts, where it used to bill the subscription. Run `clearotron doctor` after upgrading.
|
|
42
|
+
|
|
43
|
+
Before you upgrade: On a Claude install, a cloud's own switch left on, such as `CLAUDE_CODE_USE_FOUNDRY`, now stops a search unless `CLEAROTRON_AI_BILLING=cloud`.
|
|
44
|
+
- d194d41: Fixed: Connect your AI now sits last in the sidebar, under Company settings. It used to sit second, directly under Home.
|
|
45
|
+
- d194d41: Fixed: `clearotron doctor` and `clearotron start --background` look for Claude Code or the Codex CLI on the PATH the background services use. They used to say every search would be refused on a machine whose searches found the program and ran.
|
|
46
|
+
- d194d41: Fixed: When a background service's unit and its settings file set the same value, `clearotron doctor` and `clearotron connect` take the file's, as systemd does.
|
|
47
|
+
|
|
48
|
+
Fixed: `clearotron doctor` and `clearotron connect` read a doubled percent sign (`%%`) in a background service's unit as one, as systemd does.
|
|
49
|
+
- d194d41: Fixed: When the background services found the reasoning program and this machine cannot, `clearotron doctor` now suggests installing it here with setup. It used to suggest installing it where the services could already see it.
|
|
50
|
+
|
|
51
|
+
New: If a restart does not help the background services find the reasoning program, `clearotron doctor` says how to point them at it.
|
|
52
|
+
- d194d41: New: `clearotron doctor --probe-engine` tries Claude with the cloud settings in Clearotron's settings file, the Amazon keys included, as a search does.
|
|
53
|
+
- d194d41: Fixed: A search on a short or common word could return so many unrelated marks that one query filled most of the results. Any single query now contributes at most a fixed number of records. Anything beyond that is reported as a crowd, with its full count, rather than left out silently.
|
|
54
|
+
- d194d41: Fixed: A report made before this summer, reopened today, states its conditions in the same words as a new one.
|
|
55
|
+
- d194d41: New: The sign-in command from setup, `clearotron doctor` and a starting search names the copy setup installed, which is not on the PATH.
|
|
56
|
+
- d194d41: New: Setup's test turn tries Claude with the cloud settings in Clearotron's settings file, the Amazon keys included, as a search does.
|
|
57
|
+
- 6c7c7f1: For operators: the offline test suite runs in four parallel shards, so a change is checked in about a quarter of the time it used to take.
|
|
58
|
+
- d194d41: Fixed: A search covering a very large number of register records could finish with no findings document at all. Those records are now accounted for in fixed batches instead of all at once. An interrupted attempt resumes from the records still outstanding, rather than starting again.
|
|
59
|
+
- d194d41: New: A knockout report has an Export button, so anyone who opens the file can save it as a PDF.
|
|
60
|
+
- d194d41: New: Claude steps run on the newest Opus and Sonnet as soon as they ship, unless a setting holds a tier at one model.
|
|
61
|
+
- d194d41: New: Setup offers to install the reasoning program your engine uses. Before it asks, it says how much space the program takes and how to remove it.
|
|
62
|
+
|
|
63
|
+
New: Setup asks which AI should run your searches, Claude or Codex, and says what it found on this computer.
|
|
64
|
+
|
|
65
|
+
New: Claude Code or the Codex CLI already on the machine is still used first. `clearotron update` keeps the installed one current, and `clearotron doctor` says which copy runs, and its version when the program reports one.
|
|
66
|
+
|
|
67
|
+
New: Outside Windows, in demo mode, `clearotron doctor` points to setup to install the reasoning program.
|
|
68
|
+
|
|
69
|
+
New: When the reasoning program cannot be found, Global config's Engine row and the search screen name the setup command that installs it.
|
|
70
|
+
- d194d41: New: Each report names the models that did the work, including those behind the Chinese, Japanese and Korean language steps.
|
|
71
|
+
|
|
72
|
+
New: Through a cloud account, a report names the Claude model or its tier, never your organisation's own name for its deployment.
|
|
73
|
+
- d194d41: New: When a cloud account refuses the credentials, setup, `clearotron doctor` and a starting search name that cloud and the settings to check.
|
|
74
|
+
|
|
75
|
+
Fixed: When an API key is refused, setup, `clearotron doctor` and a starting search name the key to check, rather than asking for a sign-in.
|
|
76
|
+
- d194d41: Fixed: When you have used all of today's searches, the screen names the person to ask for another. On an installation with no name set it read "ask your the operator contact to run this one for you".
|
|
77
|
+
- d194d41: Fixed: A report's verdict now lists every condition it is conditional on. It used to name the first and close with "(and 2 more)". The rest sat in a separate list below it, so a reader could see that conditions existed without reading them.
|
|
78
|
+
|
|
3
79
|
## 0.3.2-beta.7
|
|
4
80
|
|
|
5
81
|
### Patch Changes
|