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/driver/run-economics.mjs
CHANGED
|
@@ -14,11 +14,14 @@
|
|
|
14
14
|
// the provider's four separately-priced token kinds.)
|
|
15
15
|
//
|
|
16
16
|
// ── WHAT A "DISPATCH" IS ──────────────────────────────────────────────────────────────────────────
|
|
17
|
-
// One model invocation: one row in `_driver/<stage>.jsonl`
|
|
18
|
-
// per ATTEMPT, so retries are separate dispatches and retry
|
|
19
|
-
// direct-API jx lanes bypass the gateway and write
|
|
20
|
-
// {model, usage} shape; they are dispatches too, under stage
|
|
21
|
-
// (run events, not dispatches) — same file selection as
|
|
17
|
+
// One model invocation: one row in `_driver/<stage>.jsonl` that tokens.mjs's `isAttemptRow` counts as
|
|
18
|
+
// a provider attempt (gateway.mjs writes one per ATTEMPT, so retries are separate dispatches and retry
|
|
19
|
+
// waste is counted, not averaged away). The direct-API jx lanes bypass the gateway and write
|
|
20
|
+
// `_driver/jx-completions.jsonl` in the same {model, usage} shape; they are dispatches too, under stage
|
|
21
|
+
// `jx-completions`. `run.jsonl` is skipped (run events, not dispatches) — same file selection as
|
|
22
|
+
// tokens.mjs, deliberately, and the SAME ROW TEST as tokens.mjs, imported rather than copied: a jx row
|
|
23
|
+
// whose turn ran and named no model carries no `model`, only `modelActual: null`, and a census that still
|
|
24
|
+
// asked for a `model` counted no dispatch and no tokens for a turn the token rollup counted with both.
|
|
22
25
|
//
|
|
23
26
|
// ── ZERO SEMANTICS: THE THING THIS MODULE EXISTS TO GET RIGHT ─────────────────────────────────────
|
|
24
27
|
// tokens.mjs sums `usage` with `u.output || 0`, so a dispatch whose usage is null contributes zero AND
|
|
@@ -32,7 +35,7 @@
|
|
|
32
35
|
// measured — the dispatch journalled a usage object (from the provider's own result envelope)
|
|
33
36
|
// streamed — usage present but RECONSTRUCTED from the stream (`signals.usageStreamed`), because the
|
|
34
37
|
// turn died before its result event. A real measurement, a weaker one, counted apart.
|
|
35
|
-
// unmeasured — the row is a dispatch
|
|
38
|
+
// unmeasured — the row is a dispatch and carries no usage at all. THE KILLED TURNS.
|
|
36
39
|
// `tokensComplete` is false whenever `unmeasured > 0`, at run level and per stage, and
|
|
37
40
|
// `unmeasuredDispatches[]` names which ones so a reader can see what the total is missing.
|
|
38
41
|
//
|
|
@@ -92,14 +95,18 @@
|
|
|
92
95
|
// so a per-record basis is the only honest shape. `tokens.mjs` keys its rollup on the requested alias
|
|
93
96
|
// for the same reason and is likewise untouched.
|
|
94
97
|
//
|
|
95
|
-
// Pure by contract: `runEconomics()` reads the run dir and nothing else — no env, no config, no network
|
|
96
|
-
//
|
|
98
|
+
// Pure by contract: `runEconomics()` reads the run dir and nothing else — no env, no config, no network.
|
|
99
|
+
// Its only driver import is the attempt-row test it shares with tokens.mjs; the log and status writers
|
|
100
|
+
// serve `stampRunEconomics()`, the only part that writes.
|
|
97
101
|
|
|
98
102
|
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
99
103
|
import { join } from "node:path";
|
|
100
104
|
import { driverDir } from "../shared/driver-dir.mjs"; //
|
|
101
105
|
import { runLog, note } from "./log.mjs";
|
|
102
106
|
import { writeRunStatus } from "./progress.mjs";
|
|
107
|
+
// tokens.mjs imports this module too (isCodeSide, stampRunEconomics). The cycle is safe because each side
|
|
108
|
+
// reads the other's bindings only inside functions, never while the module is loading.
|
|
109
|
+
import { isAttemptRow } from "./tokens.mjs";
|
|
103
110
|
|
|
104
111
|
/**
|
|
105
112
|
* The provider's separately-priced token kinds, in the driver's own `usage` vocabulary (gateway.mjs /
|
|
@@ -178,7 +185,23 @@ function billingKeyOf(rec) {
|
|
|
178
185
|
// is rather than dragged into "unknown" beside genuinely unstamped legacy rows.
|
|
179
186
|
const engine = isCodeSide(rec) ? "code" : String(rec.engine ?? "unknown");
|
|
180
187
|
const authMode = isCodeSide(rec) ? "not-provider-billed" : String(rec.authMode ?? "unknown");
|
|
181
|
-
|
|
188
|
+
// A TURN THAT NAMED NO MODEL (a jx row with `modelActual: null` and no `model`) is keyed the way the
|
|
189
|
+
// token rollup keys it (modelKey in tokens.mjs): `<engine>/no-model-reported`, a name that says the
|
|
190
|
+
// model is missing. Read through the old `?? "unknown"` it landed beside legacy rows nobody stamped,
|
|
191
|
+
// and its byBilling bucket named a different model from the rollup's byModel for the same turn.
|
|
192
|
+
//
|
|
193
|
+
// A COPY OF modelKey's RULE, NOT A SHARED ONE: tokens.mjs does not export it. So the test for "no stamp"
|
|
194
|
+
// is modelKey's own, a non-empty string `modelUsed`, and not `modelUsed == null`: under that looser test
|
|
195
|
+
// a row stamped `modelUsed: ""` keyed its bucket as the empty string while the rollup keyed the same
|
|
196
|
+
// turn `<engine>/no-model-reported`. Two copies of one rule drifting apart is how the census and the
|
|
197
|
+
// rollup came to disagree about what an attempt is, so the tests hold these two copies to each other on
|
|
198
|
+
// the rows the engine writes. They still part on a row no writer produces: no model and no engine, or
|
|
199
|
+
// engine `anthropic-agent`. This key names the missing model there, while modelKey resolves the absent
|
|
200
|
+
// model through the catalog before it asks whether one exists, and buckets the row as `undefined`.
|
|
201
|
+
const stamped = typeof rec.modelUsed === "string" && rec.modelUsed;
|
|
202
|
+
const model = !stamped && typeof rec.model !== "string"
|
|
203
|
+
? `${typeof rec.engine === "string" && rec.engine ? rec.engine : "unknown"}/no-model-reported`
|
|
204
|
+
: String(rec.modelUsed ?? rec.model ?? "unknown");
|
|
182
205
|
return { engine, authMode, model, key: `${engine}|${authMode}|${model}` };
|
|
183
206
|
}
|
|
184
207
|
|
|
@@ -223,11 +246,19 @@ function foldBilling(bucketMap, rec, cls) {
|
|
|
223
246
|
// future engine whose name began that way, which is how a vendor claim becomes a guess. An engine this
|
|
224
247
|
// table does not know is reported BY NAME and blocks the single-vendor claim, because "I do not know who
|
|
225
248
|
// billed this" and "one vendor" are different answers and only one of them is safe to print.
|
|
249
|
+
//
|
|
250
|
+
// THE NATIVE-LANGUAGE ROWS STAMP THE VENDOR ITSELF. jxBillingStamp (jx-lanes.mjs) writes as the row's
|
|
251
|
+
// engine the provider the engine door resolved, `anthropic` or `openai` (engine/auth.mjs), so those two
|
|
252
|
+
// names are engines this table must place. Without them an Anthropic-only run with a native-language turn
|
|
253
|
+
// named `anthropic` as an engine that bills to no vendor, in the same sentence that named anthropic as its
|
|
254
|
+
// one vendor. Two exact names, still a closed table.
|
|
226
255
|
export const ENGINE_VENDORS = Object.freeze({
|
|
227
256
|
"anthropic-agent": "anthropic",
|
|
228
257
|
"anthropic-direct": "anthropic",
|
|
229
258
|
"anthropic-completions": "anthropic",
|
|
230
259
|
"openai-agent": "openai",
|
|
260
|
+
"anthropic": "anthropic",
|
|
261
|
+
"openai": "openai",
|
|
231
262
|
});
|
|
232
263
|
/** The vendor an engine bills to, or null when the table does not name one. */
|
|
233
264
|
export const vendorOf = (engine) => ENGINE_VENDORS[String(engine ?? "")] ?? null;
|
|
@@ -469,7 +500,7 @@ export function runEconomics(runDir, { now = null, bytesPerOutputToken = BYTES_P
|
|
|
469
500
|
let sawDeclaredNoOutput = false;
|
|
470
501
|
|
|
471
502
|
for (const rec of rows) {
|
|
472
|
-
if (!rec
|
|
503
|
+
if (!isAttemptRow(rec)) continue; // only provider attempts are dispatches (tokens.mjs, isAttemptRow)
|
|
473
504
|
const cls = classesOf(rec.usage);
|
|
474
505
|
const streamed = cls != null && rec.signals?.usageStreamed === true;
|
|
475
506
|
|
|
@@ -44,6 +44,9 @@
|
|
|
44
44
|
// BLOCKING — without these nothing runs at all, in any product. The register and its credential (the
|
|
45
45
|
// driver throws by name at the first stage), the engine and the binary it drives, and the pool the
|
|
46
46
|
// report is written into. This is the set whose absence produced the outcome at the top of this file.
|
|
47
|
+
// And what the billing word needs, because the run door refuses without it before any turn: under
|
|
48
|
+
// `cloud` the switch of the cloud it pays through (or the gateway address), under `api-key` the
|
|
49
|
+
// engine's key; and the billing word itself, whenever the run door refuses the way it is set.
|
|
47
50
|
//
|
|
48
51
|
// NARROWING — `PERPLEXITY_API_KEY`. Its absence does NOT crash a run and does not deliver a false
|
|
49
52
|
// notice: the three clearance searches carry the common-law grid and cannot switch it off, so they
|
|
@@ -71,13 +74,16 @@
|
|
|
71
74
|
// Refusing here is refusing over OUR bug, and there is nothing for a reader to go and set.
|
|
72
75
|
//
|
|
73
76
|
// at:"order" the value an OPERATOR supplies — the register, its credential, the engine and the
|
|
74
|
-
// binary it drives. Absent, the install is not finished.
|
|
75
|
-
// AT ORDER TIME, before a stage dispatches and before
|
|
77
|
+
// binary it drives, and what the billing word needs (above). Absent, the install is not finished.
|
|
78
|
+
// The doors come up and every run is refused AT ORDER TIME, before a stage dispatches and before
|
|
79
|
+
// anything is spent, naming what is missing.
|
|
76
80
|
//
|
|
77
81
|
// The axis is on the ROW, not in the caller, for the reason the whole module exists: a start that
|
|
78
82
|
// decides for itself which names are its own and a runner that decides separately are two opinions
|
|
79
83
|
// about one list, and they drift in the direction where the second asks for less.
|
|
80
84
|
|
|
85
|
+
import { BILLING_MODES, CLOUD_SWITCH, CLOUD_SETTINGS, CLOUD_CREDENTIAL_CHECK, billingMode, cloudsSwitchedOn, resolveAuthMode } from "./engine/auth.mjs"; // — the billing row asks the one resolver which ways an engine can be paid for, and the cloud rows read its own lists
|
|
86
|
+
|
|
81
87
|
/** The pool a report is written into. Named once; the supervisor already writes it. */
|
|
82
88
|
export const POOL_ENV = "CLEAROTRON_REPORTS_DIR";
|
|
83
89
|
/** The register selection, and the engine selection. */
|
|
@@ -85,6 +91,9 @@ export const REGISTER_ENV = "CLEAROTRON_DATABASE";
|
|
|
85
91
|
export const ENGINE_ENV = "CLEAROTRON_AI";
|
|
86
92
|
/** Narrowing, never blocking — see the header. */
|
|
87
93
|
export const RESEARCH_ENV = "PERPLEXITY_API_KEY";
|
|
94
|
+
/** The settings that decide WHICH cloud a Claude turn goes to: each cloud's switch, and the gateway address.
|
|
95
|
+
* Any one of them answers the cloud billing word; start compares all of them against the services' file. */
|
|
96
|
+
export const CLOUD_ROUTES = Object.freeze([...Object.values(CLOUD_SWITCH), "ANTHROPIC_BASE_URL"]);
|
|
88
97
|
|
|
89
98
|
/** WHEN a blocking value is asked for. `START` is what `clearotron start` writes itself; `ORDER` is what
|
|
90
99
|
* an operator configures, and its absence refuses a RUN rather than an install. See the header. */
|
|
@@ -93,6 +102,35 @@ export const ORDER = "order";
|
|
|
93
102
|
|
|
94
103
|
const val = (env, name) => String(env?.[name] ?? "").trim();
|
|
95
104
|
|
|
105
|
+
// THE WAYS AN ENGINE CAN BE PAID FOR, AS THE BILLING ROW SAYS THEM. The row named all three for every engine,
|
|
106
|
+
// and Codex refuses a cloud account (engine/auth.mjs, resolveAuthMode), so a Codex install was offered a way
|
|
107
|
+
// to pay its run door refuses.
|
|
108
|
+
const PAY_WORDS = Object.freeze({ subscription: "subscription", "api-key": "API key", cloud: "cloud account" });
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The billing words `engineId` can be paid by, in BILLING_MODES order: each one asked of the resolver, the
|
|
112
|
+
* one authority on it, with what that word needs set (the engine's key for `api-key`, a cloud's switch for
|
|
113
|
+
* `cloud`), so a word the resolver accepts only when its settings are present is not read as refused. An
|
|
114
|
+
* import of a leaf with no imports of its own, so this module stays pure.
|
|
115
|
+
*/
|
|
116
|
+
export function payWays(engineId, engine = {}) {
|
|
117
|
+
return BILLING_MODES.filter((mode) => {
|
|
118
|
+
const env = { [engine.authEnv ?? "CLEAROTRON_AI_BILLING"]: mode,
|
|
119
|
+
...(mode === "api-key" && engine.apiKeyEnv ? { [engine.apiKeyEnv]: "set" } : {}),
|
|
120
|
+
...(mode === "cloud" ? { [CLOUD_SWITCH.vertex]: "1" } : {}) };
|
|
121
|
+
try { resolveAuthMode({ engineName: engineId, env }); return true; } catch { return false; }
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* A billing refusal from the run door, in words that carry no value. Every refusal names settings and the
|
|
127
|
+
* billing word, except the one for a word that is not a billing mode, which quotes the word as it is set;
|
|
128
|
+
* that quote is replaced by the setting's name. PURE. Any other message comes back unchanged.
|
|
129
|
+
*/
|
|
130
|
+
export function billingRefusalWords(message) {
|
|
131
|
+
return String(message ?? "").replace(/^([A-Z][A-Z0-9_]*)=[\s\S]*? is not a billing mode/, "$1 is set to a word that is not a billing mode");
|
|
132
|
+
}
|
|
133
|
+
|
|
96
134
|
/**
|
|
97
135
|
* Every environment name this box's configuration says a clearance needs, with the reason each one is
|
|
98
136
|
* there and whether its absence blocks or narrows.
|
|
@@ -101,9 +139,9 @@ const val = (env, name) => String(env?.[name] ?? "").trim();
|
|
|
101
139
|
* environment in hand: the composer has the supervisor's, and the guard has the one it just wrote into
|
|
102
140
|
* the unit file. A function that read the ambient environment would answer about neither.
|
|
103
141
|
*
|
|
104
|
-
* PURE.
|
|
142
|
+
* PURE, apart from the engine resolver a caller passes in `tables.resolveEngine` (see the engine row).
|
|
105
143
|
*/
|
|
106
|
-
export function runRequirements(env = {}, { registers = [], engines = {}, defaultEngine = null } = {}) {
|
|
144
|
+
export function runRequirements(env = {}, { registers = [], engines = {}, defaultEngine = null, resolveEngine = null } = {}) {
|
|
107
145
|
const out = [];
|
|
108
146
|
const push = (name, blocking, why, at = ORDER) =>
|
|
109
147
|
out.push({ name, blocking, why, at, present: Boolean(val(env, name)) });
|
|
@@ -128,11 +166,135 @@ export function runRequirements(env = {}, { registers = [], engines = {}, defaul
|
|
|
128
166
|
|
|
129
167
|
// ── THE ENGINE, AND THE BINARY IT DRIVES ─────────────────────────────────────────────────────────
|
|
130
168
|
push(ENGINE_ENV, true, "which reasoning engine runs the stages");
|
|
131
|
-
const
|
|
132
|
-
|
|
169
|
+
const engineId = val(env, ENGINE_ENV) || defaultEngine || "";
|
|
170
|
+
const engine = (engines ?? {})[engineId];
|
|
171
|
+
if (engine?.env) {
|
|
133
172
|
push(engine.env, true, `the path to the ${engine.vendor} CLI this engine drives — a stage cannot dispatch without it`);
|
|
134
|
-
|
|
135
|
-
|
|
173
|
+
// FOUND IS WHAT COUNTS, NOT SET. A program on this environment's PATH, or the copy installed with
|
|
174
|
+
// Clearotron, needs no path written anywhere, and asking only whether the variable was set refused an
|
|
175
|
+
// install whose engine the run door would have started. The resolver arrives through the tables, like
|
|
176
|
+
// everything else this module knows about the install; a caller that passes no resolver gets the
|
|
177
|
+
// variable's own answer, which is all it can see. The module's one import is engine/auth.mjs, for the
|
|
178
|
+
// billing row below: a driver leaf with no imports of its own, pure, and the one authority on which
|
|
179
|
+
// billing words an engine takes, so importing it keeps this module pure and reaches nothing in `bin/`.
|
|
180
|
+
const row = out[out.length - 1];
|
|
181
|
+
if (!row.present && typeof resolveEngine === "function") {
|
|
182
|
+
try { row.present = Boolean(resolveEngine(engineId, { env })?.resolved); } catch { /* not found is not present */ }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (engine?.authEnv) {
|
|
186
|
+
const ways = payWays(engineId, engine);
|
|
187
|
+
const said = ways.map((m) => PAY_WORDS[m]);
|
|
188
|
+
push(engine.authEnv, false, `how the engine is paid for — ${said.length > 1 ? `${said.slice(0, -1).join(", ")} or ${said.at(-1)}` : said[0]}; `
|
|
189
|
+
+ `unset means the subscription, and the adapter refuses before spending if the ${ways.includes("cloud") ? "key or cloud account" : "key"} it names is absent`);
|
|
190
|
+
const billingRow = out[out.length - 1];
|
|
191
|
+
const billingRows = out.length;
|
|
192
|
+
const word = billingMode(env);
|
|
193
|
+
const refused = "without it the engine refuses every search before spending";
|
|
194
|
+
// ── THE KEY, WHEN THE WORD IS `api-key` ──────────────────────────────────────────────────────────
|
|
195
|
+
//
|
|
196
|
+
// The same defect as the cloud below, one billing word over: the word travelled to the services' file
|
|
197
|
+
// and the key did not, so the run door refused every search after intake ("api-key but
|
|
198
|
+
// ANTHROPIC_API_KEY is not set"), while start and doctor reported nothing. Blocking at order time, like
|
|
199
|
+
// the switch: an operator's value, and the run door's own refusal without it.
|
|
200
|
+
if (word === "api-key" && engine.apiKeyEnv && ways.includes("api-key"))
|
|
201
|
+
push(engine.apiKeyEnv, true, `the key ${engine.authEnv}=api-key bills every turn to — ${refused}`);
|
|
202
|
+
// ── THE LONG-LIVED SIGN-IN, WHEN THE WORD IS THE SUBSCRIPTION ────────────────────────────────────
|
|
203
|
+
//
|
|
204
|
+
// Setup captures it on a machine that cannot complete a sign-in, which is the server a background
|
|
205
|
+
// install runs on, and the program reads it from its environment. Carried when set and never asked
|
|
206
|
+
// for: a machine signed in through the program itself needs none.
|
|
207
|
+
const tokenEnv = engine.headless?.tokenEnv;
|
|
208
|
+
if (word === "subscription" && tokenEnv && val(env, tokenEnv))
|
|
209
|
+
push(tokenEnv, false, `the long-lived sign-in the ${engine.vendor} CLI uses for the subscription on a machine it cannot sign in on; carried as set, and not asked for`);
|
|
210
|
+
// ── A CLOUD ACCOUNT, AND THE SETTINGS THAT REACH IT ──────────────────────────────────────────────
|
|
211
|
+
//
|
|
212
|
+
// The billing word travelled and the cloud did not. A machine set up to pay through a cloud account and
|
|
213
|
+
// started as background services wrote CLEAROTRON_AI_BILLING=cloud into the services' file and none of
|
|
214
|
+
// the cloud's own settings, so the run door refused every search: "none of CLAUDE_CODE_USE_VERTEX, …
|
|
215
|
+
// is set". Measured 2026-09-15 on a Microsoft Foundry configuration.
|
|
216
|
+
//
|
|
217
|
+
// ONLY WHEN THE WORD IS `cloud` AND THE ENGINE TAKES ONE — the resolver's answer through `payWays`, not
|
|
218
|
+
// a list of engines here. Under subscription or api-key nothing of a cloud is carried: a switch left on
|
|
219
|
+
// in a shell and written into the services' file sends Claude to that cloud, and the run door refuses
|
|
220
|
+
// a switch beside either word. Codex refuses a cloud account outright, so its rows do not change.
|
|
221
|
+
if (word === "cloud" && ways.includes("cloud")) {
|
|
222
|
+
const on = cloudsSwitchedOn(env);
|
|
223
|
+
const payWord = `${engine.authEnv}=cloud`;
|
|
224
|
+
// BLOCKING, AT ORDER TIME. Without the switch the run door refuses (engine/auth.mjs), so this is the
|
|
225
|
+
// same class as the engine and its program: an operator's value, whose absence refuses a run at
|
|
226
|
+
// intake and never a start.
|
|
227
|
+
for (const c of on)
|
|
228
|
+
push(CLOUD_SWITCH[c], true, `sends Claude to ${CLOUD_CREDENTIAL_CHECK[c].who}, the account ${payWord} pays through — ${refused}`);
|
|
229
|
+
if (!on.length && val(env, "ANTHROPIC_BASE_URL"))
|
|
230
|
+
push("ANTHROPIC_BASE_URL", true, `the gateway ${payWord} pays through — ${refused}`);
|
|
231
|
+
if (!on.length && !val(env, "ANTHROPIC_BASE_URL")) {
|
|
232
|
+
// NOTHING SAYS WHICH CLOUD, and a row has one name. Any one of four settings satisfies it. The row is
|
|
233
|
+
// NAMED by one real setting, Google's switch, the representative `payWays` above already uses for "a
|
|
234
|
+
// cloud account", because every reader of a row treats its name as a variable: start and doctor print
|
|
235
|
+
// it, the runner logs it, and a composite name read as four more items in doctor's list of what is
|
|
236
|
+
// missing. The REASON names all four, with whose each is. And the row carries all four as `anyOf`,
|
|
237
|
+
// which `runRequiredNames` hands out as names to read: doctor fills its view of the services by name,
|
|
238
|
+
// and reading only Google's switch reported a Microsoft machine's switch, held by its services, as
|
|
239
|
+
// missing.
|
|
240
|
+
//
|
|
241
|
+
// A SWITCH THAT IS SET AND NOT ON IS SAID, because it reads as on to a person and as off to the
|
|
242
|
+
// program: `CLAUDE_CODE_USE_FOUNDRY=0` on a Microsoft machine was answered with Google's switch
|
|
243
|
+
// and nothing else, and the reader was left to work out why the one they set did not count.
|
|
244
|
+
// And it is not an alternative to read or carry: `anyOf` is handed to the composer too, and the
|
|
245
|
+
// set-and-off line would travel through it.
|
|
246
|
+
const off = Object.values(CLOUD_SWITCH).filter((k) => val(env, k));
|
|
247
|
+
out.push({ name: CLOUD_SWITCH.vertex, anyOf: CLOUD_ROUTES.filter((k) => !off.includes(k)), blocking: true, at: ORDER, present: false,
|
|
248
|
+
why: `${payWord} pays through a cloud account and nothing names which one — set `
|
|
249
|
+
+ `${["vertex", "foundry", "bedrock"].map((c) => `${CLOUD_SWITCH[c]}=1 for ${CLOUD_CREDENTIAL_CHECK[c].who}`).join(", ")}, `
|
|
250
|
+
+ `or ANTHROPIC_BASE_URL for a gateway`
|
|
251
|
+
+ (off.length ? ` (${off.join(" and ")} ${off.length > 1 ? "are" : "is"} set, but not on: a switch is on at 1, true, yes or on)` : "")
|
|
252
|
+
+ `; ${refused}` });
|
|
253
|
+
}
|
|
254
|
+
// EVERY OTHER CLOUD SETTING THAT IS SET, CARRIED AND NEVER ASKED FOR. Which of them a cloud needs
|
|
255
|
+
// depends on how the machine signs in to it — an Azure key or the Azure sign-in, an AWS profile, an
|
|
256
|
+
// instance role or keys, gcloud or a key file — and the model pins are optional everywhere. The run
|
|
257
|
+
// door asks for none of them; the cloud refuses a missing one at the first turn. So a row is made
|
|
258
|
+
// only for a name that is set: present by construction, it can never be reported missing or refuse
|
|
259
|
+
// anything, and exists so the composer carries it. Names on CLOUD_SETTINGS only — never the rest of
|
|
260
|
+
// the environment, which would put every secret in a shell into the services' file.
|
|
261
|
+
//
|
|
262
|
+
// NEVER A SWITCH. One that is on is a row above. One that is set and not on switches nothing, and
|
|
263
|
+
// carried into the services' file it held that line there, where the add-only merge kept a later
|
|
264
|
+
// `=1` out of it for good.
|
|
265
|
+
const who = on.length === 1 ? CLOUD_CREDENTIAL_CHECK[on[0]].who : !on.length && val(env, "ANTHROPIC_BASE_URL") ? "the gateway" : "the cloud account";
|
|
266
|
+
const named = new Set(out.flatMap((r) => r.anyOf ?? [r.name]));
|
|
267
|
+
for (const k of CLOUD_SETTINGS)
|
|
268
|
+
if (!named.has(k) && !Object.values(CLOUD_SWITCH).includes(k) && val(env, k))
|
|
269
|
+
push(k, false, `one of the settings Claude reads to reach and pay ${who}; carried as set, and not asked for, because which ones a cloud needs depends on how this machine signs in to it`);
|
|
270
|
+
}
|
|
271
|
+
// ── AND ANY OTHER WAY THE RUN DOOR REFUSES HOW THIS IS PAID FOR ──────────────────────────────────
|
|
272
|
+
//
|
|
273
|
+
// The rows above name what is MISSING. The run door also refuses what is set wrongly: two clouds
|
|
274
|
+
// switched on, a switch left on beside `subscription` or `api-key`, a word that is not a billing mode,
|
|
275
|
+
// a cloud account on Codex. Each passed start's guard, the order wall and doctor, and the run was then
|
|
276
|
+
// refused after intake. Measured 2026-09-15: a services' file holding Microsoft's switch from one start
|
|
277
|
+
// and Google's from the next read clean everywhere and refused every search.
|
|
278
|
+
//
|
|
279
|
+
// ASKED OF THE RUN DOOR ITSELF, `resolveAuthMode`, over the environment being judged, and its refusal
|
|
280
|
+
// is the reason: one authority, so this check can be neither stricter nor laxer than the door. Its
|
|
281
|
+
// words carry names and the billing word, never a key. The row that turns blocking is the billing
|
|
282
|
+
// word's own, so no new name is handed to the composer: a switch beside `subscription` stays out of
|
|
283
|
+
// the services' file, as the cloud rows above intend. `present` on a row means SATISFIED, which is
|
|
284
|
+
// what every reader of it asks; for this row, set is not enough. When a row above already names what
|
|
285
|
+
// is missing, that row is the answer and this adds nothing, because the door's refusal is the same one.
|
|
286
|
+
//
|
|
287
|
+
// ONE OF THE DOOR'S REFUSALS QUOTES A VALUE: the word that is not a billing mode. A key pasted into the
|
|
288
|
+
// billing word by mistake is that value, and this reason is printed by start, by doctor and in the
|
|
289
|
+
// runner's log. So it is said by name, through billingRefusalWords below.
|
|
290
|
+
if (!out.slice(billingRows).some((r) => r.blocking && !r.present)) {
|
|
291
|
+
try { resolveAuthMode({ engineName: engineId, env }); } catch (e) {
|
|
292
|
+
if (e?.billingRefusal)
|
|
293
|
+
Object.assign(billingRow, { blocking: true, present: false, at: ORDER,
|
|
294
|
+
why: `every search is refused before spending, over how this machine is set to pay: ${billingRefusalWords(e.message)}` });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
136
298
|
|
|
137
299
|
push(RESEARCH_ENV, false, "the three clearance searches carry the common-law grid and refuse at preflight without it; a Knockout search still runs and discloses the half it skipped");
|
|
138
300
|
|
|
@@ -147,7 +309,9 @@ export function runRequirements(env = {}, { registers = [], engines = {}, defaul
|
|
|
147
309
|
* a box whose operator configured them, which is the same shape of defect one size down.
|
|
148
310
|
*/
|
|
149
311
|
export function runRequiredNames(env = {}, tables = {}) {
|
|
150
|
-
|
|
312
|
+
// A row any one of several settings satisfies names them in `anyOf`, and each is a name to carry or read.
|
|
313
|
+
// Each name once: a name handed out twice is read twice and reported twice.
|
|
314
|
+
return [...new Set(runRequirements(env, tables).flatMap((r) => r.anyOf ?? [r.name]))];
|
|
151
315
|
}
|
|
152
316
|
|
|
153
317
|
/**
|
package/driver/runner.mjs
CHANGED
|
@@ -774,15 +774,15 @@ async function backstopFailureNotice({ res, job, agentId, base, codename, studio
|
|
|
774
774
|
// starts, so nothing is spent and nothing is promised.
|
|
775
775
|
|
|
776
776
|
let __runTables = null;
|
|
777
|
-
async function runTables() {
|
|
777
|
+
export async function runTables() { // exported for the requirement-check wiring test
|
|
778
778
|
// AT CALL TIME, never a static import. `driver/run-requirements.mjs`'s header states the reason and it
|
|
779
779
|
// is load-bearing: the register SELECTION table lives in `bin/onboard.mjs`, a CLI entry point, and a
|
|
780
780
|
// static import from `driver/` would point the driver at `bin/` — the cycle that makes `clearotron
|
|
781
781
|
// doctor` exit 13 after printing most of a report.
|
|
782
782
|
if (!__runTables) {
|
|
783
783
|
const { PROVIDERS } = await import("../bin/onboard.mjs");
|
|
784
|
-
const { ENGINE_BINARIES, DEFAULT_ENGINE_ID } = await import("./driver.config.mjs");
|
|
785
|
-
__runTables = { registers: PROVIDERS, engines: ENGINE_BINARIES, defaultEngine: DEFAULT_ENGINE_ID };
|
|
784
|
+
const { ENGINE_BINARIES, DEFAULT_ENGINE_ID, resolveEngineProgram } = await import("./driver.config.mjs");
|
|
785
|
+
__runTables = { registers: PROVIDERS, engines: ENGINE_BINARIES, defaultEngine: DEFAULT_ENGINE_ID, resolveEngine: resolveEngineProgram };
|
|
786
786
|
}
|
|
787
787
|
return __runTables;
|
|
788
788
|
}
|
package/driver/stages.mjs
CHANGED
|
@@ -1260,7 +1260,7 @@ export const STAGES = {
|
|
|
1260
1260
|
},
|
|
1261
1261
|
"scope_jurisdictions / excluded_jurisdictions / scope_basis — typed fields the driver renders": {
|
|
1262
1262
|
class: "mechanical:code-rendered", tokens: ["frame_scope_missing"],
|
|
1263
|
-
why: "CLASS ALIGNED WITH `## Instructed scope` in this same stage — #850 rules that row \"Code stamps the section from _driver/instructed-scope.json\", and the instructed jurisdictions are in that same file. Not pre-bound: no form carries them. On the instructed branch the driver already holds the list and hands it over; the model retypes it into a shape the driver dictates. verify.mjs
|
|
1263
|
+
why: "CLASS ALIGNED WITH `## Instructed scope` in this same stage — #850 rules that row \"Code stamps the section from _driver/instructed-scope.json\", and the instructed jurisdictions are in that same file. Not pre-bound: no form carries them. On the instructed branch the driver already holds the list and hands it over; the model retypes it into a shape the driver dictates. verify.mjs checkFindingsSibling string-compares it back — `add(\"jurisdictions\", scope.jurisdictions)` — failing frame_scope_missing:jurisdictions, so this half IS policed, unlike the campaign-shape twin above.",
|
|
1264
1264
|
},
|
|
1265
1265
|
"Scope reasoning — search-wide/cite-narrow, the in-scope-by-reach routes, and the reopen trigger behind each exclusion": {
|
|
1266
1266
|
class: "judgment", tokens: [],
|
|
@@ -2482,8 +2482,12 @@ export const STAGES = {
|
|
|
2482
2482
|
class: "judgment", tokens: ["too_short", "missing"],
|
|
2483
2483
|
why: "'The only relevance judge' — the funnel pre-gated nothing (prelim-register SKILL.md, `## Coverage = the band blocks`). #850 keeps findings prose / relevance gate / opposition / Option-D / position rows as J.",
|
|
2484
2484
|
},
|
|
2485
|
+
"every record the run carried into this stage ends somewhere — a findings row, a Negative-results drop, or a Disagreement resolution": {
|
|
2486
|
+
class: "judgment", tokens: ["registerdigest_unaccounted_records", "registerdigest_nothing_judged", "registerdigest_accounting_unreadable", "registerdigest_model_missing", "registerdigest_batch_unknown", "registerdigest_double_counted", "registerdigest_model_write_failed"],
|
|
2487
|
+
why: "A record that simply goes unmentioned is a silent recall loss — the one failure this stage's output exists to prevent, and the one no reader of the document can see, because a band judged in part looks exactly like a band judged in full. Armed by an era stamp, so archived runs replay to the verdicts they always had. It is checked at the CALL, scoped to the batch that call was handed, and again at the EXIT over the union: the call-time scope is what lets a dense band be recorded at all, and the exit is where that concession is paid for. Four of these are driver-written and say so — a run stamped for accounting whose own facts or stored model are missing is this driver's bug, not a model defect, and telling a seat to re-state cannot fix it.",
|
|
2488
|
+
},
|
|
2485
2489
|
"the Sheet-1 findings row's identifier cells — URI, Mark, Owner, Country, Classes, Status, Filed, Expiry": {
|
|
2486
|
-
class: "mechanical:tool-written", tokens: ["registerdigest_uri_missing", "registerdigest_uri_unknown"],
|
|
2490
|
+
class: "mechanical:tool-written", tokens: ["registerdigest_uri_missing", "registerdigest_uri_unknown", "registerdigest_flag_reason_missing", "registerdigest_verify_invalid"],
|
|
2487
2491
|
why: "CONVERTED (conversion 11): the seat sends the position's `uri` and the driver renders every cell from the band record it names — record_id, mark_text, classes, status, owner_name, owner_country, application_date, registration_date, expiry_date. The join is now the check: a uri no band record carries is refused AT THE CALL, where restating it costs nothing, instead of producing a plausible row of retyped cells that fails downstream or nowhere. The DECISION that a position earns a row stays judgment (element above); the cells were never anything but transcription.",
|
|
2488
2492
|
},
|
|
2489
2493
|
"the full clickable record URL, composed from providers/<name>.md 'Record base host' plus the record `uri`": {
|
|
@@ -2495,7 +2499,7 @@ export const STAGES = {
|
|
|
2495
2499
|
why: "CONVERTED (conversion 11): the driver stamps the provider and its environment word into the rendered document from the run config it already holds, and the tool takes no field for either. 'Exactly one register per run' (prelim-register SKILL.md, `## Provider`) and digest.md's `## Provider` note conceded 'the tag is constant across the findings file' — a constant the seat was retyping onto every record.",
|
|
2496
2500
|
},
|
|
2497
2501
|
"Negative-results drop rows — the Notes cell carrying URI, screen_verdict, class and status": {
|
|
2498
|
-
class: "mechanical:tool-written", tokens: ["registerdigest_uri_unknown", "registerdigest_drop_reason_missing"],
|
|
2502
|
+
class: "mechanical:tool-written", tokens: ["registerdigest_uri_unknown", "registerdigest_drop_reason_missing", "registerdigest_drop_ground_invalid", "registerdigest_drop_ground_contradicted"],
|
|
2499
2503
|
why: "CONVERTED (conversion 11): the seat sends the dropped record's `uri` and its one-line reason; the driver renders the Notes cell's four provenance fields from the band record. This is the element the conversion most clearly repays — the acceptance gate used to parse those fields back out to check the model's retyping against material the driver already held, which is a guard comparing a value with a copy of itself. The DROP DECISION and its why stay judgment (the findings-prose element), and a drop with no stated reason is refused: a batch-dropped candidate with no row is a silent recall loss.",
|
|
2500
2504
|
},
|
|
2501
2505
|
"## Summary counts — total queries executed (search + detail-fetch), enumerated records across N axes, crowd-descriptor count, candidates past the gate, surfaced count, open-verification-flag count": {
|
|
@@ -2507,7 +2511,7 @@ export const STAGES = {
|
|
|
2507
2511
|
why: "CONVERTED (conversion 11): the driver renders the Audit trail table from the same artifacts as the Summary counts, plus `_query` which digest.md:388 says 'the driver stamps at merge' — carrying that forward was transcription of a driver stamp. The judgment half — flagging a unit that shortcut its axis — stays in the findings-prose element.",
|
|
2508
2512
|
},
|
|
2509
2513
|
"INSTRUCTED CHECKS — the answer to each requester ask the register owns": {
|
|
2510
|
-
class: "judgment", tokens: [],
|
|
2514
|
+
class: "judgment", tokens: ["registerdigest_instructed_incomplete"],
|
|
2511
2515
|
why: "Answering a lawyer's question from the frozen band, including the honest 'the frozen material cannot answer this' that becomes an open coverage row. No artifact holds it. (No token here: intake_ask_unanswered lives on validators.narrative, not registerFindings.)",
|
|
2512
2516
|
},
|
|
2513
2517
|
"the record ids read while answering each instructed check": {
|
|
@@ -2515,7 +2519,7 @@ export const STAGES = {
|
|
|
2515
2519
|
why: "CONVERTED (conversion 11): the driver renders the ids beneath each instructed check from its own reading audit, and the tool takes {ask, answer} only. Every band_shape / band_lookup / band_record call lands in reading-log.jsonl with its args (the pattern #850 names as already existing for the band tools), so the driver held this list the whole time the seat was being asked to reproduce it.",
|
|
2516
2520
|
},
|
|
2517
2521
|
"adopt-or-override each placement by engaging its reason, and the `### Disagreement resolutions` rows (one per surfaced disagreement and per borderline:true, each ADOPTED/OVERRODE in writing)": {
|
|
2518
|
-
class: "judgment", tokens: [],
|
|
2522
|
+
class: "judgment", tokens: ["registerdigest_adjudication_invalid", "registerdigest_adjudication_incomplete"],
|
|
2519
2523
|
why: "Answering the promotion question the other way, in writing, against a reason another stage authored. #850 keeps it J. The row's SUBJECT is handed over as data (the driver appends the PLACEMENT RULINGS TAIL block, pipeline.mjs:3584), so nothing here is a fetch. [citation unverified]",
|
|
2520
2524
|
},
|
|
2521
2525
|
// ── REWRITTEN, NEVER DELETED (the ruling) — AND THE ROW THAT COST THIS CONVERSION A DESIGN ──
|
|
@@ -2843,8 +2847,8 @@ export const STAGES = {
|
|
|
2843
2847
|
// 20-min override on the VELTRIPHEN run). NOTE 2026-06-17: Opus fast mode was REMOVED here and everywhere
|
|
2844
2848
|
// (it ~2.5x'd subscription usage → 5h-cap 429s); HIGH effort retained.
|
|
2845
2849
|
// CLEAROTRON_SYNTHESIS_MODEL (2026-07-10): stage-specific override for a live A/B test (Fable vs Opus 4.8) on
|
|
2846
|
-
// just this stage —
|
|
2847
|
-
//
|
|
2850
|
+
// just this stage — stage-specific because a tier-wide override would retarget all 6 opus stages.
|
|
2851
|
+
// Unset ⇒ unchanged default "opus". Toggled live in
|
|
2848
2852
|
// the service's EnvironmentFile (a oneshot unit — no restart needed, takes effect on the next
|
|
2849
2853
|
// queue-triggered run), never hardcoded here.
|
|
2850
2854
|
model: process.env.CLEAROTRON_SYNTHESIS_MODEL || "opus", thinking: "high", timeoutSec: 2500, stallSec: 900,
|
|
@@ -3516,7 +3520,7 @@ export const STAGES = {
|
|
|
3516
3520
|
},
|
|
3517
3521
|
"the `[on: N, M]` flag ordinals — which findings each flag names": {
|
|
3518
3522
|
class: "mechanical:code-extracted", tokens: [],
|
|
3519
|
-
why: "#850: selection against the finding index the driver already holds; targetsOf's normalised prose join is the fallback that already fails (6 of 9 flags resolved to nothing on a delivered run). NO TOKEN: parseOn exists at verify.mjs
|
|
3523
|
+
why: "#850: selection against the finding index the driver already holds; targetsOf's normalised prose join is the fallback that already fails (6 of 9 flags resolved to nothing on a delivered run). NO TOKEN: parseOn exists at verify.mjs commonLawHalfEvidence and validators.seniorEyeReview never calls it — the skill file itself says \"either every flag has one or none of them do any work\", and nothing checks which state a review is in",
|
|
3520
3524
|
},
|
|
3521
3525
|
"the section titled exactly \"PLAN-EXECUTION CHECK\"": {
|
|
3522
3526
|
class: "mechanical:code-rendered", tokens: ["plan_audit_missing"],
|