clearotron 0.3.2-beta.6 → 0.3.2-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +24 -23
- package/INSTALL.md +142 -75
- package/README.md +3 -3
- package/bin/onboard.mjs +637 -216
- package/bin/start.mjs +133 -23
- package/bin/update.mjs +82 -11
- package/build-info.json +2 -2
- package/docs/architecture/04-configuration-reference.md +26 -11
- package/docs/architecture/05-config-governance.md +17 -7
- package/driver/CHANGELOG.md +83 -0
- package/driver/band-size.mjs +59 -0
- package/driver/config-inventory.mjs +112 -9
- package/driver/connotation-search.mjs +45 -0
- package/driver/contract-arm2-baseline.json +1 -3
- package/driver/contract-e3-backlog.mjs +29 -29
- package/driver/contract-vocabulary.mjs +59 -25
- package/driver/door-gates.mjs +41 -7
- package/driver/driver.config.mjs +272 -59
- package/driver/engine/CONTRACT.md +10 -3
- package/driver/engine/README.md +2 -2
- package/driver/engine/anthropic-agent.mjs +77 -21
- package/driver/engine/auth.mjs +129 -10
- package/driver/engine/jx-turn.mjs +7 -6
- package/driver/engine/mcp/recording-server.mjs +13 -0
- package/driver/engine/openai-agent.mjs +4 -2
- package/driver/engine/probe.mjs +110 -23
- package/driver/findings-model.mjs +1 -1
- package/driver/flag-snapshot.mjs +28 -5
- package/driver/gateway.mjs +30 -21
- package/driver/jx-lanes.mjs +21 -2
- package/driver/jx-units.mjs +6 -3
- package/driver/jx.mjs +4 -2
- package/driver/matter-frame-record.mjs +90 -1
- package/driver/named-band.mjs +34 -2
- package/driver/package.json +1 -1
- package/driver/pipeline.mjs +391 -26
- package/driver/portal-config-view.mjs +30 -1
- package/driver/portal-report.mjs +15 -1
- package/driver/portal-service.mjs +46 -6
- package/driver/predelivery-lint.mjs +12 -2
- package/driver/publish/index.mjs +46 -5
- package/driver/publish/knockout.mjs +10 -1
- package/driver/publish/render-knockout.mjs +69 -7
- package/driver/publish/render.mjs +170 -59
- package/driver/publish/report-data.mjs +4 -1
- package/driver/publish/report-topbar.mjs +58 -0
- package/driver/publish/templates/report.css +28 -2
- package/driver/publish/xlsx.mjs +13 -1
- package/driver/register-availability.mjs +2 -2
- package/driver/register-coverage.mjs +94 -1
- package/driver/register-digest-record.mjs +236 -11
- package/driver/register-plan.mjs +170 -0
- package/driver/result-noun-fields.mjs +7 -4
- package/driver/run-economics.mjs +41 -10
- package/driver/run-requirements.mjs +173 -9
- package/driver/runner.mjs +3 -3
- package/driver/stages.mjs +12 -8
- package/driver/suite-census.json +162 -72
- package/driver/systemd/README.md +7 -4
- package/driver/terminal-clamp.mjs +107 -1
- package/driver/tokens.mjs +169 -3
- package/driver/unit-environment.mjs +42 -15
- package/driver/unit-inventory.mjs +19 -2
- package/driver/verify.mjs +50 -5
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/http-server.mjs +4 -0
- package/mcp-server/lib/audit.mjs +11 -1
- package/mcp-server/lib/http-handler.mjs +6 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +16 -2
- package/package.json +1 -1
- package/portal-ui/dist/assets/{index-5UyqAyNM.js → index-6jzO9HiX.js} +155 -79
- package/portal-ui/dist/index.html +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/clarivate/src/capabilities.js +5 -5
- package/providers/clarivate/src/core.js +1 -1
- package/providers/corsearch/src/core.js +2 -2
- package/providers/jx/README.md +2 -1
- package/providers/jx/src/turn-envelope.mjs +8 -3
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/perplexity/src/core.js +3 -3
- package/providers/signa/src/capabilities.js +5 -6
- package/providers/signa/src/core.js +1 -1
- package/providers/uspto-local/README.md +1 -1
- package/scripts/authority-boundary-probe.mjs +4 -2
- package/scripts/env-audit.mjs +12 -6
- package/scripts/freeze-example-run.mjs +49 -16
- package/scripts/generated-files-are-current.mjs +69 -4
- package/scripts/release-duplicate-notes.mjs +246 -0
- package/scripts/release-publish-guard.mjs +64 -6
- package/scripts/report-print-check.mjs +194 -0
- package/scripts/settings-render-check.mjs +75 -2
- package/scripts/test-full.mjs +96 -3
- package/scripts/test-run.mjs +10 -0
- package/shared/deployment-box.mjs +7 -2
- package/shared/driver-dir.mjs +1 -1
- package/shared/names-in-force.mjs +1 -1
package/driver/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,88 @@
|
|
|
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
|
+
|
|
79
|
+
## 0.3.2-beta.7
|
|
80
|
+
|
|
81
|
+
### Patch Changes
|
|
82
|
+
|
|
83
|
+
- 4bde529: Fixed: A clearance that completed all but one or two of its local-language searches now delivers the report. Before, a single search that did not complete threw the whole clearance away, including the fifty-nine that had run. The report says which term's search was short.
|
|
84
|
+
- b5b789c: Fixed: The exported PDF no longer prints a collapsed arrow above sections that are already fully open.
|
|
85
|
+
|
|
3
86
|
## 0.3.2-beta.6
|
|
4
87
|
|
|
5
88
|
### Patch Changes
|
package/driver/band-size.mjs
CHANGED
|
@@ -64,3 +64,62 @@ export function bandSizeForStage(stage, paths, io) {
|
|
|
64
64
|
if (!BAND_READING_STAGES.has(stage)) return undefined;
|
|
65
65
|
return bandSizeAtDispatch(paths, io);
|
|
66
66
|
}
|
|
67
|
+
|
|
68
|
+
// ── A STAGE'S TIME LIMIT IS DERIVED FROM WHAT IT IS HANDED, NEVER A FIXED CONSTANT ────────────────
|
|
69
|
+
//
|
|
70
|
+
// THE DEFECT. The two stages that read the register band carried per-stage constants — numbers chosen
|
|
71
|
+
// once, against a band nobody recorded beside them. On a dense matter both died at their wall having
|
|
72
|
+
// written nothing: the placement attempt at 2,765s against a 2,700s budget, the digest at 2,470s
|
|
73
|
+
// against 2,400s. Both sat exactly AT the ceiling, so what ended them was the budget expiring rather
|
|
74
|
+
// than any guard firing; the stall guards had half an hour of quiet to fire in and could not, because
|
|
75
|
+
// tokens were still moving. Nothing malfunctioned. The number was simply sized for a different matter.
|
|
76
|
+
//
|
|
77
|
+
// THE TWO MEASUREMENTS THESE CONSTANTS ARE SET AGAINST, named here because a budget without the band
|
|
78
|
+
// it was measured at is the thing being fixed:
|
|
79
|
+
// · 2026-09-16, the dense matter: an 8 MB merged band; the placement attempt needed more than 2,765s
|
|
80
|
+
// and was killed at 2,700s. The derived limit at that size must exceed what the attempt took.
|
|
81
|
+
// · Every ordinary matter before it: a band at or under REFERENCE_MB, where 2,700s was sufficient and
|
|
82
|
+
// is what those runs are verified at. At or below that size the derivation must return the base
|
|
83
|
+
// unchanged, so no existing run's budget moves and no archived verdict is re-decided.
|
|
84
|
+
export const LIMIT_REFERENCE_MB = 2;
|
|
85
|
+
export const LIMIT_GROWTH_PER_MB = 0.03;
|
|
86
|
+
// The band size above which a stage is refused at dispatch rather than started. A limit past this is
|
|
87
|
+
// not a budget, it is a prediction that the stage will die: the 177 MB band measured on an earlier
|
|
88
|
+
// round would derive past an hour and a half, and starting it spends that hour and a half to arrive
|
|
89
|
+
// where the refusal already is. Refusing NAMES the size, which is the finding; being killed does not.
|
|
90
|
+
export const LIMIT_CEILING_SEC = 5400;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The time limit for this dispatch, derived from the band it is handed. PURE.
|
|
94
|
+
*
|
|
95
|
+
* ABOVE THE REFERENCE SIZE ONLY. A band at or under the reference returns the stage's own base
|
|
96
|
+
* unchanged — that is what every run before this is verified at, and a derivation that moved those
|
|
97
|
+
* numbers would re-decide archived verdicts to fix a defect they never had.
|
|
98
|
+
*
|
|
99
|
+
* AN UNMEASURABLE BAND RETURNS THE BASE, and says so through `basis`. The alternatives are worse in
|
|
100
|
+
* both directions: guessing a bigger number spends a client's money on an input nobody measured, and
|
|
101
|
+
* guessing a smaller one kills a stage for a band that might have been ordinary. The base is what the
|
|
102
|
+
* run would have used anyway, so an absent measurement changes nothing rather than changing something
|
|
103
|
+
* arbitrary.
|
|
104
|
+
*/
|
|
105
|
+
export function derivedLimitSec(baseSec, bandSize) {
|
|
106
|
+
const base = Number.isFinite(baseSec) && baseSec > 0 ? baseSec : null;
|
|
107
|
+
if (base === null) return { sec: null, inputBytes: null, basis: "no base for this stage" };
|
|
108
|
+
const bytes = Number.isFinite(bandSize?.bytes) ? bandSize.bytes : null;
|
|
109
|
+
if (bytes === null) return { sec: base, inputBytes: null, basis: bandSize?.absent ? `band unmeasured: ${bandSize.absent}` : "no band for this stage" };
|
|
110
|
+
const mb = bytes / (1024 * 1024);
|
|
111
|
+
const over = Math.max(0, mb - LIMIT_REFERENCE_MB);
|
|
112
|
+
const sec = Math.round(base * (1 + LIMIT_GROWTH_PER_MB * over));
|
|
113
|
+
return { sec, inputBytes: bytes, basis: over > 0 ? `derived from ${mb.toFixed(1)} MB` : `at or under the ${LIMIT_REFERENCE_MB} MB reference` };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Is this derived limit past the point where starting the stage only buys a later kill? PURE. */
|
|
117
|
+
export function limitExceedsCeiling(sec) {
|
|
118
|
+
return Number.isFinite(sec) && sec > LIMIT_CEILING_SEC;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** The refusal a dispatch past the ceiling carries — it NAMES the size, which is the finding. PURE. */
|
|
122
|
+
export function ceilingRefusal(stage, derived) {
|
|
123
|
+
const mb = Number.isFinite(derived?.inputBytes) ? (derived.inputBytes / (1024 * 1024)).toFixed(1) : "an unmeasured";
|
|
124
|
+
return `stage_input_over_ceiling:${stage} was handed a ${mb} MB band, which derives a ${derived?.sec}s limit against a ${LIMIT_CEILING_SEC}s ceiling — the stage is refused at dispatch rather than started, because a limit past the ceiling is a prediction that it will be killed and starting it spends the whole budget to arrive at the same place. The band size is the finding: a band this size is the defect, not the budget`;
|
|
125
|
+
}
|
|
@@ -42,7 +42,10 @@ import {
|
|
|
42
42
|
DEFAULT_ENGINE_ID, ENGINE_BINARIES, PROVIDERS, RESEARCH_PROVIDERS, SERP_PROVIDERS,
|
|
43
43
|
missingCredentials, preflightEngineBinary, providerIdFrom,
|
|
44
44
|
} from "./driver.config.mjs";
|
|
45
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
resolveAuthMode, billingMode, cloudsSwitchedOn, BILLING_MODES, CLOUD_SWITCH, CLOUD_CREDENTIAL_CHECK,
|
|
47
|
+
} from "./engine/auth.mjs";
|
|
48
|
+
import { billingRefusalWords } from "./run-requirements.mjs"; // — a refusal that quotes the billing word says it by name
|
|
46
49
|
import { CASELAW_BRIDGES } from "./engine/mcp/gather-config.mjs"; // — the list that decides what is spawned
|
|
47
50
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
48
51
|
import { join } from "node:path";
|
|
@@ -60,7 +63,9 @@ const shown = (names) => names.map((n) => n);
|
|
|
60
63
|
* absent, because the alternative is silently billing a subscription the operator thought they had
|
|
61
64
|
* stopped using (engine/auth.mjs's opening argument). A writer that caught that and recorded "unknown"
|
|
62
65
|
* would erase precisely the misconfiguration a staff config page exists to surface, so it is caught and
|
|
63
|
-
* recorded AS ITSELF: mode `api-key`, `apiBilled: false`, and a fault naming the variable to set.
|
|
66
|
+
* recorded AS ITSELF: mode `api-key`, `apiBilled: false`, and a fault naming the variable to set. The
|
|
67
|
+
* cloud mode's refusals, and a word that is not a mode at all, are recorded as themselves too, by the
|
|
68
|
+
* refusal's own sentence.
|
|
64
69
|
*
|
|
65
70
|
* `apiBilled` is what the page should believe over `mode` — the two come apart in exactly this case,
|
|
66
71
|
* and only one of them describes who gets the invoice.
|
|
@@ -77,14 +82,38 @@ export function engineInventory(env = process.env) {
|
|
|
77
82
|
const billing = (() => {
|
|
78
83
|
try {
|
|
79
84
|
const a = resolveAuthMode({ engineName: id, env });
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
// `cloud` is the resolver's own answer, never re-derived here: the account a run bills is whatever
|
|
86
|
+
// the run door resolves, and a page that worked it out a second way could name another.
|
|
87
|
+
const cloud = a.cloud ?? null;
|
|
88
|
+
return { mode: a.mode, apiBilled: a.apiBilled === true, missing: [], cloud, cloudName: cloudName(cloud) };
|
|
89
|
+
} catch (e) {
|
|
90
|
+
// Recorded AS ITSELF, whichever refusal it was. An API-key mode with no key names the variable to
|
|
91
|
+
// set, reconstructed from the table so it cannot drift from it. Every other refusal (a cloud mode with
|
|
92
|
+
// no cloud switched on or with two, a cloud switch beside a mode it contradicts, a word that is not a
|
|
93
|
+
// mode) leaves `missing` empty, because the page reads `missing` as "set this key" and a key that is
|
|
94
|
+
// set is not missing, and carries a `reason` the page words itself (billingRefusalReason, below).
|
|
95
|
+
const mode = billingMode(env);
|
|
96
|
+
const keyAbsent = Boolean(spec?.apiKeyEnv) && String(env[spec.apiKeyEnv] ?? "") === "";
|
|
97
|
+
if (mode === "api-key" && keyAbsent) {
|
|
98
|
+
return { mode, apiBilled: false, missing: shown([spec.apiKeyEnv]), cloud: null, cloudName: null };
|
|
99
|
+
}
|
|
100
|
+
const reason = billingRefusalReason(id, env);
|
|
101
|
+
const notAMode = reason.kind === "not-a-mode";
|
|
84
102
|
return {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
103
|
+
// A WORD THAT IS NOT A MODE IS NOT RECORDED. It is whatever was typed into the setting that decides
|
|
104
|
+
// who pays, and a key pasted there by mistake is that word; this inventory is written to a file and
|
|
105
|
+
// served to a browser. So the mode reads `unknown`, and the refusal says the setting by name.
|
|
106
|
+
mode: notAMode ? "unknown" : mode,
|
|
107
|
+
apiBilled: false, missing: [], cloud: null, cloudName: null,
|
|
108
|
+
// THAT REFUSAL IS WRITTEN HERE, NOT CLEANED FROM THE RESOLVER'S. The resolver's sentence quotes the
|
|
109
|
+
// typed word, and removing it by pattern depends on how the word is spelled: a word that itself
|
|
110
|
+
// contains " is not a billing mode" left its tail behind. Built from names alone, nothing typed can
|
|
111
|
+
// reach it. Every other refusal quotes only setting names and the three mode words.
|
|
112
|
+
refusal: notAMode
|
|
113
|
+
? `${reason.setting} is set to a word that is not a billing mode — refusing to guess, because the guess `
|
|
114
|
+
+ `would bill the subscription. One of: ${reason.modes.join(", ")}.`
|
|
115
|
+
: billingRefusalWords(String(e?.message ?? e)),
|
|
116
|
+
reason,
|
|
88
117
|
};
|
|
89
118
|
}
|
|
90
119
|
})();
|
|
@@ -104,6 +133,80 @@ export function engineInventory(env = process.env) {
|
|
|
104
133
|
return { id, vendor: spec?.vendor ?? null, known: Boolean(spec), billing, binaryPresent };
|
|
105
134
|
}
|
|
106
135
|
|
|
136
|
+
/**
|
|
137
|
+
* What a reader calls the account a cloud mode bills: "Google Cloud", "Microsoft Azure", "Amazon Bedrock"
|
|
138
|
+
* or "Gateway". Null for no cloud.
|
|
139
|
+
*
|
|
140
|
+
* ONE TABLE OF CLOUD NAMES, NOT TWO. `CLOUD_CREDENTIAL_CHECK` in engine/auth.mjs already names each cloud
|
|
141
|
+
* the way a reader knows it, for the advice a refused sign-in prints, and this reads it rather than keeping
|
|
142
|
+
* a second list that could name a cloud differently on the page. It writes the gateway "the gateway", for
|
|
143
|
+
* use inside a sentence; a label on a page drops the article and starts with a capital, and that one
|
|
144
|
+
* adjustment is the whole of what this adds.
|
|
145
|
+
*/
|
|
146
|
+
export function cloudName(cloud) {
|
|
147
|
+
const who = CLOUD_CREDENTIAL_CHECK[cloud]?.who;
|
|
148
|
+
if (!who) return null;
|
|
149
|
+
const bare = who.replace(/^the /, "");
|
|
150
|
+
return bare.charAt(0).toUpperCase() + bare.slice(1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* WHY A BILLING SETTING REFUSES EVERY SEARCH, as data: which refusal it is and the names involved, so the
|
|
155
|
+
* config page words it once, in plain English, without reading the resolver's sentence apart.
|
|
156
|
+
*
|
|
157
|
+
* `kind` is one of:
|
|
158
|
+
* switch-beside-mode subscription or api-key, and a cloud's switch on beside it (`clouds`)
|
|
159
|
+
* two-clouds cloud, and more than one cloud switched on (`clouds`)
|
|
160
|
+
* no-cloud cloud, and no cloud switched on and no gateway address (`clouds` lists every
|
|
161
|
+
* switch, and `gateway` the gateway's setting, as the choices to make)
|
|
162
|
+
* not-a-mode a word that is not a billing mode (`modes` are the ones this engine takes)
|
|
163
|
+
* cloud-on-codex cloud, on the Codex engine, which a cloud account cannot pay for (`engineSetting`
|
|
164
|
+
* and `engineChoice` name the Claude engine only where it would pay: one cloud
|
|
165
|
+
* switched on, or none and a gateway address; otherwise that move is one more refusal)
|
|
166
|
+
* unclassified a refusal this list does not name; the page says only that searches are refused.
|
|
167
|
+
* Not "other": that is an assistant's id in connect-clients.mjs, and a surface may
|
|
168
|
+
* not name one in code
|
|
169
|
+
*
|
|
170
|
+
* `defaulted` says the billing setting is blank, so `subscription` is the default and not a word anybody
|
|
171
|
+
* wrote: a page that said "payment is set to subscription" would send a reader looking for a line their
|
|
172
|
+
* settings file does not have. It is the state measured on Foundry, a switch on and the word unset.
|
|
173
|
+
*
|
|
174
|
+
* THE SAME PREDICATES AS THE RESOLVER, IN ITS ORDER. `resolveAuthMode` refuses by throwing a sentence and
|
|
175
|
+
* nothing else, so the kind is read back from the same environment through the same functions it uses
|
|
176
|
+
* (`billingMode`, `cloudsSwitchedOn`), in the order it checks them. Called only once it has thrown.
|
|
177
|
+
*
|
|
178
|
+
* NAMES, NEVER A VALUE. `mode` is sent only when it is one of the three modes; the word in a `not-a-mode`
|
|
179
|
+
* refusal is whatever was typed, and it is not sent at all.
|
|
180
|
+
*/
|
|
181
|
+
export function billingRefusalReason(id, env = process.env) {
|
|
182
|
+
const mode = billingMode(env);
|
|
183
|
+
const setting = "CLEAROTRON_AI_BILLING";
|
|
184
|
+
const clouds = (ids) => ids.map((c) => ({ name: cloudName(c), setting: CLOUD_SWITCH[c] }));
|
|
185
|
+
const defaulted = String(env[setting] ?? "").trim() === "";
|
|
186
|
+
const base = { setting, mode: null, defaulted, clouds: [], modes: [], gateway: null, engineSetting: null, engineChoice: null };
|
|
187
|
+
if (id === "openai-agent") {
|
|
188
|
+
if (mode === "cloud") {
|
|
189
|
+
// The resolver's own test for a cloud the Claude engine would pay through, in its words: one switch,
|
|
190
|
+
// or no switch and a gateway address.
|
|
191
|
+
const on = cloudsSwitchedOn(env);
|
|
192
|
+
const claudeWouldPay = on.length === 1 || (!on.length && Boolean(env.ANTHROPIC_BASE_URL));
|
|
193
|
+
return { ...base, kind: "cloud-on-codex", mode, modes: ["subscription", "api-key"],
|
|
194
|
+
...(claudeWouldPay ? { engineSetting: "CLEAROTRON_AI", engineChoice: "anthropic-agent" } : {}) };
|
|
195
|
+
}
|
|
196
|
+
if (mode !== "subscription" && mode !== "api-key") return { ...base, kind: "not-a-mode", modes: ["subscription", "api-key"] };
|
|
197
|
+
}
|
|
198
|
+
if (id === "anthropic-agent") {
|
|
199
|
+
const on = cloudsSwitchedOn(env);
|
|
200
|
+
if ((mode === "subscription" || mode === "api-key") && on.length) return { ...base, kind: "switch-beside-mode", mode, clouds: clouds(on) };
|
|
201
|
+
if (mode === "cloud" && on.length > 1) return { ...base, kind: "two-clouds", mode, clouds: clouds(on) };
|
|
202
|
+
if (mode === "cloud" && !on.length) {
|
|
203
|
+
return { ...base, kind: "no-cloud", mode, clouds: clouds(Object.keys(CLOUD_SWITCH)), gateway: "ANTHROPIC_BASE_URL" };
|
|
204
|
+
}
|
|
205
|
+
if (!BILLING_MODES.includes(mode)) return { ...base, kind: "not-a-mode", modes: [...BILLING_MODES] };
|
|
206
|
+
}
|
|
207
|
+
return { ...base, kind: "unclassified", mode: BILLING_MODES.includes(mode) ? mode : null };
|
|
208
|
+
}
|
|
209
|
+
|
|
107
210
|
/**
|
|
108
211
|
* The three states an install can be in, as far as running a search goes..
|
|
109
212
|
*
|
|
@@ -276,6 +276,51 @@ export const queryKey = (s) => String(s ?? "")
|
|
|
276
276
|
.trim()
|
|
277
277
|
.toLowerCase();
|
|
278
278
|
|
|
279
|
+
/**
|
|
280
|
+
* The POPULATION behind `parsePrRiskResults`, because that reader reduces its input and nothing at a
|
|
281
|
+
* call site said so.
|
|
282
|
+
*
|
|
283
|
+
* `parsePrRiskResults` folds rows into a map keyed on the raw query text, so two rows carrying the same
|
|
284
|
+
* query become one and its output is smaller than the ledger it read. Every count taken during one
|
|
285
|
+
* evening's diagnosis was post-fold, by two people, and neither had named the raw population — so "the
|
|
286
|
+
* seat wrote 59 rows" and "59 rows survived the fold" were indistinguishable, and they are different
|
|
287
|
+
* facts with different causes. A seat that recorded one query twice and skipped another looks identical
|
|
288
|
+
* to a seat that simply skipped one, from the folded count alone.
|
|
289
|
+
*
|
|
290
|
+
* Returns both counts so a caller can say which it means. `rows` is what the ledger carries; `distinct`
|
|
291
|
+
* is what the fold leaves; the difference is rows that repeat a query already counted.
|
|
292
|
+
*/
|
|
293
|
+
/**
|
|
294
|
+
* THE TWO LABELS THE MEANING GATE PUTS ON A QUERY IT COULD NOT JOIN, and they are a CONTRACT rather
|
|
295
|
+
* than prose: `gateway.mjs` reads them back to choose which repair a seat is offered, and the two
|
|
296
|
+
* repairs are opposite. Re-wording either one in the validator, with the reader matching on its own
|
|
297
|
+
* copy of the old text, silently collapses that choice — the hint keeps being sent and stops being
|
|
298
|
+
* the right one. Both sides now build and detect from here, so a wording change is one edit.
|
|
299
|
+
*
|
|
300
|
+
* Anything a gate wants to ADD — which file it searched, what a neighbour is evidence of — goes
|
|
301
|
+
* OUTSIDE these markers, so the sentence can grow without moving the contract.
|
|
302
|
+
*/
|
|
303
|
+
export const CONNOTATION_UNMATCHED_MARK = "[unmatched; nearest recorded:";
|
|
304
|
+
export const CONNOTATION_NO_RESEMBLANCE_MARK = "[no recorded query resembles this one]";
|
|
305
|
+
|
|
306
|
+
export function prRiskPopulation(ledgerRaw) {
|
|
307
|
+
let parsed;
|
|
308
|
+
try { parsed = JSON.parse(ledgerRaw); } catch { return { rows: 0, distinct: 0, repeated: 0 }; }
|
|
309
|
+
const batches = Array.isArray(parsed) ? parsed : [parsed];
|
|
310
|
+
let rows = 0;
|
|
311
|
+
const seen = new Set();
|
|
312
|
+
for (const b of batches) {
|
|
313
|
+
const pr = b?.extras?.pr_risk;
|
|
314
|
+
if (!Array.isArray(pr)) continue;
|
|
315
|
+
for (const e of pr) {
|
|
316
|
+
if (!e || typeof e.query !== "string" || !e.query.trim()) continue;
|
|
317
|
+
rows += 1;
|
|
318
|
+
seen.add(e.query.trim());
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return { rows, distinct: seen.size, repeated: rows - seen.size };
|
|
322
|
+
}
|
|
323
|
+
|
|
279
324
|
export function parsePrRiskResults(ledgerRaw) {
|
|
280
325
|
let parsed;
|
|
281
326
|
try { parsed = JSON.parse(ledgerRaw); } catch { return []; }
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"the baseline tracks named elements, and a rename is a new name. Regenerate rather than work around it.",
|
|
12
12
|
"Measured 139 of 287 declared elements."
|
|
13
13
|
],
|
|
14
|
-
"total":
|
|
14
|
+
"total": 105,
|
|
15
15
|
"byStage": {
|
|
16
16
|
"matter-frame": [
|
|
17
17
|
"### Intake asks line shape — rendered by the driver from intake_asks[]",
|
|
@@ -94,8 +94,6 @@
|
|
|
94
94
|
"register-digest": [
|
|
95
95
|
"## 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",
|
|
96
96
|
"Audit trail — per-unit search/detail-fetch counts, per-jurisdiction `_query` attribution",
|
|
97
|
-
"INSTRUCTED CHECKS — the answer to each requester ask the register owns",
|
|
98
|
-
"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)",
|
|
99
97
|
"return payload — the absolute output path plus a 2-3 line summary (counts + the path of the file written)",
|
|
100
98
|
"rolled-up coverage judgment — `sufficient: <true|false>`",
|
|
101
99
|
"source attribution — the register name tagged on each record, plus the EUIPO `environment` word",
|