clearotron 0.4.0-beta.0 → 0.4.0-beta.2
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 +9 -0
- package/INSTALL.md +4 -31
- package/README.md +5 -5
- package/bin/brandowner.mjs +1 -1
- package/bin/onboard.mjs +18 -74
- package/bin/start.mjs +58 -14
- package/bin/update.mjs +10 -2
- package/build-info.json +2 -2
- package/demo/full-country-search/run/_driver/search-policy.json +1 -1
- package/demo/full-country-search/run/status.json +1 -1
- package/demo/global-preliminary-search/run/_driver/search-policy.json +1 -1
- package/demo/global-preliminary-search/run/status.json +1 -1
- package/demo/knockout-search/run/_driver/search-policy.json +1 -1
- package/demo/knockout-search/run/status.json +1 -1
- package/demo/multi-country-focus-search/run/_driver/search-policy.json +1 -1
- package/demo/multi-country-focus-search/run/status.json +1 -1
- package/docs/README.md +1 -0
- package/docs/SECURITY-OWASP.md +298 -0
- package/docs/SECURITY.md +18 -13
- package/docs/architecture/02-architecture.md +5 -5
- package/docs/architecture/03-run-lifecycle.md +28 -29
- package/docs/architecture/04-configuration-reference.md +5 -10
- package/docs/architecture/05-config-governance.md +5 -7
- package/docs/architecture/06-operations-runbook.md +0 -12
- package/docs/architecture/07-quality-and-audit.md +12 -13
- package/docs/architecture/09-security-and-data.md +9 -4
- package/driver/CHANGELOG.md +70 -0
- package/driver/ask-ledger.mjs +4 -2
- package/driver/authority-trees.mjs +90 -8
- package/driver/band-shape.mjs +13 -13
- package/driver/blind-frame-model.mjs +1 -1
- package/driver/case-law-ledger.mjs +19 -3
- package/driver/claim-liveness.mjs +30 -1
- package/driver/clearance-variants-record.mjs +10 -3
- package/driver/common-law-receipts.mjs +64 -14
- package/driver/commonlaw-carry.mjs +2 -2
- package/driver/compare.mjs +2 -2
- package/driver/connotation-search.mjs +33 -104
- package/driver/contract-arm2-baseline.json +6 -5
- package/driver/contract-e3-backlog.mjs +62 -62
- package/driver/contract-vocabulary.mjs +38 -31
- package/driver/coverage-form-io.mjs +13 -1
- package/driver/coverage-form.mjs +42 -4
- package/driver/coverage-ledger.mjs +3 -5
- package/driver/cross-check-wait.mjs +43 -0
- package/driver/declination-call.mjs +34 -10
- package/driver/declination-tool.mjs +27 -15
- package/driver/deferral-row.mjs +139 -0
- package/driver/degraded-parts.mjs +377 -0
- package/driver/deliver-trigger.sh +1 -1
- package/driver/demo-container.mjs +4 -1
- package/driver/dev-portal.mjs +5 -5
- package/driver/dispatch-record.mjs +2 -2
- package/driver/driver.config.mjs +193 -96
- package/driver/e2e/README.md +1 -1
- package/driver/effective-scope.mjs +58 -2
- package/driver/engine/anthropic-agent.mjs +103 -27
- package/driver/engine/auth.mjs +2 -2
- package/driver/engine/child-record.mjs +11 -0
- package/driver/engine/cli-version.mjs +4 -3
- package/driver/engine/common.mjs +17 -2
- package/driver/engine/deny-authority-write.mjs +3 -1
- package/driver/engine/engine-env.mjs +174 -0
- package/driver/engine/engine-spawn.mjs +179 -0
- package/driver/engine/mcp/band-server.mjs +1 -1
- package/driver/engine/mcp/clarivate-server.mjs +2 -1
- package/driver/engine/mcp/codex-config.mjs +112 -3
- package/driver/engine/mcp/corsearch-server.mjs +1 -0
- package/driver/engine/mcp/declination-server.mjs +9 -8
- package/driver/engine/mcp/dispositions-server.mjs +2 -1
- package/driver/engine/mcp/euipo-server.mjs +1 -0
- package/driver/engine/mcp/fetch-server.mjs +14 -11
- package/driver/engine/mcp/free-tier-server.mjs +1 -1
- package/driver/engine/mcp/gather-config.mjs +23 -11
- package/driver/engine/mcp/perplexity-server.mjs +97 -17
- package/driver/engine/mcp/probe-server.mjs +43 -21
- package/driver/engine/mcp/public-fetch.mjs +195 -0
- package/driver/engine/mcp/recording-server.mjs +79 -14
- package/driver/engine/mcp/signa-server.mjs +1 -0
- package/driver/engine/mcp/supplemental.mjs +14 -14
- package/driver/engine/mcp/unit-note-server.mjs +50 -1
- package/driver/engine/mcp/uspto-local-server.mjs +1 -0
- package/driver/engine/openai-agent.mjs +109 -28
- package/driver/engine/probe.mjs +152 -19
- package/driver/enqueue-schema.mjs +5 -5
- package/driver/feedback-issues.mjs +1 -1
- package/driver/feedback-store.mjs +1 -1
- package/driver/findings-model.mjs +67 -16
- package/driver/flag-snapshot.mjs +1 -1
- package/driver/floor-duty.mjs +23 -7
- package/driver/form-neighbourhood.mjs +52 -17
- package/driver/frame-diff-model.mjs +3 -3
- package/driver/framework-method.mjs +304 -0
- package/driver/gateway.mjs +49 -21
- package/driver/hand-off-exits.mjs +129 -0
- package/driver/jx-lanes.mjs +1 -1
- package/driver/jx.mjs +8 -5
- package/driver/knockout-assess-record.mjs +7 -1
- package/driver/knockout-frame-record.mjs +96 -34
- package/driver/knockout-review-record.mjs +7 -2
- package/driver/log.mjs +2 -2
- package/driver/matter-frame-record.mjs +31 -2
- package/driver/methodology-witness.mjs +6 -3
- package/driver/named-band.mjs +1 -1
- package/driver/owner-use-check.mjs +45 -10
- package/driver/package.json +1 -1
- package/driver/partial-payload-baseline.json +4 -0
- package/driver/phase0.mjs +2 -2
- package/driver/pipeline-knockout.mjs +256 -129
- package/driver/pipeline.mjs +417 -614
- package/driver/placement-carry.mjs +22 -3
- package/driver/placement-form-io.mjs +18 -4
- package/driver/placement-form.mjs +53 -4
- package/driver/placement-union.mjs +32 -5
- package/driver/portal-report.mjs +3 -3
- package/driver/portal-service.mjs +6 -5
- package/driver/portal-static.mjs +11 -18
- package/driver/predelivery-lint.mjs +10 -10
- package/driver/profile-page.html +5 -5
- package/driver/profiles.mjs +5 -5
- package/driver/progress.mjs +6 -4
- package/driver/provider-usage.mjs +25 -2
- package/driver/publish/index.mjs +97 -25
- package/driver/publish/knockout.mjs +136 -14
- package/driver/publish/profiles-page.mjs +2 -0
- package/driver/publish/publish-inputs.mjs +10 -4
- package/driver/publish/render-knockout.mjs +48 -21
- package/driver/publish/render.mjs +13 -8
- package/driver/publish/report-data.mjs +2 -0
- package/driver/publish/search-depth.mjs +77 -4
- package/driver/publish/templates/report.css +1 -1
- package/driver/publish/xlsx.mjs +34 -15
- package/driver/reasoning-tripwires.mjs +2 -160
- package/driver/recall-receipt.mjs +27 -0
- package/driver/recall-reconciliation.mjs +1 -1
- package/driver/record-carry.mjs +14 -14
- package/driver/recording-agreement.mjs +2 -2
- package/driver/reference-score.mjs +169 -47
- package/driver/register-count.mjs +61 -4
- package/driver/register-digest-record.mjs +14 -1
- package/driver/register-plan.mjs +161 -80
- package/driver/register-records.mjs +37 -4
- package/driver/registration-scripts.mjs +22 -0
- package/driver/registry-fidelity.mjs +4 -4
- package/driver/repair-composers.mjs +23 -7
- package/driver/repair-contract.mjs +1 -1
- package/driver/repairs.mjs +5 -2
- package/driver/reviewer-open-points.mjs +33 -0
- package/driver/rule-shape.mjs +8 -8
- package/driver/run-economics.mjs +3 -3
- package/driver/run-integrity.mjs +2 -2
- package/driver/runner.mjs +17 -5
- package/driver/scope-facts.mjs +89 -12
- package/driver/score-redaction.mjs +247 -0
- package/driver/screen-gate.mjs +1 -1
- package/driver/search-policy.mjs +17 -0
- package/driver/skills/README.md +2 -2
- package/driver/skills/blind-frame/SKILL.md +2 -2
- package/driver/skills/clearance-common-law/SKILL.md +29 -36
- package/driver/skills/clearance-common-law/perplexity-prompts.md +8 -30
- package/driver/skills/clearance-register/SKILL.md +15 -16
- package/driver/skills/clearance-register/digest.md +15 -31
- package/driver/skills/clearance-register/register-recipes.md +14 -56
- package/driver/skills/clearance-register/unit.md +37 -48
- package/driver/skills/clearance-search/SKILL.md +6 -8
- package/driver/skills/clearance-search/delivery-contract.md +0 -7
- package/driver/skills/clearance-search/firm-wide-reasoning.md +5 -4
- package/driver/skills/clearance-search/phase2-execution.md +5 -6
- package/driver/skills/clearance-search/report-prose.md +5 -5
- package/driver/skills/clearance-search/risk-framework-triage.md +2 -2
- package/driver/skills/clearance-search/synthesis-rules.md +9 -9
- package/driver/skills/clearance-search/template-formatting.md +2 -2
- package/driver/skills/clearance-variants/SKILL.md +8 -8
- package/driver/skills/clearance-variants/transliteration-scripts.md +2 -2
- package/driver/skills/frame-diff/SKILL.md +2 -2
- package/driver/skills/knockout-assess/SKILL.md +40 -10
- package/driver/skills/knockout-frame/SKILL.md +42 -10
- package/driver/skills/matter-frame/SKILL.md +12 -5
- package/driver/skills/matter-frame/watchlist-reference.md +1 -1
- package/driver/skills/narrative-refutation/SKILL.md +5 -3
- package/driver/skills/placement-inquiry/SKILL.md +5 -1
- package/driver/stage-context.mjs +27 -6
- package/driver/stages-knockout.mjs +92 -79
- package/driver/stages.mjs +111 -68
- package/driver/stray-artifacts.mjs +9 -5
- package/driver/suite-census.json +696 -150
- package/driver/synthesis-record.mjs +26 -4
- package/driver/systemd/README.md +5 -6
- package/driver/systemd/clearotron-client-mcp.service +24 -0
- package/driver/systemd/clearotron-mcp-face.service +24 -0
- package/driver/systemd/clearotron-portal.service +24 -0
- package/driver/systemd/clearotron-worker.service +29 -0
- package/driver/tokens.mjs +10 -9
- package/driver/turnaround-bands.mjs +1 -1
- package/driver/unit-inventory.mjs +24 -43
- package/driver/variant-manifest-model.mjs +22 -3
- package/driver/verify-knockout.mjs +163 -4
- package/driver/verify.mjs +42 -16
- package/driver/web-grid.mjs +150 -0
- package/driver/whatif-memo-run.mjs +1 -1
- package/driver/withheld-families.mjs +113 -4
- package/driver/worker-heartbeat.mjs +13 -2
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/README.md +1 -1
- package/mcp-server/lib/knockout.mjs +1 -1
- package/mcp-server/lib/ops.mjs +10 -10
- package/mcp-server/lib/scrub.mjs +1 -1
- package/mcp-server/lib/trace.mjs +3 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +3 -3
- package/package.json +1 -1
- package/portal-ui/dist/assets/{index-DVtz44vH.js → index-BPAUjcI0.js} +3 -2
- package/portal-ui/dist/assets/{index-5CCwiJG7.css → index-D2wrw9cH.css} +18 -2
- package/portal-ui/dist/assets/plus-jakarta-sans-BUCHxqJ-.woff2 +0 -0
- package/portal-ui/dist/index.html +2 -15
- package/portal-ui/package.json +1 -1
- package/providers/_shared/README.md +1 -1
- package/providers/_shared/answer-memory.mjs +199 -0
- package/providers/_shared/enumerate.mjs +155 -50
- package/providers/_shared/execute-plan.mjs +62 -16
- package/providers/_shared/ledger-path.mjs +1 -1
- package/providers/_shared/ledger.mjs +48 -6
- package/providers/_shared/plan-guards.mjs +7 -0
- package/providers/_shared/script-form.mjs +24 -5
- package/providers/_shared/term-shape.mjs +5 -5
- package/providers/clarivate/src/capabilities.js +11 -0
- package/providers/clarivate/src/core.js +193 -18
- package/providers/corsearch/README.md +1 -2
- package/providers/corsearch/src/core.js +2 -2
- package/providers/jx-subclass/lookup.mjs +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/README.md +8 -7
- package/providers/oauth-mcp-bridge/bridge.mjs +21 -5
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/oauth-mcp-bridge/systemd/courtlistener-mcp.service +1 -1
- package/providers/oauth-mcp-bridge/warm-server.mjs +16 -5
- package/providers/perplexity/src/core.js +246 -21
- package/providers/signa/src/capabilities.js +24 -0
- package/providers/signa/src/core.js +93 -25
- package/scripts/README.md +2 -1
- package/scripts/added-reference-check.mjs +2 -1
- package/scripts/backup-recall-stores.mjs +5 -5
- package/scripts/demo-evidence.mjs +2 -1
- package/scripts/deprecate-below.mjs +114 -2
- package/scripts/drive-env-check.mjs +2 -1
- package/scripts/e2e-first-time.mjs +469 -0
- package/scripts/e2e-scenario-ops.mjs +441 -0
- package/scripts/e2e.mjs +104 -16
- package/scripts/env-audit.mjs +8 -0
- package/scripts/freeze-example-run.mjs +7 -7
- package/scripts/generated-files-are-current.mjs +2 -1
- package/scripts/hand-off-exits-probe.mjs +75 -0
- package/scripts/import-cycle-check.mjs +2 -1
- package/scripts/merge-shape-check.mjs +2 -1
- package/scripts/package-size-budget.mjs +2 -1
- package/scripts/record-carry-probe.mjs +1 -1
- package/scripts/release-entry-catch-up.mjs +77 -8
- package/scripts/release-install-check.mjs +4 -1
- package/scripts/release-note-required.mjs +139 -9
- package/scripts/release-rehearsal-version.mjs +60 -0
- package/scripts/release-sbom.mjs +104 -0
- package/scripts/release-visible-check.mjs +7 -5
- package/scripts/render-check.mjs +5 -2
- package/scripts/report-offline-render-check.mjs +169 -0
- package/scripts/score.mjs +79 -8
- package/scripts/strip-titles-and-attributions.mjs +2 -1
- package/scripts/strip-tracker-citations.mjs +2 -1
- package/scripts/test-full.mjs +2 -1
- package/scripts/test-run.mjs +10 -3
- package/scripts/third-party-notices.mjs +3 -1
- package/scripts/travelling-predicates.mjs +1 -1
- package/scripts/writing-standard-check.mjs +2 -1
- package/shared/brand-fonts.mjs +59 -0
- package/shared/brand.mjs +6 -6
- package/shared/browser-temp-root.mjs +3 -2
- package/shared/doctrine-overlay.mjs +1 -1
- package/shared/driver-dir.mjs +47 -11
- package/shared/fonts/OFL-fira-code.txt +93 -0
- package/shared/fonts/OFL-plus-jakarta-sans.txt +93 -0
- package/shared/fonts/README.md +37 -0
- package/shared/fonts/fira-code.woff2 +0 -0
- package/shared/fonts/plus-jakarta-sans.woff2 +0 -0
- package/shared/names-in-force.mjs +2 -0
- package/shared/npm-cli.mjs +23 -0
- package/shared/os-advice.mjs +14 -2
- package/shared/path-seps.mjs +42 -0
- package/shared/process-table.mjs +71 -3
- package/shared/reference-guard-classes.mjs +30 -6
- package/shared/root-doc-commands.mjs +6 -2
- package/shared/running-start.mjs +24 -7
- package/shared/scope.mjs +2 -2
- package/shared/wsl.mjs +1 -1
- package/driver/known-conflicts.mjs +0 -327
package/driver/driver.config.mjs
CHANGED
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
// Every value is env-overridable so the identical code runs from a developer's shell and from the
|
|
8
8
|
// systemd unit on a deployed host.
|
|
9
9
|
|
|
10
|
-
import { join, dirname, basename, isAbsolute,
|
|
10
|
+
import { join, dirname, basename, isAbsolute, win32 } from "node:path";
|
|
11
11
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
12
12
|
import { readdirSync, existsSync, accessSync, statSync, statfsSync, readFileSync, realpathSync, openSync, readSync, closeSync, constants as FS } from "node:fs";
|
|
13
13
|
import { homedir } from "node:os";
|
|
14
14
|
import { isWsl } from "../shared/wsl.mjs"; // — the one answer to "is this Linux under Windows", which decides the /mnt/<drive> skip
|
|
15
15
|
import { envFrom } from "../shared/env-aliases.mjs"; // — an operator-facing name is the one an operator sets, and it has to work where they set it; — envFrom is the resolver that reads every spelling of it
|
|
16
16
|
import { invoke } from "../shared/invocation.mjs"; // — name a command the reader can actually type
|
|
17
|
+
import { sepClass, notSepClass, hasSep } from "../shared/path-seps.mjs"; // a Windows path is built with "\"
|
|
17
18
|
import { envFileRead } from "../shared/env-local.mjs"; // — WHICH file to set it in, measured; null for a service that read none
|
|
18
19
|
import { numericSetting, resolveNumericSetting } from "./numeric-setting.mjs"; import { STUDIO_SEGMENTS, STUDIO_SEGMENT_RE, studioSegmentFor } from "../shared/pre-rename-spellings.mjs"; export { STUDIO_SEGMENTS, STUDIO_SEGMENT_RE, studioSegmentFor }; // the studio segment an install keeps its runs under // — a number, or a refusal that names the variable; never NaN
|
|
19
20
|
|
|
@@ -50,8 +51,9 @@ const home = () => homedir();
|
|
|
50
51
|
// CLEAROTRON_SKILLS_STORE_STRICT (skills-store-provenance.mjs). There was a fourth, CLEAROTRON_SELFTEST_PATHS_ONLY,
|
|
51
52
|
// and it died with driver/selftest.mjs. The first two are here because they LIED, the third because it was written
|
|
52
53
|
// after this note and a new gate has no excuse to spell itself a fifth way. Roughly
|
|
53
|
-
//
|
|
54
|
-
//
|
|
54
|
+
// FOUR gates are still inline `!== "0"`: CLEAROTRON_BAND_TRUTH_GATE, CLEAROTRON_REGISTER_GAP_CLAMP,
|
|
55
|
+
// CLEAROTRON_WARM_RETRY, CLEAROTRON_SATPROBE_CODESIDE. (CLEAROTRON_RECALL_TRIPWIRE was a fifth, and went
|
|
56
|
+
// with the recall store on 2026-09-24.) Those are CORRECT — `0`
|
|
55
57
|
// disables each, which is what they document — they simply do not accept `off`/`false`/`no`.
|
|
56
58
|
//
|
|
57
59
|
// IT WAS TEN. Three went with their knobs: CLEAROTRON_TAINT_CHAIN, CLEAROTRON_XCHECK and
|
|
@@ -60,9 +62,10 @@ const home = () => homedir();
|
|
|
60
62
|
// rollback. The ones that remain stay because the test corpus sets them, and two of them are how a
|
|
61
63
|
// hermetic suite keeps from dialling a real provider.
|
|
62
64
|
//
|
|
63
|
-
// CLEAROTRON_RECALL_PROBES was nearly a fourth and is the cautionary
|
|
64
|
-
// `||=`, and the census that cleared it for deletion looked for a
|
|
65
|
-
// it armed the probes in all five. A measurement that cannot see a form
|
|
65
|
+
// CLEAROTRON_RECALL_PROBES (also gone with the recall store) was nearly a fourth and is the cautionary
|
|
66
|
+
// one: five test files disabled it with `||=`, and the census that cleared it for deletion looked for a
|
|
67
|
+
// plain `=` after the name. Deleting it armed the probes in all five. A measurement that cannot see a form
|
|
68
|
+
// reports clean.
|
|
66
69
|
//
|
|
67
70
|
// Anyone extending the accessors to the rest must update the note in .env.example's conventions header
|
|
68
71
|
// in the same change — a header that claims more coverage than the code has is the defect this whole
|
|
@@ -304,8 +307,8 @@ export const config = {
|
|
|
304
307
|
|
|
305
308
|
// ── Per-agent paths ───────────────────────────────────────────────────────
|
|
306
309
|
// A clearotron job is enqueued into the FORWARDING agent's OWN workspace queue (that agent's `write` tool
|
|
307
|
-
// is sandboxed there), and we run the whole pipeline as that same agent so the reply tool
|
|
308
|
-
//
|
|
310
|
+
// is sandboxed there), and we run the whole pipeline as that same agent so the reply tool,
|
|
311
|
+
// the run-dir, and the agent's memory all line up. The queue LOCATION therefore
|
|
309
312
|
// encodes the agent identity — the driver derives {agentId, studioRoot} from where it claimed the job,
|
|
310
313
|
// so there is no forwarder→agent map to keep in sync. Delivery is still centralized (poolRoot below).
|
|
311
314
|
// The per-agent directory prefix under workspaceRoot. Inherited as a bare `workspace-` layout; now ONE
|
|
@@ -339,9 +342,10 @@ export const config = {
|
|
|
339
342
|
return join(this.studioRootForAgent(agentId), "archive");
|
|
340
343
|
},
|
|
341
344
|
// …/<prefix><id>/studio/<segment>/queue → "<id>"; null if the path isn't an agent queue dir. Either
|
|
342
|
-
// spelling of the segment, because an install keeps the one it has.
|
|
343
|
-
agentIdFromQueueDir(qdir) {
|
|
344
|
-
const
|
|
345
|
+
// spelling of the segment, because an install keeps the one it has, and either separator on Windows.
|
|
346
|
+
agentIdFromQueueDir(qdir, { platform = process.platform } = {}) {
|
|
347
|
+
const [S, N] = [sepClass(platform), notSepClass(platform)];
|
|
348
|
+
const m = new RegExp(`(?:^|${S})${this.workspacePrefixRe}(${N}+)${S}studio${S}${STUDIO_SEGMENT_RE}${S}queue${S}?$`).exec(qdir);
|
|
345
349
|
return m ? m[1] : null;
|
|
346
350
|
},
|
|
347
351
|
// Every agent workspace's clearotron queue. The systemd `.path` watches these and the runner drains ALL of
|
|
@@ -369,38 +373,45 @@ export const config = {
|
|
|
369
373
|
}
|
|
370
374
|
}
|
|
371
375
|
} catch { /* workspaceRoot may not exist in some test envs — fall through to the canonical queue */ }
|
|
372
|
-
// THE
|
|
376
|
+
// THE INSTALL'S OWN QUEUE IS ALWAYS WATCHED, AND IT USED TO BE ONE PARTICULAR AGENT'S. `this.queueDir`
|
|
377
|
+
// was `queueDirForAgent("<the old default>")` — a LITERAL, not `defaultAgent` — so every deployment,
|
|
378
|
+
// however configured, watched that one directory as well as its own. It was back-compat for a real
|
|
379
|
+
// legacy queue, and its own note said removing it was a SEQUENCED MIGRATION rather than an edit:
|
|
380
|
+
// drain that queue, or name it explicitly with CLEAROTRON_QUEUE_DIR, and only then drop the literal.
|
|
373
381
|
//
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
//
|
|
382
|
+
// BOTH PRECONDITIONS WERE MEASURED BEFORE IT WENT (2026-09-27). Every install that runs a clearance
|
|
383
|
+
// unit sets CLEAROTRON_QUEUE_DIR, and holds nothing in that legacy directory. One retired deployment
|
|
384
|
+
// still has 23 files there; classified against this product's own live-marker rule, NONE is live —
|
|
385
|
+
// ten results, ten done markers and three hand-made probe files, newest a month old — that deployment
|
|
386
|
+
// runs no clearance unit, and the path resolved per-install anyway, so no other deployment could ever
|
|
387
|
+
// have reached it.
|
|
379
388
|
//
|
|
380
|
-
//
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
// legacy queue, or name it explicitly via CLEAROTRON_QUEUE_DIR, then drop this), not an edit: it needs
|
|
384
|
-
// the box, and no repository-only change can do it safely.
|
|
385
|
-
//
|
|
386
|
-
// Written down because a sweep of this platform name will find it again and read it as residue.
|
|
387
|
-
// It is not residue; it is the one instance whose removal has a cost outside this repository.
|
|
389
|
+
// What remains is the honest version of what the literal was for: the queue of the agent this
|
|
390
|
+
// install actually runs as, appended so a deployment whose own queue is not among the scanned
|
|
391
|
+
// workspaces still drains it.
|
|
388
392
|
const canonical = this.queueDir;
|
|
389
393
|
if (!dirs.includes(canonical)) dirs.push(canonical);
|
|
390
394
|
return [...new Set(dirs)]; // CLEAROTRON_QUEUE_DIR may equal a scanned workspace queue
|
|
391
395
|
},
|
|
392
396
|
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
// agent
|
|
397
|
+
// Defaults for a caller with no derived agent (selftest, docs, a CLI invocation): the agent this
|
|
398
|
+
// install runs as, which is what CLEAROTRON_DEFAULT_AGENT says and `localagent` when it says nothing.
|
|
399
|
+
// They named one particular agent as a literal until 2026-09-27; see `queueDirs` above for what that
|
|
400
|
+
// was for and what was measured before it went. Per-run code uses the *ForAgent helpers with the agent
|
|
401
|
+
// derived from the claimed queue dir, and is unaffected either way.
|
|
402
|
+
//
|
|
403
|
+
// THIS MOVES NO PATH ON A DEPLOYMENT THAT SETS ITS OWN AGENT, which is the point: the two installs
|
|
404
|
+
// whose default agent is the id this replaced resolve to exactly the directories they resolved to
|
|
405
|
+
// before, because that is what their own configuration says. An install that sets nothing moves to its
|
|
406
|
+
// own default, where its runs already are.
|
|
396
407
|
get studioRoot() {
|
|
397
|
-
return this.studioRootForAgent(
|
|
408
|
+
return this.studioRootForAgent(this.defaultAgent);
|
|
398
409
|
},
|
|
399
410
|
get queueDir() {
|
|
400
|
-
return this.queueDirForAgent(
|
|
411
|
+
return this.queueDirForAgent(this.defaultAgent);
|
|
401
412
|
},
|
|
402
413
|
get archiveRoot() {
|
|
403
|
-
return this.archiveRootForAgent(
|
|
414
|
+
return this.archiveRootForAgent(this.defaultAgent);
|
|
404
415
|
},
|
|
405
416
|
|
|
406
417
|
// Delivery pool: the deterministic publish step writes report.html + audit.xlsx here; Caddy serves it
|
|
@@ -617,20 +628,20 @@ export const config = {
|
|
|
617
628
|
//
|
|
618
629
|
// THE DEFAULT IS PART OF A PATH, so changing it moves where an install looks for its own runs:
|
|
619
630
|
// every run dir is `<workspaceRoot>/workspace-<agent>/studio/clearance-search/…`. An install created
|
|
620
|
-
// before this default changed keeps its runs under the
|
|
621
|
-
// because the gather servers
|
|
622
|
-
//
|
|
623
|
-
//
|
|
624
|
-
// CLEAROTRON_GATHER_AGENT=clawdi
|
|
631
|
+
// before this default changed keeps its runs under the id it was created with, and must set both
|
|
632
|
+
// CLEAROTRON_DEFAULT_AGENT and CLEAROTRON_GATHER_AGENT to that id — both, because the gather servers
|
|
633
|
+
// read their own variable. Unpinned, such an install starts a fresh empty workspace and its existing
|
|
634
|
+
// runs read as absent rather than as an error.
|
|
625
635
|
//
|
|
626
|
-
//
|
|
627
|
-
//
|
|
636
|
+
// THE ID IS NO LONGER WRITTEN DOWN ANYWHERE FOR THEM (ruling 563, 2026-09-27): what this tree knows is
|
|
637
|
+
// that the id is a path segment, not which id any particular install was created with. An operator who
|
|
638
|
+
// needs it reads the name of the workspace directory their runs are in. The release note for that
|
|
639
|
+
// ruling carries the instruction; this comment carries the reason.
|
|
628
640
|
//
|
|
629
|
-
// THE ID IS ALSO A KEY, not only a path segment. `AGENT_WHATSAPP` in stages.mjs is keyed by agent
|
|
630
|
-
//
|
|
631
|
-
//
|
|
632
|
-
//
|
|
633
|
-
// cite line numbers in stages.mjs, so a comment added there repoints all of them.)
|
|
641
|
+
// THE ID IS ALSO A KEY, not only a path segment. `AGENT_WHATSAPP` in stages.mjs is keyed by agent id,
|
|
642
|
+
// so a deployment whose agent is not in that roster resolves no operator number and the hole reads as
|
|
643
|
+
// "no copy configured" rather than as a miss. (The roster stays one line: 32 backlog rows cite line
|
|
644
|
+
// numbers in stages.mjs, so a comment added there repoints all of them.)
|
|
634
645
|
get defaultAgent() { return process.env.CLEAROTRON_DEFAULT_AGENT || "localagent"; },
|
|
635
646
|
|
|
636
647
|
// Per-stage retry budget (fresh session key per retry).
|
|
@@ -668,13 +679,12 @@ export const config = {
|
|
|
668
679
|
// Owner's ruling, 2026-09-23: record the tier. What a run asked for is a tier, what it was served is
|
|
669
680
|
// recorded separately and already is, and the report names the model that ran — none of that moves.
|
|
670
681
|
//
|
|
671
|
-
// WHAT IT COSTS, RULED ON AND ACCEPTED RATHER THAN DISCOVERED LATER.
|
|
672
|
-
//
|
|
673
|
-
//
|
|
674
|
-
//
|
|
675
|
-
//
|
|
676
|
-
//
|
|
677
|
-
// fix it properly, which is larger than this and not what was ruled.
|
|
682
|
+
// WHAT IT COSTS, RULED ON AND ACCEPTED RATHER THAN DISCOVERED LATER. A stage's rows are keyed on the
|
|
683
|
+
// tier it asked for, and a native-language lane's rows on the model that served them (`modelKey` in
|
|
684
|
+
// tokens.mjs). So one model reached both ways lands in two buckets: `anthropic/claude-haiku` from a
|
|
685
|
+
// stage, `anthropic/claude-haiku-4-5` from a native-language lane. That is a real split in a per-model
|
|
686
|
+
// total and it was accepted with the ruling; keying every row on what served it is the change that
|
|
687
|
+
// would close it, which is larger than this and not what was ruled.
|
|
678
688
|
export const MODELS = {
|
|
679
689
|
haiku: "anthropic/claude-haiku",
|
|
680
690
|
sonnet: "anthropic/claude-sonnet",
|
|
@@ -1319,7 +1329,15 @@ export const PROVIDERS = {
|
|
|
1319
1329
|
// The office's own numbers come off the full record Signa returns on search, through the
|
|
1320
1330
|
// provider's own normaliser, so publish can address the office's page for each filing
|
|
1321
1331
|
// (publish/office-record-links.mjs) instead of showing the handle.
|
|
1322
|
-
|
|
1332
|
+
//
|
|
1333
|
+
// THE TOTAL RIDES THE SAME ANSWER and is carried, not dropped: `include_total` puts the register's
|
|
1334
|
+
// own count for this exact question on every search, so the listing of a term already holds the
|
|
1335
|
+
// figure the count lane would ask for separately. An approximation stays one: no number in
|
|
1336
|
+
// `total`, the floor beside it — the count lane's rule, unchanged.
|
|
1337
|
+
return { ok: true,
|
|
1338
|
+
total: Number.isFinite(p.total_hits) ? p.total_hits : null,
|
|
1339
|
+
...(p.total_approximate === true ? { approximate: true, floor: Number.isFinite(p.total_floor) ? p.total_floor : null } : {}),
|
|
1340
|
+
records: (Array.isArray(p.results) ? p.results : []).map((row) => {
|
|
1323
1341
|
const rec = row?.raw && typeof row.raw === "object" ? core.normalizeRecord(row.raw, row.office || null) : null;
|
|
1324
1342
|
return {
|
|
1325
1343
|
record_id: row?.record_id ?? null,
|
|
@@ -1660,12 +1678,16 @@ export function activeProvider(env = null) {
|
|
|
1660
1678
|
|
|
1661
1679
|
|
|
1662
1680
|
// ── Research providers (Phase 2a, knockout lane) — the same lazy-import + cred-guard idiom as
|
|
1663
|
-
// PROVIDERS, for CODE-side research calls (no model in the data path). The knockout
|
|
1664
|
-
//
|
|
1665
|
-
//
|
|
1666
|
-
//
|
|
1667
|
-
// CLEAROTRON_KNOCKOUT_SWEEP_FIXTURES — dir of
|
|
1681
|
+
// PROVIDERS, for CODE-side research calls (no model in the data path). The knockout's live executors:
|
|
1682
|
+
// `grid` runs one mark's web search as a dictated grid, `research` asks one question (the owner lookups).
|
|
1683
|
+
// Tests inject their own, the dev instance uses CLEAROTRON_KNOCKOUT_SWEEP_FIXTURES. Knobs:
|
|
1684
|
+
// PERPLEXITY_API_KEY — required for live calls (absent ⇒ the web half is skipped, loud)
|
|
1685
|
+
// CLEAROTRON_KNOCKOUT_SWEEP_FIXTURES — dir of canned answers ($0 dev/e2e): <mark-kebab>.grid.json, the
|
|
1686
|
+
// grid program's printed ledger for a mark, and <mark-kebab>.md,
|
|
1687
|
+
// the answer to a question asked about that mark
|
|
1668
1688
|
// CLEAROTRON_KNOCKOUT_MODEL — LLM override for the frame/assess turns (default opus)
|
|
1689
|
+
// The preset and the reasoning setting are not knobs: they are pinned in stages-knockout.mjs, so no
|
|
1690
|
+
// environment can quietly move them.
|
|
1669
1691
|
export const RESEARCH_PROVIDERS = {
|
|
1670
1692
|
perplexity: {
|
|
1671
1693
|
// The vendor's own name, for the staff config page's provider inventory (config-inventory.mjs).
|
|
@@ -1689,8 +1711,11 @@ export const RESEARCH_PROVIDERS = {
|
|
|
1689
1711
|
const body = core.buildRequestBody({ task, preset });
|
|
1690
1712
|
const data = await core.callAgentAPI(process.env.PERPLEXITY_API_KEY, body, { retries: core.retriesForPreset(preset) });
|
|
1691
1713
|
const text = core.formatResponse(data, preset);
|
|
1692
|
-
|
|
1693
|
-
|
|
1714
|
+
// The searches behind the answer, kept for the caller to write into the run: the answer alone
|
|
1715
|
+
// cannot say which pages were retrieved.
|
|
1716
|
+
const raw = core.rawResultItems(data);
|
|
1717
|
+
if (!text || !String(text).trim()) return { ok: false, cause: "empty research response", raw };
|
|
1718
|
+
return { ok: true, text: String(text), bytes: Buffer.byteLength(String(text)), tookMs: Date.now() - started, raw };
|
|
1694
1719
|
} catch (e) {
|
|
1695
1720
|
// OUTAGE-SHAPED OR NOT — the caller cannot tell from prose, so say it here.
|
|
1696
1721
|
//
|
|
@@ -1716,6 +1741,38 @@ export const RESEARCH_PROVIDERS = {
|
|
|
1716
1741
|
};
|
|
1717
1742
|
}
|
|
1718
1743
|
},
|
|
1744
|
+
/**
|
|
1745
|
+
* ONE DICTATED GRID, THROUGH THE CLEARANCE GRID'S OWN FUNCTIONS: the program task built from the spec,
|
|
1746
|
+
* the sandbox call, and the capture that reconciles what the program printed against every cell the
|
|
1747
|
+
* spec asked for. Nothing is summarised; the ledger is what came back.
|
|
1748
|
+
*
|
|
1749
|
+
* The outage rule is `research`'s, for the same reason: 429 and 5xx park, anything else is loud.
|
|
1750
|
+
*/
|
|
1751
|
+
async grid(spec, { preset = "pro-search", reasoning = null } = {}) {
|
|
1752
|
+
if (!process.env.PERPLEXITY_API_KEY) return { ok: false, cause: "PERPLEXITY_API_KEY absent from driver env" };
|
|
1753
|
+
let core;
|
|
1754
|
+
try { core = await import("../providers/perplexity/src/core.js"); }
|
|
1755
|
+
catch (e) { return { ok: false, cause: `perplexity core unavailable: ${e.message}` }; }
|
|
1756
|
+
const started = Date.now();
|
|
1757
|
+
try {
|
|
1758
|
+
const body = core.buildRequestBody({ task: core.buildGridProgramTask(spec), preset, enableSandbox: true, reasoning });
|
|
1759
|
+
const data = await core.callAgentAPI(process.env.PERPLEXITY_API_KEY, body, { retries: core.retriesForPreset(preset) });
|
|
1760
|
+
const raw = core.rawResultItems(data);
|
|
1761
|
+
const cap = core.captureGridFromResponse(data, spec);
|
|
1762
|
+
if (!cap.ok) return { ok: false, cause: `grid: ${String(cap.error ?? "capture failed").slice(0, 200)}`, raw, tookMs: Date.now() - started };
|
|
1763
|
+
return { ok: true, ledgerJson: cap.ledgerJson, present: cap.present, requested: cap.requested, missing: cap.missing.length,
|
|
1764
|
+
bytes: Buffer.byteLength(cap.ledgerJson), tookMs: Date.now() - started, raw };
|
|
1765
|
+
} catch (e) {
|
|
1766
|
+
const status = Number.isInteger(e?.status) ? e.status : null;
|
|
1767
|
+
return {
|
|
1768
|
+
ok: false,
|
|
1769
|
+
cause: `grid call threw: ${String(e?.message ?? e).slice(0, 200)}`,
|
|
1770
|
+
status,
|
|
1771
|
+
outage: status === 429 || (status >= 500 && status <= 599),
|
|
1772
|
+
tookMs: Date.now() - started,
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
},
|
|
1719
1776
|
},
|
|
1720
1777
|
};
|
|
1721
1778
|
|
|
@@ -2024,9 +2081,10 @@ export const ENGINE_BINARIES = {
|
|
|
2024
2081
|
// npm, NOT the vendor's shell installer: a command this product executes on someone's box has to be
|
|
2025
2082
|
// one they can read in full before they answer, and a piped remote script is not.
|
|
2026
2083
|
// — the documented headless ending. `claude setup-token` walks the sign-in and
|
|
2027
|
-
// prints a long-lived token; the stage subprocess env is a
|
|
2028
|
-
//
|
|
2029
|
-
// untouched, and that
|
|
2084
|
+
// prints a long-lived token; the stage subprocess env is a list (engine/engine-env.mjs) that carries the
|
|
2085
|
+
// vendor's own `CLAUDE_*` names — the API key is the one name it keeps only under `api-key` — so a token
|
|
2086
|
+
// in the env file reaches the CLI untouched, and that is the whole mechanism (armed in
|
|
2087
|
+
// engine.anthropic.test.mjs). The wizard
|
|
2030
2088
|
// captures it by paste: the vendor's stream layout is not ours to guess at, and a paste works
|
|
2031
2089
|
// whatever it prints where.
|
|
2032
2090
|
headless: { cmd: "claude setup-token", tokenEnv: "CLAUDE_CODE_OAUTH_TOKEN" },
|
|
@@ -2171,7 +2229,17 @@ export function engineAdapterSpecifier(engine) {
|
|
|
2171
2229
|
/** A path on a Windows drive as WSL mounts it. */
|
|
2172
2230
|
export const ON_A_WINDOWS_DRIVE = /^\/mnt\/[a-z]\//i;
|
|
2173
2231
|
|
|
2174
|
-
|
|
2232
|
+
// ON WINDOWS THE EXECUTE BIT DOES NOT EXIST: `X_OK` there answers whether the file exists, so npm's
|
|
2233
|
+
// extensionless `claude` shim, a shell script Windows cannot start, passed this check and the spawn failed
|
|
2234
|
+
// with ENOENT (seen on a real Windows run, 2026-09-09). There a program is what Windows starts itself, an
|
|
2235
|
+
// `.exe` or `.com`, or a JavaScript file, which runs through the Node running now (engine/engine-spawn.mjs).
|
|
2236
|
+
const WINDOWS_PROGRAM = /\.(?:exe|com|[cm]?js)$/i;
|
|
2237
|
+
const isExecFile = (p, platform = process.platform) => {
|
|
2238
|
+
try {
|
|
2239
|
+
if (!statSync(p).isFile()) return false;
|
|
2240
|
+
return platform === "win32" ? WINDOWS_PROGRAM.test(p) : (accessSync(p, X_OK), true);
|
|
2241
|
+
} catch { return false; }
|
|
2242
|
+
};
|
|
2175
2243
|
const realOrNull = (p) => { try { return realpathSync(p); } catch { return null; } };
|
|
2176
2244
|
const readPackage = (dir) => { try { return JSON.parse(readFileSync(join(dir, "package.json"), "utf8")); } catch { return null; } };
|
|
2177
2245
|
|
|
@@ -2229,9 +2297,52 @@ function installedProgram(spec, root) {
|
|
|
2229
2297
|
return rel ? join(dir, rel) : null;
|
|
2230
2298
|
}
|
|
2231
2299
|
|
|
2300
|
+
/**
|
|
2301
|
+
* PATH as Windows spells it. Windows names are not case-sensitive, and Node copies the environment with
|
|
2302
|
+
* the case the system gave it, which is `Path`. A lookup of `PATH` alone reads nothing there.
|
|
2303
|
+
*/
|
|
2304
|
+
function pathOf(env, platform) {
|
|
2305
|
+
if (platform !== "win32") return env.PATH;
|
|
2306
|
+
const key = Object.keys(env).find((k) => k.toUpperCase() === "PATH");
|
|
2307
|
+
return key ? env[key] : undefined;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
/**
|
|
2311
|
+
* The program a PATH directory holds under `name` on Windows, or null.
|
|
2312
|
+
*
|
|
2313
|
+
* WINDOWS FINDS A PROGRAM BY ITS EXTENSION, in the order PATHEXT lists them, so `claude` is `claude.exe`
|
|
2314
|
+
* or `claude.cmd` and never the extensionless file. An `.exe` or `.com` is the program itself: the
|
|
2315
|
+
* vendor's own installer puts `claude.exe` on PATH. A `.cmd` or `.bat` is npm's shim for a global
|
|
2316
|
+
* install, and Node refuses to start a batch file without a shell. So the shim is read as what it stands
|
|
2317
|
+
* for: the vendor's package in `node_modules` beside it, and the program that package's `bin` names. A
|
|
2318
|
+
* name that already ends in one of those extensions, as a setting of `claude.exe` does, is looked for as
|
|
2319
|
+
* written, as Windows looks for it.
|
|
2320
|
+
*/
|
|
2321
|
+
function windowsProgramIn(dir, name, spec, env) {
|
|
2322
|
+
const key = Object.keys(env).find((k) => k.toUpperCase() === "PATHEXT");
|
|
2323
|
+
const exts = String((key && env[key]) || ".COM;.EXE;.BAT;.CMD").split(";").map((x) => x.trim().toLowerCase())
|
|
2324
|
+
.filter((x) => [".com", ".exe", ".bat", ".cmd"].includes(x));
|
|
2325
|
+
const own = /\.(?:com|exe|bat|cmd)$/i.exec(name)?.[0]?.toLowerCase();
|
|
2326
|
+
const tries = own ? [[name, own]] : exts.map((ext) => [`${name}${ext}`, ext]);
|
|
2327
|
+
for (const [file, ext] of tries) {
|
|
2328
|
+
const p = join(dir, file);
|
|
2329
|
+
let found = false;
|
|
2330
|
+
try { found = statSync(p).isFile(); } catch { /* not here */ }
|
|
2331
|
+
if (!found) continue;
|
|
2332
|
+
if (ext === ".exe" || ext === ".com") return p;
|
|
2333
|
+
return installedProgram(spec, dir);
|
|
2334
|
+
}
|
|
2335
|
+
return null;
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2232
2338
|
/** Can this candidate be spawned as the engine? `{ok: true, version}` or `{ok: false, why}`. */
|
|
2233
|
-
|
|
2234
|
-
|
|
2339
|
+
/** Why a candidate the lookup cannot start is refused, in the platform's own terms: Windows has no execute bit. */
|
|
2340
|
+
const notStartable = (platform) => platform === "win32"
|
|
2341
|
+
? "not a program Windows can start (it is missing, is a folder, or is not an .exe file)"
|
|
2342
|
+
: "not an executable file (it is missing, is a directory, or lacks the execute bit for this user)";
|
|
2343
|
+
|
|
2344
|
+
function engineCandidate(p, spec, platform = process.platform) {
|
|
2345
|
+
if (!isExecFile(p, platform)) return { ok: false, why: notStartable(platform) };
|
|
2235
2346
|
const own = owningPackage(p);
|
|
2236
2347
|
const vendor = Boolean(spec.package) && own?.name === spec.package;
|
|
2237
2348
|
if (vendor && !runsDirectly(p)) {
|
|
@@ -2258,7 +2369,8 @@ function engineCandidate(p, spec) {
|
|
|
2258
2369
|
* way, because the services run as user units under the same home. `wsl` and `onWindowsDrive` are
|
|
2259
2370
|
* injectable for the reason shared/wsl.mjs gives.
|
|
2260
2371
|
*/
|
|
2261
|
-
export function resolveEngineProgram(engine, { env = process.env, enginesDir = undefined, wsl = null, onWindowsDrive = null
|
|
2372
|
+
export function resolveEngineProgram(engine, { env = process.env, enginesDir = undefined, wsl = null, onWindowsDrive = null,
|
|
2373
|
+
platform = process.platform } = {}) {
|
|
2262
2374
|
const id = String(engine ?? "").trim().toLowerCase();
|
|
2263
2375
|
const spec = ENGINE_BINARIES[id];
|
|
2264
2376
|
const out = { engine: id, binEnv: spec?.env ?? null, bin: null, explicit: false, relative: false,
|
|
@@ -2270,14 +2382,14 @@ export function resolveEngineProgram(engine, { env = process.env, enginesDir = u
|
|
|
2270
2382
|
const underWsl = wsl ?? isWsl({ env });
|
|
2271
2383
|
const onDrive = onWindowsDrive ?? ((p) => ON_A_WINDOWS_DRIVE.test(p));
|
|
2272
2384
|
const take = (p, source) => {
|
|
2273
|
-
const c = engineCandidate(p, spec);
|
|
2385
|
+
const c = engineCandidate(p, spec, platform);
|
|
2274
2386
|
if (!c.ok) { out.rejected.push({ path: p, why: c.why, source }); return false; }
|
|
2275
2387
|
Object.assign(out, { resolved: p, source, version: c.version });
|
|
2276
2388
|
return true;
|
|
2277
2389
|
};
|
|
2278
2390
|
|
|
2279
|
-
if (out.explicit && out.bin
|
|
2280
|
-
if (!isAbsolute(out.bin)) { out.relative = true; return out; }
|
|
2391
|
+
if (out.explicit && hasSep(out.bin, platform)) {
|
|
2392
|
+
if (!(platform === "win32" ? win32.isAbsolute(out.bin) : isAbsolute(out.bin))) { out.relative = true; return out; }
|
|
2281
2393
|
out.windowsShim = underWsl && onDrive(out.bin);
|
|
2282
2394
|
take(out.bin, "explicit");
|
|
2283
2395
|
return out;
|
|
@@ -2287,9 +2399,9 @@ export function resolveEngineProgram(engine, { env = process.env, enginesDir = u
|
|
|
2287
2399
|
// tears a Windows PATH at every drive letter; the wizard's walk already used the delimiter.
|
|
2288
2400
|
const installed = out.explicit ? null : installedProgram(spec, enginesDir !== undefined ? enginesDir : enginesFolder());
|
|
2289
2401
|
const installedReal = installed ? realOrNull(installed) : null;
|
|
2290
|
-
for (const dir of String(env
|
|
2291
|
-
const p = join(dir, out.bin);
|
|
2292
|
-
if (!isExecFile(p)) continue;
|
|
2402
|
+
for (const dir of String(pathOf(env, platform) ?? "").split(platform === "win32" ? ";" : ":").filter(Boolean)) {
|
|
2403
|
+
const p = platform === "win32" ? windowsProgramIn(dir, out.bin, spec, env) : join(dir, out.bin);
|
|
2404
|
+
if (!p || !isExecFile(p, platform)) continue;
|
|
2293
2405
|
// UNDER WSL THE WINDOWS PATH IS APPENDED TO THIS ONE, so `claude` on a fresh WSL2 Ubuntu resolves to the
|
|
2294
2406
|
// Windows build before any Linux install. It is executable, and it fails the proof turn as "not signed
|
|
2295
2407
|
// in" because the credential it looks for is the Linux one. Passed over, and named for the caller to say.
|
|
@@ -2313,26 +2425,11 @@ export function resolveEngineProgram(engine, { env = process.env, enginesDir = u
|
|
|
2313
2425
|
* registry's.
|
|
2314
2426
|
*/
|
|
2315
2427
|
export function preflightEngineBinary(env = process.env, { platform = process.platform, enginesDir = undefined, wsl = null, onWindowsDrive = null } = {}) {
|
|
2316
|
-
//
|
|
2317
|
-
//
|
|
2318
|
-
//
|
|
2319
|
-
//
|
|
2320
|
-
//
|
|
2321
|
-
// delimiter, and the refusal stands anyway, because its real grounds were never the lookup: a stage runs
|
|
2322
|
-
// as its own process group and is stopped by signalling that group, an immediate stop identifies the
|
|
2323
|
-
// process from /proc or ps, and the write-boundary hook is quoted for a POSIX shell. Native Windows has
|
|
2324
|
-
// none of that, and where the program is found changes none of it.
|
|
2325
|
-
//
|
|
2326
|
-
// This fires FIRST so that no message about a PATH reaches a reader whose platform is the answer.
|
|
2327
|
-
//
|
|
2328
|
-
// `platform` is injectable so the refusal is testable off win32 — the population this protects is the
|
|
2329
|
-
// one that cannot run this suite to find out.
|
|
2330
|
-
if (platform === "win32") {
|
|
2331
|
-
throw new Error("[preflight] this engine does not run on native Windows. Each stage runs as its own process "
|
|
2332
|
-
+ "group and is stopped by signalling that group, which native Windows cannot do, so a stopped stage would "
|
|
2333
|
-
+ "leave the tools it started still running. Run it under WSL2, or in the devcontainer (.devcontainer/), "
|
|
2334
|
-
+ "where the documented install path applies unchanged.");
|
|
2335
|
-
}
|
|
2428
|
+
// NATIVE WINDOWS RUNS. This door refused it by name, on three grounds, and each now has a Windows
|
|
2429
|
+
// answer: a stage is stopped as a process tree rather than a group (engine/engine-spawn.mjs), the
|
|
2430
|
+
// immediate stop reads start times through PowerShell (claim-liveness.mjs), and the write-boundary hook
|
|
2431
|
+
// runs with no shell (anthropic-agent.mjs, writeBoundarySettings). `platform` stays a parameter, because
|
|
2432
|
+
// the lookup below answers by it.
|
|
2336
2433
|
const engine = (env.CLEAROTRON_AI || DEFAULT_ENGINE_ID).trim().toLowerCase();
|
|
2337
2434
|
const spec = ENGINE_BINARIES[engine];
|
|
2338
2435
|
if (!spec) return { engine, binEnv: null, bin: null, resolved: null }; // selectEngine owns this refusal
|
|
@@ -2352,7 +2449,7 @@ export function preflightEngineBinary(env = process.env, { platform = process.pl
|
|
|
2352
2449
|
// `envFrom` is therefore BELT-AND-BRACES, not the repair: it makes this site correct on its own terms
|
|
2353
2450
|
// rather than correct because something upstream normalised the environment first — a coupling
|
|
2354
2451
|
// nothing at this site declares and nothing here could notice breaking.
|
|
2355
|
-
const r = resolveEngineProgram(engine, { env, enginesDir, wsl, onWindowsDrive }); // injectable for the reason it gives
|
|
2452
|
+
const r = resolveEngineProgram(engine, { env, enginesDir, wsl, onWindowsDrive, platform }); // injectable for the reason it gives
|
|
2356
2453
|
const bin = r.bin;
|
|
2357
2454
|
const setTo = String(envFrom(env, spec.env) ?? "").trim();
|
|
2358
2455
|
const where = `${spec.env}${r.explicit ? "" : setTo
|
|
@@ -2361,7 +2458,7 @@ export function preflightEngineBinary(env = process.env, { platform = process.pl
|
|
|
2361
2458
|
|
|
2362
2459
|
if (r.relative) {
|
|
2363
2460
|
throw new Error(`[preflight] ${where} is the RELATIVE path "${bin}", which cannot work: the engine is `
|
|
2364
|
-
+ "spawned with the RUN DIRECTORY as its cwd
|
|
2461
|
+
+ "spawned with the RUN DIRECTORY as its cwd, not the repo, so a relative command is looked "
|
|
2365
2462
|
+ `for inside the run. Give an absolute path — e.g. ${join(REPO_ROOT, bin)} — or a bare name on PATH.`);
|
|
2366
2463
|
}
|
|
2367
2464
|
|
|
@@ -2375,9 +2472,9 @@ export function preflightEngineBinary(env = process.env, { platform = process.pl
|
|
|
2375
2472
|
const windows = r.skipped.length
|
|
2376
2473
|
? `. Passed over because they sit on a Windows drive, and a Windows build cannot run a stage here: ${r.skipped.join(", ")}` : "";
|
|
2377
2474
|
throw new Error(`[preflight] the ${engine} engine cannot run: ${where} names "${bin}", which is `
|
|
2378
|
-
+ (bin
|
|
2379
|
-
? (r.rejected[0]?.why ??
|
|
2380
|
-
: `not on PATH as an executable file (PATH=${env
|
|
2475
|
+
+ (hasSep(bin, platform)
|
|
2476
|
+
? (r.rejected[0]?.why ?? notStartable(platform))
|
|
2477
|
+
: `not on PATH as an executable file (PATH=${pathOf(env, platform) || "(empty)"})`
|
|
2381
2478
|
+ (r.explicit || installedRefused ? "" : ", and Clearotron has not installed one")
|
|
2382
2479
|
+ (passedOver ? `. Passed over: ${passedOver}` : "")
|
|
2383
2480
|
+ windows)
|
package/driver/e2e/README.md
CHANGED
|
@@ -37,7 +37,7 @@ other, which is the only proof available where no gold set exists.
|
|
|
37
37
|
|
|
38
38
|
A knockout scenario graded against a similar-marks sheet scores a **structural zero**. Knockout answers
|
|
39
39
|
one narrow question — how many register filings exist for the exact mark and its close variations in the
|
|
40
|
-
named classes — and never retrieves similar marks, so a lawyer's list of
|
|
40
|
+
named classes — and never retrieves similar marks, so a lawyer's list of WAVO PUNCH and WAVO TROPICS is
|
|
41
41
|
unreachable by construction. R3 and R4 scored 0/8 and 0/9 on both free-tier and clarivate on the same
|
|
42
42
|
day with the same engine, which measured the pairing rather than the engine.
|
|
43
43
|
|
|
@@ -90,7 +90,7 @@ export const jobJurisdictions = (job) =>
|
|
|
90
90
|
/**
|
|
91
91
|
* — WHAT THE ACCOUNT'S DEFAULT TERRITORIES MAY SAY TO THE MATTER FRAME.
|
|
92
92
|
*
|
|
93
|
-
* Returns 0
|
|
93
|
+
* Returns 0 to 2 prompt lines, spread into the frame's message by stages.mjs.
|
|
94
94
|
*
|
|
95
95
|
* THE RULE IS THE LADDER'S RULE, which is why it lives beside the ladder rather than in the prompt: a
|
|
96
96
|
* default FILLS AN ABSENT SCOPE and never widens a stated one. `resolveTerritories` below has always
|
|
@@ -139,9 +139,65 @@ export function defaultTerritoryState(profile) {
|
|
|
139
139
|
return { kept, unrecognized: dropped };
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* THE MAJOR MARKETS: where a crowded result is narrowed when neither the order nor the account names a
|
|
144
|
+
* market and the customer's field and brand point nowhere in particular. One list, and the manuals that
|
|
145
|
+
* name the major markets name these (ruled 2026-09-26: a generic customer narrows by the order's classes
|
|
146
|
+
* and the major markets). Codes as the territory vocabulary writes them, so GB is the UK.
|
|
147
|
+
*/
|
|
148
|
+
export const MAJOR_MARKETS = Object.freeze(["US", "EU", "GB", "CN", "JP"]);
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* WHERE A CROWDED RESULT IS NARROWED BY MARKET, in the ruled order (2026-09-26), as far as the driver can
|
|
152
|
+
* know it. An order with a territorial scope (its own countries, or the account's defaults filling an
|
|
153
|
+
* absent one) narrows within that scope, one market at a time: nothing outside it was ordered. A worldwide
|
|
154
|
+
* order, or one with no territory at all, narrows to the customer's priority markets (the account's
|
|
155
|
+
* defaults); else to the markets the customer's field and brand point to, which is the reading step's
|
|
156
|
+
* judgment; else to the major markets.
|
|
157
|
+
* `{ scope, priorities, major }` — arrays of codes; `scope` empty means worldwide. PURE.
|
|
158
|
+
*/
|
|
159
|
+
export function narrowingMarkets(job, profile) {
|
|
160
|
+
const { jurisdictions, geographyMode } = resolveTerritories(job, profile);
|
|
161
|
+
const scope = geographyMode === "worldwide" ? [] : jurisdictions.map((t) => String(t).trim()).filter(Boolean);
|
|
162
|
+
if (scope.length) return { scope, priorities: [], major: [] };
|
|
163
|
+
return { scope: [], priorities: defaultTerritoryState(profile).kept, major: [...MAJOR_MARKETS] };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* THE NARROWING ORDER, as the register reading step is told it (ruled 2026-09-26): goods and class
|
|
168
|
+
* first, then the markets in turn, with the markets the driver knows filled in. One line. PURE.
|
|
169
|
+
*/
|
|
170
|
+
export function narrowingOrderLine(job, profile, inScopeClasses = null) {
|
|
171
|
+
const { scope, priorities, major } = narrowingMarkets(job, profile);
|
|
172
|
+
const markets = scope.length
|
|
173
|
+
? `one question per market of the order's scope (${scope.join(", ")}); nothing outside it was ordered`
|
|
174
|
+
: `one question per market: ${[
|
|
175
|
+
priorities.length ? `the customer's priority markets (${priorities.join(", ")})` : "",
|
|
176
|
+
"the markets the customer's field and brand point to (judge them from the matter frame)",
|
|
177
|
+
`the major markets (${major.join(", ")})`,
|
|
178
|
+
].filter(Boolean).join(", else ")}`;
|
|
179
|
+
return `NARROWING A CROWD, in this order: first the goods and the classes, the client's goods words in `
|
|
180
|
+
+ `${inScopeClasses ? `the order's classes [${inScopeClasses}]` : "the order's classes"}, then the classes the crowding owners `
|
|
181
|
+
+ `file in; still a crowd, then the markets, ${markets}. From the narrowed lists, read what a lawyer would raise. `
|
|
182
|
+
+ `What was counted and not read stays on its coverage row with its count: set aside, never clean.`;
|
|
183
|
+
}
|
|
184
|
+
|
|
142
185
|
export function defaultJurisdictionsLine(job, profile) {
|
|
143
|
-
if (jobJurisdictions(job).length) return [];
|
|
144
186
|
const { kept } = defaultTerritoryState(profile);
|
|
187
|
+
// THE ORDER'S SCOPE IN ONE LINE, and the account's defaults as the customer's PRIORITIES, never as the
|
|
188
|
+
// scope (ruled 2026-09-26). The ladder's first rung, which this line had never heard of: measured
|
|
189
|
+
// 2026-09-25, a worldwide search under an account with seven default territories was told "the request
|
|
190
|
+
// names none — apply these", framed as those seven, and its crowded identical-mark question was read
|
|
191
|
+
// in those seven only.
|
|
192
|
+
if (job?.geography?.mode === "worldwide") {
|
|
193
|
+
return [
|
|
194
|
+
"Order scope: worldwide (AUTHORITATIVE — every register question is asked worldwide; do NOT narrow the scope, and exclude no market for lying outside the customer's priority markets).",
|
|
195
|
+
...(kept.length ? [`Customer priority markets (the account's default territories — where a crowded result is narrowed first; never the scope of this order): ${kept.join(", ")}.`] : []),
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
// A named order's own countries are its scope and the first markets a crowd narrows to, so no default
|
|
199
|
+
// appears anywhere in its prompt.
|
|
200
|
+
if (jobJurisdictions(job).length) return [];
|
|
145
201
|
return kept.length
|
|
146
202
|
? [`Customer-default jurisdictions that materially matter (the request names none — apply these): ${kept.join(", ")}.`]
|
|
147
203
|
: [];
|