clearotron 0.2.4 → 0.3.0-beta.1
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 +13 -2
- package/CONTRIBUTING.md +1 -1
- package/INSTALL.md +62 -38
- package/bin/brandowner.mjs +18 -169
- package/bin/clearotron.mjs +3 -1
- package/bin/connect.mjs +28 -19
- package/bin/disconnect.mjs +3 -3
- package/bin/example.mjs +7 -7
- package/bin/framework-preflight.mjs +49 -0
- package/bin/grant.mjs +151 -93
- package/bin/onboard.mjs +220 -138
- package/bin/start.mjs +146 -137
- package/bin/stop.mjs +2 -2
- package/bin/update.mjs +1 -1
- package/build-info.json +2 -2
- package/docs/CLIENT-MCP.md +2 -2
- package/docs/E2E.md +12 -2
- package/docs/ONBOARDING.md +1 -1
- package/docs/PORTAL.md +14 -13
- package/docs/SECURITY.md +23 -24
- package/docs/architecture/04-configuration-reference.md +12 -5
- package/docs/architecture/05-config-governance.md +7 -7
- package/docs/architecture/07-quality-and-audit.md +1 -1
- package/docs/architecture/08-development-guide.md +5 -0
- package/docs/configuration.md +118 -0
- package/docs/decisions/0004-documentation-structure.md +2 -2
- package/docs/decisions/0006-what-the-public-repository-carries.md +2 -2
- package/driver/CHANGELOG.md +70 -0
- package/driver/ask-ledger.mjs +2 -2
- package/driver/cancel.mjs +27 -0
- package/driver/case-law-sources.mjs +3 -3
- package/driver/company-bundle.mjs +261 -0
- package/driver/compare.mjs +1 -1
- package/driver/compose-read.mjs +2 -2
- package/driver/config-inventory.mjs +2 -2
- package/driver/contract-audit.mjs +1 -1
- package/driver/contract-e3-backlog.mjs +1 -1
- package/driver/contract-vocabulary.mjs +1 -1
- package/driver/declination-call.mjs +1 -1
- package/driver/deliver-trigger.sh +3 -3
- package/driver/dev-portal.mjs +3 -1
- package/driver/digest-queue.mjs +1 -1
- package/driver/disposition-tool.mjs +1 -1
- package/driver/doc-constants.mjs +1 -1
- package/driver/drain-posture.mjs +2 -2
- package/driver/drainer-identity.mjs +1 -1
- package/driver/driver.config.mjs +44 -9
- package/driver/effective-scope.mjs +30 -1
- package/driver/effort-model.mjs +6 -6
- package/driver/engine/CONTRACT.md +2 -2
- package/driver/engine/anthropic-agent.mjs +11 -11
- package/driver/engine/jx-turn.mjs +1 -1
- package/driver/engine/mcp/gather-config.mjs +29 -4
- package/driver/engine/mcp/recording-server.mjs +73 -1
- package/driver/engine/openai-agent.mjs +1 -1
- package/driver/engine/probe.mjs +28 -4
- package/driver/enqueue-schema.mjs +23 -3
- package/driver/findings-model.mjs +2 -2
- package/driver/flag-snapshot.mjs +2 -2
- package/driver/floor-duty.mjs +2 -2
- package/driver/frame-diff-model.mjs +1 -1
- package/driver/framework-preflight.mjs +143 -0
- package/driver/gateway.mjs +9 -1
- package/driver/hit-list.mjs +1 -1
- package/driver/jx-lanes.mjs +1 -1
- package/driver/jx.mjs +1 -1
- package/driver/knockout-assess-record.mjs +1 -1
- package/driver/knockout-review-record.mjs +435 -0
- package/driver/order-probe.mjs +1 -1
- package/driver/outbox-backoff.mjs +2 -2
- package/driver/owner-use-check.mjs +2 -2
- package/driver/package.json +1 -1
- package/driver/pipeline-knockout.mjs +105 -8
- package/driver/pipeline.mjs +81 -30
- package/driver/plain-register.mjs +77 -3
- package/driver/portal-access.mjs +141 -74
- package/driver/portal-config-view.mjs +59 -70
- package/driver/portal-report.mjs +4 -4
- package/driver/portal-service.mjs +348 -141
- package/driver/portal-upstream.mjs +105 -17
- package/driver/predelivery-lint.mjs +43 -18
- package/driver/product-rows.mjs +1 -1
- package/driver/products.mjs +1 -1
- package/driver/profile-page.html +30 -5
- package/driver/profile-service.mjs +197 -26
- package/driver/profiles.mjs +48 -1
- package/driver/publish/index.mjs +31 -13
- package/driver/publish/knockout.mjs +9 -5
- package/driver/publish/office-record-links.mjs +189 -0
- package/driver/publish/parse.mjs +3 -3
- package/driver/publish/publish-inputs.mjs +26 -0
- package/driver/publish/render-knockout.mjs +42 -42
- package/driver/publish/render.mjs +29 -4
- package/driver/publish/report-data.mjs +2 -2
- package/driver/publish/seed-pool.mjs +1 -1
- package/driver/publish/templates/report.css +8 -8
- package/driver/publish/xlsx.mjs +49 -7
- package/driver/queue-watch-verdict.mjs +2 -2
- package/driver/recipe-service.mjs +1 -1
- package/driver/record-carry.mjs +1 -1
- package/driver/reference-score.mjs +1 -1
- package/driver/reference-strip-signatures.mjs +1 -1
- package/driver/register-availability.mjs +4 -3
- package/driver/register-count.mjs +3 -3
- package/driver/register-records.mjs +1 -1
- package/driver/repair-composers.mjs +1 -1
- package/driver/repairs.mjs +3 -3
- package/driver/replay-archive.mjs +1 -1
- package/driver/report-card-record.mjs +1 -1
- package/driver/result-noun-fields.mjs +5 -0
- package/driver/roster-verdict.mjs +48 -5
- package/driver/run-activity.mjs +1 -1
- package/driver/run-requirements.mjs +18 -5
- package/driver/runner.mjs +24 -15
- package/driver/search-policy.mjs +8 -8
- package/driver/senior-rights.mjs +1 -1
- package/driver/skills/prelim-search/delivery-contract.md +1 -1
- package/driver/skills/prelim-search/risk-framework-triage.md +10 -7
- package/driver/stages-knockout.mjs +72 -6
- package/driver/stages.mjs +10 -10
- package/driver/suite-census.json +238 -70
- package/driver/synthesis-record.mjs +2 -2
- package/driver/systemd/clearotron-client-mcp.service +3 -3
- package/driver/systemd/clearotron-deploy.service +2 -2
- package/driver/systemd/clearotron-mcp-face.service +1 -1
- package/driver/systemd/clearotron-portal.service +3 -3
- package/driver/systemd/clearotron-worker.service +5 -5
- package/driver/systemd/install-census.mjs +1 -1
- package/driver/systemd/render-units.mjs +9 -9
- package/driver/terminal-clamp.mjs +1 -1
- package/driver/trigger-cap.mjs +18 -2
- package/driver/unit-inventory.mjs +8 -8
- package/driver/usage-ledger.mjs +5 -3
- package/driver/verify-knockout.mjs +7 -7
- package/driver/verify.mjs +5 -5
- package/driver/whatif-memo-run.mjs +1 -1
- package/driver/whatif-queue.mjs +3 -3
- package/driver/whatif-worker.mjs +2 -2
- package/examples/README.md +1 -1
- package/examples/grants.example.json +25 -24
- package/mcp-server/CHANGELOG.md +10 -0
- package/mcp-server/http-server.mjs +3 -3
- package/mcp-server/key-socket.mjs +1 -1
- package/mcp-server/lib/audit-view.mjs +3 -3
- package/mcp-server/lib/brief.mjs +3 -3
- package/mcp-server/lib/driver.mjs +1 -1
- package/mcp-server/lib/events.mjs +1 -1
- package/mcp-server/lib/http-handler.mjs +2 -2
- package/mcp-server/lib/instructions.mjs +2 -2
- package/mcp-server/lib/knockout.mjs +1 -1
- package/mcp-server/lib/ops.mjs +6 -3
- package/mcp-server/lib/options.mjs +15 -4
- package/mcp-server/lib/plan.mjs +7 -6
- package/mcp-server/lib/runs.mjs +10 -0
- package/mcp-server/lib/whatif.mjs +5 -5
- package/mcp-server/package.json +1 -1
- package/mcp-server/packs/README.md +1 -1
- package/mcp-server/remote/client-mcp-apikey.service +1 -1
- package/mcp-server/remote/client-mcp.service +2 -2
- package/mcp-server/remote/trademark-artifacts-http.service +1 -1
- package/mcp-server/server.mjs +38 -24
- package/package.json +2 -2
- package/portal-ui/dist/assets/{index-KFAHMgdT.js → index-CWTHP0sH.js} +3471 -1901
- package/portal-ui/dist/assets/{index-1ziUJX1E.css → index-KpytsmNH.css} +79 -26
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/_shared/lane-probe.mjs +9 -3
- package/providers/jx-subclass/lookup.mjs +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/oauth-mcp-bridge/systemd/courtlistener-mcp.service +1 -1
- package/scripts/citation-drift-report.mjs +1 -1
- package/scripts/citation-line-check.mjs +2 -2
- package/scripts/drive-env-check.mjs +1 -1
- package/scripts/e2e.mjs +5 -5
- package/scripts/env-audit.mjs +13 -1
- package/scripts/headless-page.mjs +5 -5
- package/scripts/live-surface-check.mjs +26 -2
- package/scripts/mint-names-in-force.mjs +19 -5
- package/scripts/mint-reference-strip-backlog.mjs +1 -1
- package/scripts/mint-suite-census.mjs +37 -10
- package/scripts/pack-publishable.mjs +1 -1
- package/scripts/preinstall-node-check.mjs +1 -1
- package/scripts/release-await-cut.mjs +3 -3
- package/scripts/release-cut-decision.mjs +1 -1
- package/scripts/release-dist-tag.mjs +1 -1
- package/scripts/release-install-check.mjs +1 -1
- package/scripts/release-notes-lint.mjs +1 -1
- package/scripts/release-publish-guard.mjs +1 -1
- package/scripts/release-version-pr-checks.mjs +2 -2
- package/scripts/release-version.mjs +61 -5
- package/scripts/render-brand-banner.mjs +1 -1
- package/scripts/render-check.mjs +2 -2
- package/scripts/repo-writes.mjs +1 -1
- package/scripts/report-frame-check.mjs +1 -1
- package/scripts/report-screenshot.mjs +2 -2
- package/scripts/retire-bare-refs.mjs +1 -1
- package/scripts/revisit-render-check.mjs +1 -1
- package/scripts/score.mjs +1 -1
- package/scripts/strip-titles-and-attributions.mjs +389 -0
- package/scripts/strip-tracker-citations.mjs +122 -5
- package/scripts/test-run.mjs +4 -4
- package/scripts/third-party-notices.mjs +1 -1
- package/scripts/verify-publishable.mjs +1 -1
- package/shared/access-audience.mjs +2 -2
- package/shared/anon-overlay.mjs +1 -1
- package/shared/brand.mjs +15 -1
- package/shared/bundle-freshness.mjs +1 -1
- package/shared/bundle-rebuild.mjs +1 -1
- package/shared/checkout-move.mjs +2 -2
- package/shared/client-door.mjs +8 -8
- package/shared/connect-clients.mjs +7 -7
- package/shared/connector-signin-probe.mjs +1 -1
- package/shared/env-aliases.mjs +1 -1
- package/shared/env-local.mjs +5 -5
- package/shared/grants-edit.mjs +76 -0
- package/shared/install-auth.mjs +1 -1
- package/shared/listen.mjs +3 -3
- package/shared/mcp-challenge.mjs +1 -1
- package/shared/names-in-force.mjs +2 -1
- package/shared/onboarding-store.mjs +19 -2
- package/shared/reference-guard-classes.mjs +44 -2
- package/shared/register-selection.mjs +1 -1
- package/shared/scope.mjs +223 -56
- package/shared/secret-file.mjs +1 -1
- package/shared/server-units.mjs +1 -1
- package/shared/staff-domain.mjs +45 -78
- package/shared/summary-blocks.mjs +2 -2
- package/shared/systemd-failure.mjs +3 -3
- package/shared/tracked-files.mjs +1 -1
- package/shared/trigger-lane.mjs +1 -1
- package/shared/tty-style.mjs +1 -1
- package/shared/usage-block.mjs +1 -1
- package/shared/vacuous-pass.mjs +1 -1
- package/shared/verb-shim.mjs +1 -1
package/driver/driver.config.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import { readdirSync, existsSync, accessSync, statSync, statfsSync, constants as
|
|
|
13
13
|
import { homedir } from "node:os";
|
|
14
14
|
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
|
|
15
15
|
import { invoke } from "../shared/invocation.mjs"; // — name a command the reader can actually type
|
|
16
|
-
import { envFileRead } from "../shared/env-local.mjs"; // — WHICH file to set it in, measured; null for a service that read none
|
|
16
|
+
import { envFileRead } from "../shared/env-local.mjs"; // — WHICH file to set it in, measured; null for a service that read none
|
|
17
17
|
import { numericSetting, resolveNumericSetting } from "./numeric-setting.mjs"; // — a number, or a refusal that names the variable; never NaN
|
|
18
18
|
|
|
19
19
|
const { X_OK } = FS;
|
|
@@ -117,7 +117,7 @@ export const config = {
|
|
|
117
117
|
// two different things depending on which accessor you asked, and `queueDirs` resolved a RELATIVE
|
|
118
118
|
// queue directory under the process cwd, which is its own incident shape.
|
|
119
119
|
//
|
|
120
|
-
//
|
|
120
|
+
// Ruling 2026-08-19: a whitespace-only value counts as UNSET, everywhere. `X=` already meant
|
|
121
121
|
// "not configured" and this is that rule finishing its sentence — not a reversal of it, which
|
|
122
122
|
// is why the 13 sites relying on empty-means-unset are untouched: they get the same answer they
|
|
123
123
|
// always did, and only the spaces-shaped near-miss changes.
|
|
@@ -226,6 +226,41 @@ export const config = {
|
|
|
226
226
|
return join(dirname(this.skillsBaseDir), rel);
|
|
227
227
|
},
|
|
228
228
|
|
|
229
|
+
/**
|
|
230
|
+
* WHICH LAYER ANSWERED, as a fact rather than a path — `resolveSkillPath` with its reasoning shown.
|
|
231
|
+
*
|
|
232
|
+
* THE SILENT CASE IS NOT THE MISSING ONE. A file in neither root resolves to a base path that does not
|
|
233
|
+
* exist, and the read then throws by name, which is loud already. The case nobody hears is a file
|
|
234
|
+
* ABSENT FROM THE OVERLAY AND PRESENT IN THE BASE: a real, readable file is returned, and if that file
|
|
235
|
+
* is a risk framework the matter is rated under somebody else's rubric with nothing in the log. The
|
|
236
|
+
* shipped tree carries `risk-framework.md`, `risk-framework-demo.md` and `risk-framework-triage.md`
|
|
237
|
+
* under the same names customers use for their own, so removing one from the config store swaps the
|
|
238
|
+
* deck rather than emptying it. NAME ONLY DECKS THAT SHIP: `files[]` excludes
|
|
239
|
+
* `risk-framework-*` and re-includes exactly those three, so an example naming any other deck is
|
|
240
|
+
* describing a tree the reader does not have.
|
|
241
|
+
*
|
|
242
|
+
* `layer` is what happened: "overlay" served from the config store, "base" served from the repo while
|
|
243
|
+
* an overlay was configured and did not hold it, "base-only" served from the repo with no overlay
|
|
244
|
+
* configured at all (the ordinary single-tree install — nothing to say about it), and "missing" held by
|
|
245
|
+
* neither, which the caller's own read reports.
|
|
246
|
+
*
|
|
247
|
+
* BEHAVIOUR IS NOT CHANGED HERE and must not be. The overlay-then-base fallback is the migration
|
|
248
|
+
* design: every generic methodology file legitimately falls back today, and making that throw would
|
|
249
|
+
* take the whole product down to make one class of file loud. This reports; the caller decides which
|
|
250
|
+
* layers matter to it.
|
|
251
|
+
*/
|
|
252
|
+
resolveSkillPathReport(relFromSkillsRoot) {
|
|
253
|
+
const rel = String(relFromSkillsRoot ?? "").replace(/^\/+/, "");
|
|
254
|
+
const basePath = join(dirname(this.skillsBaseDir), rel);
|
|
255
|
+
const overlay = this.skillsOverlayDir;
|
|
256
|
+
if (!overlay) return { path: basePath, rel, layer: existsSync(basePath) ? "base-only" : "missing", overlayPath: null, basePath };
|
|
257
|
+
if (!existsSync(overlay))
|
|
258
|
+
throw new Error(`skills_overlay_unreadable:${overlay} (CLEAROTRON_INSTRUCTIONS_DIR is set but the process cannot see it — customer-specific skills would silently fall back to the repo defaults)`);
|
|
259
|
+
const overlayPath = join(dirname(overlay), rel);
|
|
260
|
+
if (existsSync(overlayPath)) return { path: overlayPath, rel, layer: "overlay", overlayPath, basePath };
|
|
261
|
+
return { path: basePath, rel, layer: existsSync(basePath) ? "base" : "missing", overlayPath, basePath };
|
|
262
|
+
},
|
|
263
|
+
|
|
229
264
|
/**
|
|
230
265
|
* Every skills root handed to the engine's file tools (overlay + base, deduped).
|
|
231
266
|
*
|
|
@@ -377,7 +412,7 @@ export const config = {
|
|
|
377
412
|
//
|
|
378
413
|
// "`install` writes one for you" is the whole remedy this carried, and `install` refuses a
|
|
379
414
|
// non-terminal. This message reaches `start --background`'s screen, which is the scripted and hosted
|
|
380
|
-
// install route, so its reader was being sent to a wizard they cannot open
|
|
415
|
+
// install route, so its reader was being sent to a wizard they cannot open.
|
|
381
416
|
//
|
|
382
417
|
// `envFileRead()` rather than a path composed here, for the reason its header gives: this is a
|
|
383
418
|
// library reached by CLI entries AND by unit-booted services, and a service read no file of its own.
|
|
@@ -503,7 +538,7 @@ export const config = {
|
|
|
503
538
|
// ON-DISK NAME, NOT A PRODUCT NAME: an install that never set the variable is already using this
|
|
504
539
|
// directory, so renaming the default moves the install to an empty one and nothing migrates. Here
|
|
505
540
|
// the orphaned files are run-slot locks, so a live run's slot goes unseen and the global cap is
|
|
506
|
-
// silently exceeded rather than enforced.
|
|
541
|
+
// silently exceeded rather than enforced. Ruling.
|
|
507
542
|
get runLockDir() { return this.envValue("CLEAROTRON_RUN_LOCK_DIR") || join(this.workspaceRoot, "prelim-run-locks"); },
|
|
508
543
|
|
|
509
544
|
// Delivery outbox (Workstream B). On a handoff-mode finish the driver drops <runId>.pending here (naming
|
|
@@ -515,7 +550,7 @@ export const config = {
|
|
|
515
550
|
// ON-DISK NAME, NOT A PRODUCT NAME: an install that never set the variable is already using this
|
|
516
551
|
// directory, so renaming the default moves the install to an empty one and nothing migrates. Here
|
|
517
552
|
// the orphaned files are requester-facing events — delivered, run-failed, intake-rejected — so the
|
|
518
|
-
// visible failure is a requester never told their run finished.
|
|
553
|
+
// visible failure is a requester never told their run finished. Ruling.
|
|
519
554
|
get outboxDir() { return this.envValue("CLEAROTRON_OUTBOX_DIR") || join(this.workspaceRoot, "prelim-outbox"); },
|
|
520
555
|
|
|
521
556
|
// ── Delivery/comms (Phase 2, standalone product) ─────────────────────────────────────────────────
|
|
@@ -972,7 +1007,7 @@ export const PROVIDERS = {
|
|
|
972
1007
|
credEnv: "CLARIVATE_API_KEY",
|
|
973
1008
|
skillDoc: "skills/prelim-register/providers/clarivate.md",
|
|
974
1009
|
hasPublicRecordUrl: false, // Compumark Content has no public record URL — cite the office register
|
|
975
|
-
//,
|
|
1010
|
+
//, ruling 2026-08-20 — WHAT A CARD SHOWS WHERE A LINK CANNOT GO. A UI exists for this
|
|
976
1011
|
// provider and we do not know its per-record URL, so the card says so and says it is unfinished.
|
|
977
1012
|
// "placeholder" is the reader-facing admission; anything else would read as a citation.
|
|
978
1013
|
recordCitation: "placeholder",
|
|
@@ -1109,7 +1144,7 @@ export const PROVIDERS = {
|
|
|
1109
1144
|
credEnv: "SIGNA_API_KEY",
|
|
1110
1145
|
skillDoc: "skills/prelim-register/providers/signa.md",
|
|
1111
1146
|
hasPublicRecordUrl: false, // Signa exposes no per-record public URL — cite the office register
|
|
1112
|
-
//,
|
|
1147
|
+
//, ruling 2026-08-20 — no register UI exists to link to at all, so the card points at
|
|
1113
1148
|
// the artifact that DOES carry the record: the audit workbook. Naming it is the whole of this
|
|
1114
1149
|
// branch; constructing a per-record URL for a provider that publishes none would be a fabricated
|
|
1115
1150
|
// citation on a legal deliverable, and the ruling forbids it in those words.
|
|
@@ -1506,7 +1541,7 @@ export const RESEARCH_PROVIDERS = {
|
|
|
1506
1541
|
// and so the page never renders a title-cased guess at an id.
|
|
1507
1542
|
label: "Perplexity",
|
|
1508
1543
|
credEnv: "PERPLEXITY_API_KEY",
|
|
1509
|
-
// READER-TERMS FIELDS
|
|
1544
|
+
// READER-TERMS FIELDS: what its absence costs, in the reader's world,
|
|
1510
1545
|
// and where a key comes from. Stated beside the credential like `label`, so setup and doctor can
|
|
1511
1546
|
// DERIVE their prompts from this table instead of keeping a second hand list — the second list is
|
|
1512
1547
|
// how SERPAPI_API_KEY went unprompted while the code said "required for live grid cells".
|
|
@@ -1561,7 +1596,7 @@ export const RESEARCH_PROVIDERS = {
|
|
|
1561
1596
|
//
|
|
1562
1597
|
// ── / — THE NATIVE-LANGUAGE LANES RUN ON THE PROGRAM THE CUSTOMER CHOSE ────────────────
|
|
1563
1598
|
//
|
|
1564
|
-
//
|
|
1599
|
+
// Ruling 2026-08-20, verbatim: "one LLM provider only ever, API or auth, no mix." These three
|
|
1565
1600
|
// lanes were the mix. They POSTed to the Anthropic Messages API on `ANTHROPIC_API_KEY` at a hardcoded
|
|
1566
1601
|
// haiku tier no matter which engine the run was configured for — so round 21f9b0ad's receipt carried
|
|
1567
1602
|
// engine `openai-agent` / SUBSCRIPTION on its agentic stages and `anthropic-direct` / API-KEY on all
|
|
@@ -118,9 +118,30 @@ export const jobJurisdictions = (job) =>
|
|
|
118
118
|
* carries thirty-odd line-number citations from other files, so inserting explanation into it silently
|
|
119
119
|
* repoints all of them. Keeping the reasoning where the rule lives costs that file no lines.
|
|
120
120
|
*/
|
|
121
|
+
/**
|
|
122
|
+
* The account's default territories, split by whether the engine can search them.
|
|
123
|
+
*
|
|
124
|
+
* ONE reading of `defaultJurisdictions`, because there were two and they disagreed in silence. The
|
|
125
|
+
* prompt line below dropped what the vocabulary did not recognize; the resolved scope carried the same
|
|
126
|
+
* entries through untouched. So a misspelled default was simultaneously absent from what the model was
|
|
127
|
+
* told and present in what the search planned against — and the person who typed it saw neither.
|
|
128
|
+
*
|
|
129
|
+
* `unrecognized` is what `recognizedTerritories` calls `dropped`, renamed at this boundary because its
|
|
130
|
+
* own docstring states the rule the caller has to keep: it is A FINDING, NEVER A SILENCE. Returning it
|
|
131
|
+
* is what makes that possible; `resolveEffectiveScope` is where it reaches somebody.
|
|
132
|
+
*
|
|
133
|
+
* NOT a filter on what gets searched. What a stored default DOES is a client-outcome question and it is
|
|
134
|
+
* not this function's to answer, and the ruling behind this change is explicit that the request path's
|
|
135
|
+
* tolerance for an unrecognized territory stays exactly as it is. This reports; it does not narrow.
|
|
136
|
+
*/
|
|
137
|
+
export function defaultTerritoryState(profile) {
|
|
138
|
+
const { kept, dropped } = recognizedTerritories(profile ? profile.defaultJurisdictions ?? [] : []);
|
|
139
|
+
return { kept, unrecognized: dropped };
|
|
140
|
+
}
|
|
141
|
+
|
|
121
142
|
export function defaultJurisdictionsLine(job, profile) {
|
|
122
143
|
if (jobJurisdictions(job).length) return [];
|
|
123
|
-
const { kept } =
|
|
144
|
+
const { kept } = defaultTerritoryState(profile);
|
|
124
145
|
return kept.length
|
|
125
146
|
? [`Customer-default jurisdictions that materially matter (the request names none — apply these): ${kept.join(", ")}.`]
|
|
126
147
|
: [];
|
|
@@ -203,6 +224,14 @@ export function resolveEffectiveScope(job = {}, profile = null, resolved = null)
|
|
|
203
224
|
// The common-law grid's size follows the platform list, so widening the marketplaces widens the
|
|
204
225
|
// work. Surfaced because it is the part a requester cannot infer from their own request.
|
|
205
226
|
gridCellsPerVariant: profile ? derivedFloor(widened) : null,
|
|
227
|
+
// The stored defaults the engine cannot search — named, so the person approving a run can see that
|
|
228
|
+
// the configuration says more than the search will do. Empty on a profile with none, which is every
|
|
229
|
+
// profile written through an editor that refuses them at the point of entry.
|
|
230
|
+
//
|
|
231
|
+
// Reported off the PROFILE rather than off this run's ladder outcome. A default that lost to a
|
|
232
|
+
// request is still a default that will never work, and a reader who only hears about it on the runs
|
|
233
|
+
// where it happened to apply learns about a broken setting at random.
|
|
234
|
+
unrecognizedDefaultTerritories: defaultTerritoryState(profile).unrecognized,
|
|
206
235
|
};
|
|
207
236
|
}
|
|
208
237
|
|
package/driver/effort-model.mjs
CHANGED
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
* "1.5–2.5 hours" and a knockout from 45 to 15 minutes — and a stored quote is honoured at the version
|
|
52
52
|
* it was given. A version-2 quote must therefore not be read against this table.
|
|
53
53
|
* 4 —: the wave multiplier is gone and the turnaround quote is a table lookup with no arithmetic
|
|
54
|
-
* (
|
|
54
|
+
* (ruling 2026-08-26, recorded at `turnaroundBounds`). No weight moved and no unit changed.
|
|
55
55
|
* The SHAPE did, which is what this counter tracks — but state the size of it honestly: `runCount`
|
|
56
56
|
* is 1 for every job any door will admit, so the removed term was 1 on every quote that has ever
|
|
57
57
|
* been stored. No version-3 quote in any run record holds a number this table would not reproduce.
|
|
@@ -214,11 +214,11 @@ export const costBand = (i) => Math.max(1, Math.min(5, Math.ceil(effortUnits(i)
|
|
|
214
214
|
// ── turnaround ──────────────────────────────────────────────────────────────────────────────────────
|
|
215
215
|
//
|
|
216
216
|
// THERE IS NO ARITHMETIC IN THIS SECTION, AND THAT IS THE DESIGN. The quote is a table lookup and
|
|
217
|
-
// nothing else —
|
|
217
|
+
// nothing else — ruling 2026-08-26, below. Anything that multiplies, adds to or scales the ruled
|
|
218
218
|
// range is the thing this section was rebuilt to remove; do not reintroduce one without a new ruling.
|
|
219
219
|
|
|
220
220
|
/**
|
|
221
|
-
* THE QUOTED BOUNDS. ONE SOURCE —
|
|
221
|
+
* THE QUOTED BOUNDS. ONE SOURCE — ruling, 2026-08-23.
|
|
222
222
|
*
|
|
223
223
|
* WHAT THIS REPLACED, and why a constant beat a model. The old quote was a base plus one adder per lane:
|
|
224
224
|
* 1.5h, +0.5 for case law, +0.5 for a native-language lane, +0.5 for a single territory. It missed the
|
|
@@ -239,12 +239,12 @@ export const costBand = (i) => Math.max(1, Math.min(5, Math.ceil(effortUnits(i)
|
|
|
239
239
|
* harness judges against — see reconcileTurnaround.
|
|
240
240
|
*
|
|
241
241
|
* KNOCKOUT was quoted 45 min against 4–6 min delivered. Ruled "~15 min" — and SUPERSEDED by a later
|
|
242
|
-
*
|
|
242
|
+
* ruling to 5-10 minutes, a range. Recorded rather than rewritten,
|
|
243
243
|
* because the 4–6 min measurement beside it is the reason 5-10 is the better quote: 15 never sat on it.
|
|
244
244
|
*/
|
|
245
245
|
export const TURNAROUND_QUOTE = Object.freeze({
|
|
246
246
|
clearance: Object.freeze({ lowHours: 1.5, highHours: 2.5 }),
|
|
247
|
-
// —
|
|
247
|
+
// — RULING: a knockout quotes 5-10 MINUTES, a range, not a flat figure.
|
|
248
248
|
//
|
|
249
249
|
// The flat 0.25 came from the "no compute, just say it, keep it simple", and that ruling is
|
|
250
250
|
// untouched here: this is still one stated figure per pipeline with nothing computed from levers. What
|
|
@@ -264,7 +264,7 @@ export const quoteBoundsFor = (l) => TURNAROUND_QUOTE[deriveMode(l) === "knockou
|
|
|
264
264
|
/**
|
|
265
265
|
* The bounds for THIS job. The ruled range, and NOTHING is done to it.
|
|
266
266
|
*
|
|
267
|
-
* —
|
|
267
|
+
* — RULING, 2026-08-26: "No compute. We just say 1.5–2.5 hours for
|
|
268
268
|
* big reports, period. Keep it simple." The question put to him was which run-slot cap the quote should
|
|
269
269
|
* divide by, and he removed the division instead of answering it.
|
|
270
270
|
*
|
|
@@ -50,7 +50,7 @@ ladder consumes it without knowing which engine produced it ([gateway.mjs](../ga
|
|
|
50
50
|
// "text-parsed" on resetsAtBasis means it was read out of vendor
|
|
51
51
|
// prose that stated no timezone, so it resolved in the running
|
|
52
52
|
// box's zone — a reading, never a provider fact. Absent whenever
|
|
53
|
-
// `resetsAt` is absent.
|
|
53
|
+
// `resetsAt` is absent.
|
|
54
54
|
reads: string[] | absent, // READS GAUGE — file paths this turn's Read tool_use blocks
|
|
55
55
|
// opened; [] = recorded "read nothing"; key absent = engine cannot
|
|
56
56
|
// observe reads (openai-agent) → gateway journals `reads: null`
|
|
@@ -261,7 +261,7 @@ not extend the budget for it.
|
|
|
261
261
|
|
|
262
262
|
**The hard ceiling — the third clock, and it measures ACTIVE time.** The last-resort wall fires at the
|
|
263
263
|
stage's timeout + 60s (`CLEAROTRON_HARD_MS` pins it; unset and with no stage timeout, 660s) — compared not
|
|
264
|
-
against elapsed but against elapsed MINUS tool wait, including a call still in flight.
|
|
264
|
+
against elapsed but against elapsed MINUS tool wait, including a call still in flight. Ruling: a
|
|
265
265
|
model always delivers something or fails, so this ceiling exists for the harness's own failure modes,
|
|
266
266
|
and a turn waiting on a register call is working. Read on elapsed, a stage that spent 74.8% of its wall
|
|
267
267
|
in tool calls and one that stalled are indistinguishable; read on active time the first is a normal turn
|
|
@@ -494,7 +494,7 @@ export const anthropicAgentEngine = {
|
|
|
494
494
|
// "omitted" on Opus 5, so an ENGAGED block streams with a zero-length `thinking` string and a real
|
|
495
495
|
// `signature` — reading the text would report "no thinking" on every production turn.
|
|
496
496
|
let thought = false;
|
|
497
|
-
// MODEL GAUGE (
|
|
497
|
+
// MODEL GAUGE (corruption 3): which model the PROVIDER says served this turn. Until now
|
|
498
498
|
// nothing on any record was a function of the provider's response — `modelUsed` was
|
|
499
499
|
// `resolveModel(<the alias we asked for>)`, a pure function of the request, so a substitution or a
|
|
500
500
|
// tier bounce was unobservable by construction. The truth is on the wire twice: `system:init`
|
|
@@ -564,7 +564,7 @@ export const anthropicAgentEngine = {
|
|
|
564
564
|
// real (two chunks, no gap); keying on `spent === 0` would flag those and miss a stall that
|
|
565
565
|
// happened to leave 1ms on the clock.
|
|
566
566
|
let toolAskedChunk = null;
|
|
567
|
-
//
|
|
567
|
+
// THE LATENT HALF, NOT THE STALL — ONE OUTSTANDING ASK WAS ALL THIS COULD
|
|
568
568
|
// HOLD. `toolAskedAt` was a single slot, so a second assistant message arriving before the first
|
|
569
569
|
// ask's result OVERWROTE the first ask's start time and its wait was never counted at all.
|
|
570
570
|
// Measured on the overlap fixture (two asks, 150ms each): the old code reported toolWaitMs=149
|
|
@@ -689,7 +689,7 @@ export const anthropicAgentEngine = {
|
|
|
689
689
|
.filter((b) => b?.type === "tool_use");
|
|
690
690
|
const asked = askBlocks.map((b) => String(b?.name ?? "?"));
|
|
691
691
|
toolAskedAt = asked.length ? Date.now() : null;
|
|
692
|
-
toolAskedChunk = asked.length ? chunkSeq : null;
|
|
692
|
+
toolAskedChunk = asked.length ? chunkSeq : null;
|
|
693
693
|
toolAskedNames = asked.length ? [...new Set(asked)].sort().join("+") : null;
|
|
694
694
|
if (asked.length) {
|
|
695
695
|
const now = Date.now();
|
|
@@ -776,7 +776,7 @@ export const anthropicAgentEngine = {
|
|
|
776
776
|
const watchdog = setInterval(() => {
|
|
777
777
|
artifactProgress();
|
|
778
778
|
const now = Date.now();
|
|
779
|
-
// THE STARTUP DEBT.
|
|
779
|
+
// THE STARTUP DEBT. An earlier repair widened the deadline below until the first byte but left this
|
|
780
780
|
// clock's ORIGIN at spawn, so the startup interval stayed on the meter: the moment the grace let
|
|
781
781
|
// go, progIdle already WAS the whole boot, and any NOPROG shorter than startup had expired before
|
|
782
782
|
// the child was observed at all. The kill then landed on the next tick, before the ask could reach
|
|
@@ -787,9 +787,9 @@ export const anthropicAgentEngine = {
|
|
|
787
787
|
// ── STARTUP IS NEITHER SILENCE NOR WORK ──────────────────────────────────────────────
|
|
788
788
|
// Every clock here USED TO start at SPAWN, so until the child's first byte they were all timing
|
|
789
789
|
// process startup: an idle clock that had not yet seen silence, and an active clock that had not
|
|
790
|
-
// yet seen work. Past tense deliberately —
|
|
790
|
+
// yet seen work. Past tense deliberately — that work is finished, and the sentence stayed true of
|
|
791
791
|
// the byte-stall below (`lastMove`) while being false of the progress clock above, which is
|
|
792
|
-
// precisely the gap that let the defect sit here for a day looking like ambient flake.
|
|
792
|
+
// precisely the gap that let the defect sit here for a day looking like ambient flake. This was established for the byte-stall in common.mjs — which this engine does not use,
|
|
793
793
|
// because it spawns its own child, so that fix never reached this watchdog. Under full-suite load
|
|
794
794
|
// a starved spawn crossed a 400ms test ceiling before the mock had emitted anything, and the kill
|
|
795
795
|
// was recorded as a no-progress stall against a turn that had not yet been given a chance to
|
|
@@ -802,7 +802,7 @@ export const anthropicAgentEngine = {
|
|
|
802
802
|
else if (progIdle >= (started ? NOPROG : Math.max(NOPROG, GRACE))) { stallKill = true; noProgressKill = true; killed = true; killTree(); }
|
|
803
803
|
// ── THE CEILING MEASURES ACTIVE TIME, NOT ELAPSED ────────────────────────────────────
|
|
804
804
|
//
|
|
805
|
-
//
|
|
805
|
+
// Ruling: "there isnt such thing as a hung model. it always delivers something or fails."
|
|
806
806
|
// So this ceiling exists for the harness's own failure modes, not to budget the model, and a turn
|
|
807
807
|
// still doing work must not die because a tool it was waiting on took a while to answer.
|
|
808
808
|
//
|
|
@@ -855,7 +855,7 @@ export const anthropicAgentEngine = {
|
|
|
855
855
|
// STDERR DOES NOT START THE CLOCKS, and this line used to. This engine's protocol is
|
|
856
856
|
// stream-json on STDOUT; stderr carries node warnings and CLI notices, which a child can emit
|
|
857
857
|
// before it has done anything at all. Treating one as "the child has spoken" ended the grace and
|
|
858
|
-
// — since
|
|
858
|
+
// — since the change that made the first byte the progress clock's ORIGIN — started that clock too,
|
|
859
859
|
// so a turn whose real startup was still running got its no-progress ceiling measured from a
|
|
860
860
|
// deprecation warning. A child that writes ONLY stderr is still bounded: it never starts, so the
|
|
861
861
|
// byte-stall below fires at max(STALL, GRACE) exactly as it does for a silent spawn.
|
|
@@ -893,7 +893,7 @@ export const anthropicAgentEngine = {
|
|
|
893
893
|
//
|
|
894
894
|
// A turn killed mid-tool-call has an OPEN ask that `toolWaitMs` has not closed. Reporting the
|
|
895
895
|
// unclosed accumulator left `activeMs + toolWaitMs` short of the wall by exactly that gap — and
|
|
896
|
-
// only on turns killed during a call, which is the population
|
|
896
|
+
// only on turns killed during a call, which is the population this rule is about. A guard asserting
|
|
897
897
|
// the identity would have held on every ordinary attempt and quietly not held on the interesting
|
|
898
898
|
// one. Closed here, ONCE, so both fields are derived from the same number.
|
|
899
899
|
//
|
|
@@ -987,7 +987,7 @@ export const anthropicAgentEngine = {
|
|
|
987
987
|
// nothing unmeasurable), following toolWaitByTool's own rule that absence and "cannot report"
|
|
988
988
|
// must not look alike. `toolWaitMs` and the per-tool split are untouched.
|
|
989
989
|
toolWaitUnmeasurable: [...unmeasurable],
|
|
990
|
-
// MODEL GAUGE
|
|
990
|
+
// MODEL GAUGE: the id the WIRE reported, or null when the stream never said. Assistant
|
|
991
991
|
// message first (what served the call), init second (what the session was configured with).
|
|
992
992
|
// Never the requested alias — see the declaration above.
|
|
993
993
|
modelWire: wireModelAssistant ?? wireModelInit ?? null,
|
|
@@ -1023,7 +1023,7 @@ function errResult(t0, e, resumeRef) {
|
|
|
1023
1023
|
stderr: `anthropic-agent spawn error: ${e?.message ?? e}`, laneWaitMs: 0,
|
|
1024
1024
|
// reads: a spawn error means NO turn ran — [] is the true observation (nothing was read), not a gap.
|
|
1025
1025
|
// modelWire: null for the opposite reason — no turn ran, so the wire said nothing about a model, and
|
|
1026
|
-
// the record must say UNKNOWN rather than inherit the alias that was asked for
|
|
1026
|
+
// the record must say UNKNOWN rather than inherit the alias that was asked for.
|
|
1027
1027
|
json: null, usage: null, reads: [], readsTruncated: false, modelWire: null, sessionRef: resumeRef ?? null,
|
|
1028
1028
|
};
|
|
1029
1029
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
|
|
3
3
|
// engine/jx-turn.mjs — the native-language lanes reach a model the same way every other stage does.
|
|
4
4
|
//
|
|
5
|
-
// ── why ( /,
|
|
5
|
+
// ── why ( /, ruling 2026-08-20) ─────────────────────────────────────────────────────
|
|
6
6
|
//
|
|
7
7
|
// Verbatim: "one LLM provider only ever, API or auth, no mix". The jx lanes did not obey it and could
|
|
8
8
|
// not: they POSTed to the Anthropic Messages API on `ANTHROPIC_API_KEY` at a hardcoded haiku tier,
|
|
@@ -350,7 +350,7 @@ const RECORDING = Object.freeze({
|
|
|
350
350
|
// reads are already the seeded `Read` grant's. No `search_run_artifacts`: this stage reads NOTHING from
|
|
351
351
|
// the run — its finding arrives INLINE in the dispatch, which is the isolation the stage exists for.
|
|
352
352
|
//
|
|
353
|
-
// WHAT DOES NOT MOVE: the card's PROSE.
|
|
353
|
+
// WHAT DOES NOT MOVE: the card's PROSE. Ruling S2 (2026-08-13) re-scoped — the mechanical
|
|
354
354
|
// fields move now, the prose half waits on a side-by-side reading of one matter built both ways. The
|
|
355
355
|
// transport carries the seat's bullets; it does not write them.
|
|
356
356
|
"report-card": {
|
|
@@ -389,7 +389,7 @@ const RECORDING = Object.freeze({
|
|
|
389
389
|
// and must take it whole. That is a context cost, not a capability loss, and it is the honest price.
|
|
390
390
|
// NINTH — narrative-refutation, and the first conversion whose seat KEEPS its retrieval surface.
|
|
391
391
|
//
|
|
392
|
-
// The reviewer is the report's only check. Its output converts; its REACH does not.
|
|
392
|
+
// The reviewer is the report's only check. Its output converts; its REACH does not. Ruling
|
|
393
393
|
// (relayed 2026-08-26): it keeps the perplexity and band groups, because a reviewer that can only
|
|
394
394
|
// compare the report against itself is a proofreader, and a prose-consistency read is the shape that
|
|
395
395
|
// let the false coverage claim through. So this row states plainly what every sibling could take for
|
|
@@ -551,6 +551,23 @@ const RECORDING = Object.freeze({
|
|
|
551
551
|
+ "CALLS, UNMEASURED PRE-CONVERSION — this lane's seats hold no tools today, so the first "
|
|
552
552
|
+ "converted run is where the count comes from",
|
|
553
553
|
},
|
|
554
|
+
// THE LANE'S THIRD CONVERSION, and the only stage on it that was born typed — there is no
|
|
555
|
+
// pre-conversion knockout-review that wrote its own file, so nothing here is a migration.
|
|
556
|
+
//
|
|
557
|
+
// NOT FANNED. The pass reads the WHOLE merged record in one turn, because the rule it applies is about
|
|
558
|
+
// consistency across the lines a reader meets: two marks' basis lines rewritten in separate turns drift
|
|
559
|
+
// apart in register, which is the defect the pass exists to remove.
|
|
560
|
+
"knockout-review": {
|
|
561
|
+
seatWrites: false,
|
|
562
|
+
tools: Object.freeze(["record_knockout_review"]),
|
|
563
|
+
reason: "hands back rewrites as VALUES, each naming the line it replaces by the typed address the "
|
|
564
|
+
+ "driver measured and handed over. The address is what makes this a transport rather than a "
|
|
565
|
+
+ "matching problem: prose quoted back has to be found again in the record, and the finder then "
|
|
566
|
+
+ "decides where the rewrite lands. Refused by name at the call when an address names no line on "
|
|
567
|
+
+ "this run's record, so a seat learns while it can still send a repair turn. EXPECTED FROM THE "
|
|
568
|
+
+ "DICTATION'S ORDERED CALLS, UNMEASURED PRE-CONVERSION — this lane's seats hold no tools today, "
|
|
569
|
+
+ "so the first converted run is where the count comes from",
|
|
570
|
+
},
|
|
554
571
|
});
|
|
555
572
|
|
|
556
573
|
// stage → its server key. DERIVED, and the alphabet is checked rather than assumed: an underscore in a
|
|
@@ -1094,8 +1111,8 @@ const FANNED_STAGES = Object.freeze({ perAxis: PER_AXIS_STAGES, perChunk: PER_CH
|
|
|
1094
1111
|
* The BOUND ORDINAL for a fanned-out recording stage: which member of a per-ordinal artifact this turn
|
|
1095
1112
|
* is writing. The driver knows, because the driver is what fanned it out.
|
|
1096
1113
|
*
|
|
1097
|
-
* ONE FUNCTION, BOTH SUFFIX FORMS, for the reason `toolGroupsForStage` gives one block to both
|
|
1098
|
-
*
|
|
1114
|
+
* ONE FUNCTION, BOTH SUFFIX FORMS, for the reason `toolGroupsForStage` gives one block to both:
|
|
1115
|
+
* a second derivation beside the first is a second place to forget, and forgetting is how
|
|
1099
1116
|
* the `#` form came to be unhandled in the first place. It walks SUFFIX_FORMS, so a separator this
|
|
1100
1117
|
* module knows for grants is a separator it also knows for binding — the two cannot drift apart.
|
|
1101
1118
|
*
|
|
@@ -1247,6 +1264,14 @@ export const RECORDING_TOOLS = Object.freeze({
|
|
|
1247
1264
|
// retrieval grant here would contradict its own dispatch. Its reads are the instructed-scope sidecar
|
|
1248
1265
|
// and its skill doc, which the seeded Read grant serves.
|
|
1249
1266
|
"knockout-frame": Object.freeze(["Read", "mcp__recording-knockout-frame__record_knockout_frame"]),
|
|
1267
|
+
// THE LANE'S THIRD AND LAST CONVERSION. BY HAND, like every row here — O1 asserts the resolved grant
|
|
1268
|
+
// EQUALS this row, so a derived row would compare a value with itself.
|
|
1269
|
+
//
|
|
1270
|
+
// `Read` and its one record tool, and NO Write: this stage's artifact is the driver's. No search tool
|
|
1271
|
+
// and no register tools — the pass reads lines and rewrites them, and a retrieval grant would invite a
|
|
1272
|
+
// rewrite that adds a fact the rating never weighed. Its one read is the merged record the dispatch
|
|
1273
|
+
// names by path, which the seeded Read grant serves.
|
|
1274
|
+
"knockout-review": Object.freeze(["Read", "mcp__recording-knockout-review__record_knockout_review"]),
|
|
1250
1275
|
// THE WRITER. BY HAND, like every row here — O1 asserts the resolved grant EQUALS this row, so a
|
|
1251
1276
|
// derived row would compare a value with itself. This stage keeps three retrieval groups, and O1
|
|
1252
1277
|
// compares the WHOLE grant, so they are listed: a row naming only the recording half would be a row
|
|
@@ -64,6 +64,7 @@ import { REGISTER_AXES } from "../../coverage-ledger.mjs";
|
|
|
64
64
|
import { searchRunArtifacts, SEARCH_LIMITS } from "../../skeptic-search.mjs";
|
|
65
65
|
import { recordKnockoutAssess } from "../../knockout-assess-record.mjs";
|
|
66
66
|
import { recordKnockoutFrame } from "../../knockout-frame-record.mjs";
|
|
67
|
+
import { recordKnockoutReview } from "../../knockout-review-record.mjs";
|
|
67
68
|
|
|
68
69
|
async function record_blind_frame(params) {
|
|
69
70
|
const runDir = String(process.env.CLEAROTRON_BAND_RUN_DIR ?? "");
|
|
@@ -114,6 +115,16 @@ async function record_knockout_frame(params) {
|
|
|
114
115
|
return recordKnockoutFrame(runDir, params);
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
async function record_knockout_review(params) {
|
|
119
|
+
const runDir = String(process.env.CLEAROTRON_BAND_RUN_DIR ?? "");
|
|
120
|
+
if (!runDir) {
|
|
121
|
+
return { error: "this server was started without a run — the driver wires it per run; there is no parameter for it and this tool never guesses one" };
|
|
122
|
+
}
|
|
123
|
+
// NO BOUND ORDINAL. The pass reads the whole merged record in one turn, so there is nothing for the
|
|
124
|
+
// driver to bind and nothing for a payload to usurp — the same asymmetry knockout-frame states.
|
|
125
|
+
return recordKnockoutReview(runDir, params);
|
|
126
|
+
}
|
|
127
|
+
|
|
117
128
|
async function record_prelim_variants(params) {
|
|
118
129
|
const runDir = String(process.env.CLEAROTRON_BAND_RUN_DIR ?? "");
|
|
119
130
|
if (!runDir) {
|
|
@@ -686,7 +697,7 @@ serve({
|
|
|
686
697
|
}, {
|
|
687
698
|
// ── CONVERSION 5 — ONE FINDING CARD, AND THE FIRST FAN-OUT TRANSPORT ──────────────────────────
|
|
688
699
|
//
|
|
689
|
-
// The schema carries the seat's JUDGMENT and nothing else.
|
|
700
|
+
// The schema carries the seat's JUDGMENT and nothing else. Ruling S2 keeps the prose the
|
|
690
701
|
// model's pending a side-by-side reading, so `text` is the seat's sentence — what the driver takes
|
|
691
702
|
// over is every SHAPE around it: the bullet, the bold lead-in, the `::p::` position, and the final
|
|
692
703
|
// `- Source:` line, which is composed from the finding's own record rather than typed from a host
|
|
@@ -1170,6 +1181,67 @@ serve({
|
|
|
1170
1181
|
},
|
|
1171
1182
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1172
1183
|
handler: record_knockout_assess,
|
|
1184
|
+
}, {
|
|
1185
|
+
name: "record_knockout_review",
|
|
1186
|
+
description:
|
|
1187
|
+
"Hand back your REWRITES of the lines a reader meets first, as VALUES. The driver writes them into " +
|
|
1188
|
+
"the rated record itself, so you never format JSON and never write a file. Each rewrite names the " +
|
|
1189
|
+
"line it replaces by the `at` address you were handed in the dispatch, copied verbatim — you do " +
|
|
1190
|
+
"not quote the sentence and there is no field for one. An address naming no line on this run's " +
|
|
1191
|
+
"record is refused by name, so a mistyped address costs you a turn rather than landing your text " +
|
|
1192
|
+
"on the wrong line. A repair turn may send only the rows it is correcting: the driver merges BY " +
|
|
1193
|
+
"ADDRESS onto what it already accepted, so rows you omit survive.",
|
|
1194
|
+
inputSchema: {
|
|
1195
|
+
type: "object",
|
|
1196
|
+
additionalProperties: false,
|
|
1197
|
+
properties: {
|
|
1198
|
+
schema_version: { type: "integer", description: "Accepted and ignored — the driver stamps its own." },
|
|
1199
|
+
rewrites: {
|
|
1200
|
+
type: "array",
|
|
1201
|
+
description:
|
|
1202
|
+
"One row per line you are replacing. A rewrite keeps every fact and changes only the " +
|
|
1203
|
+
"register — the same band, names, reasons and conclusion, in the words the reader owns.",
|
|
1204
|
+
items: {
|
|
1205
|
+
type: "object", additionalProperties: false, required: ["at", "text"],
|
|
1206
|
+
properties: {
|
|
1207
|
+
at: {
|
|
1208
|
+
type: "object", additionalProperties: false, required: ["field"],
|
|
1209
|
+
description: "The address, copied VERBATIM from the dispatch row. Do not compose one.",
|
|
1210
|
+
properties: {
|
|
1211
|
+
field: { type: "string", description: "The field this line lives in." },
|
|
1212
|
+
mark: { type: "string", description: "The mark's name, where the field is a mark's." },
|
|
1213
|
+
index: { type: "integer", description: "The position, where the field is a list." },
|
|
1214
|
+
ordinal: { type: "integer", description: "The finding's ordinal, for a conflict's one sentence." },
|
|
1215
|
+
},
|
|
1216
|
+
},
|
|
1217
|
+
text: { type: "string", description: "The replacement prose, finished — written into the field exactly as sent." },
|
|
1218
|
+
why: { type: "string", description: "Optional: what was wrong with the line you replaced." },
|
|
1219
|
+
},
|
|
1220
|
+
},
|
|
1221
|
+
},
|
|
1222
|
+
declined: {
|
|
1223
|
+
type: "array",
|
|
1224
|
+
description:
|
|
1225
|
+
"One row per flagged line you are LEAVING ALONE, with the reason. A line you neither " +
|
|
1226
|
+
"rewrite nor decline cannot be told afterwards from a line nobody read.",
|
|
1227
|
+
items: {
|
|
1228
|
+
type: "object", additionalProperties: false, required: ["at", "why"],
|
|
1229
|
+
properties: {
|
|
1230
|
+
at: {
|
|
1231
|
+
type: "object", additionalProperties: false, required: ["field"],
|
|
1232
|
+
properties: {
|
|
1233
|
+
field: { type: "string" }, mark: { type: "string" },
|
|
1234
|
+
index: { type: "integer" }, ordinal: { type: "integer" },
|
|
1235
|
+
},
|
|
1236
|
+
},
|
|
1237
|
+
why: { type: "string", description: "Why the line stands as written." },
|
|
1238
|
+
},
|
|
1239
|
+
},
|
|
1240
|
+
},
|
|
1241
|
+
},
|
|
1242
|
+
},
|
|
1243
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1244
|
+
handler: record_knockout_review,
|
|
1173
1245
|
}, {
|
|
1174
1246
|
name: "record_knockout_frame",
|
|
1175
1247
|
description:
|
|
@@ -39,7 +39,7 @@ const codexBin = () => envFrom(process.env, "CLEAROTRON_CODEX_PATH") || "codex";
|
|
|
39
39
|
// on both engines. `sol` remains what codex itself reports as its own default (live-probed 2026-07-27:
|
|
40
40
|
// `model: gpt-5.6-sol, provider: openai`), which is why the judgment tier keeps it.
|
|
41
41
|
//
|
|
42
|
-
// ── THE THREE TIERS MAP ONTO THE CODEX LADDER (
|
|
42
|
+
// ── THE THREE TIERS MAP ONTO THE CODEX LADDER (ruling 2026-09-02,) ──────────────────────────
|
|
43
43
|
//
|
|
44
44
|
// Every stage declares a tier — opus / sonnet / haiku — engine-independently. The anthropic engine
|
|
45
45
|
// resolves those three declarations onto three models. Until this ruling codex resolved ALL THREE onto
|
package/driver/engine/probe.mjs
CHANGED
|
@@ -90,7 +90,20 @@ const tail = (s) => {
|
|
|
90
90
|
return t.length > DETAIL_CHARS ? `…${t.slice(-DETAIL_CHARS)}` : t;
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
// THE HEADLESS ROUTE, WHERE THE ENGINE HAS ONE. The interactive sign-in is the one thing a server with no
|
|
94
|
+
// browser cannot do, and it was the only remedy this offered — including to a box that had configured the
|
|
95
|
+
// route built for servers. The engine table already carries that route; this reads it rather than a copy.
|
|
96
|
+
const signInLine = (engine) => {
|
|
97
|
+
const spec = ENGINE_BINARIES[engine];
|
|
98
|
+
const base = spec?.signIn ?? "sign the CLI in";
|
|
99
|
+
const h = spec?.headless;
|
|
100
|
+
// Both forms the wizard already offers, read off the same table: a TOKEN route is run elsewhere and
|
|
101
|
+
// carried here by variable; a DEVICE route is run on this box and signs it in directly.
|
|
102
|
+
if (!h?.cmd) return base;
|
|
103
|
+
return h.tokenEnv
|
|
104
|
+
? `${base} — or, on a box with no browser, run \`${h.cmd}\` on any machine you can sign in on and set the token it prints as ${h.tokenEnv} in this install's environment file`
|
|
105
|
+
: `${base} — or, on a box with no browser, run \`${h.cmd}\` here`;
|
|
106
|
+
};
|
|
94
107
|
|
|
95
108
|
/**
|
|
96
109
|
* One verdict from one turn. PURE — no clock, no filesystem, no process.
|
|
@@ -273,6 +286,19 @@ export function probeWeatherWarning(verdict) {
|
|
|
273
286
|
* again; a credential outliving the check that borrowed it would be a worse defect than the one this
|
|
274
287
|
* fixed, and a check drives the restore rather than trusting it.
|
|
275
288
|
*/
|
|
289
|
+
/**
|
|
290
|
+
* EVERY VARIABLE THAT DECIDES WHAT AN ENGINE SPAWN IS — selection, binary, billing mode and the
|
|
291
|
+
* credentials, the API key and the headless token alike. ONE list: `applyEngineEnv` below applies it,
|
|
292
|
+
* and a caller building the environment it hands this module fills from it. `doctor` kept a second copy
|
|
293
|
+
* that had dropped both credentials, so it filled a probe environment without the very token it then
|
|
294
|
+
* reported missing. Exported so there is nothing left to copy.
|
|
295
|
+
*/
|
|
296
|
+
export function engineEnvKeys() {
|
|
297
|
+
return [...new Set(["CLEAROTRON_AI", ...Object.values(ENGINE_BINARIES)
|
|
298
|
+
.flatMap((s) => [s.env, s.authEnv, s.apiKeyEnv, s.headless?.tokenEnv])
|
|
299
|
+
.filter(Boolean)])];
|
|
300
|
+
}
|
|
301
|
+
|
|
276
302
|
function applyEngineEnv(env) {
|
|
277
303
|
if (!env || env === process.env) return () => {};
|
|
278
304
|
// EVERY VARIABLE THAT DECIDES WHAT THE SPAWN IS, not only the ones that decide WHICH BINARY.
|
|
@@ -293,9 +319,7 @@ function applyEngineEnv(env) {
|
|
|
293
319
|
// written, so the restore put that back instead of deleting it, and the mode leaked into the rest of
|
|
294
320
|
// the process. Caught by the arm that exists to prove the restore, which is the only reason widening
|
|
295
321
|
// this list was safe to do at all.
|
|
296
|
-
const keys =
|
|
297
|
-
.flatMap((s) => [s.env, s.authEnv, s.apiKeyEnv, s.headless?.tokenEnv])
|
|
298
|
-
.filter(Boolean)])];
|
|
322
|
+
const keys = engineEnvKeys();
|
|
299
323
|
const saved = new Map();
|
|
300
324
|
for (const k of keys) {
|
|
301
325
|
saved.set(k, process.env[k]);
|
|
@@ -115,6 +115,8 @@ export const DECLARED_JOB_FIELDS = Object.freeze([
|
|
|
115
115
|
"deliveryRoute", "parentRunId",
|
|
116
116
|
// whose rulebook rates it
|
|
117
117
|
"profileKey", "projectKey", "customer", "customerUnknown",
|
|
118
|
+
// which organisation's Generic — set by a door only on a Generic run, from the verified principal
|
|
119
|
+
"tenant",
|
|
118
120
|
// intake fidelity — posture and prose, verbatim from the request
|
|
119
121
|
"deliverableSpec", "commercialFlexibility", "priorUse", "campaignShape",
|
|
120
122
|
"upfrontInstructions", "brief", "rawRequest", "deadline", "provider",
|
|
@@ -675,7 +677,7 @@ export function validateJob(job, { atClaim = false } = {}) {
|
|
|
675
677
|
errs.push(`both product (${JSON.stringify(rawProduct)}) and recipeKey (${JSON.stringify(rawRecipe)}) are set — name ONE selector (a saved search already carries its product)`);
|
|
676
678
|
if (rawProduct && !ORDERABLE_PRODUCTS.includes(rawProduct))
|
|
677
679
|
errs.push(unknownProductMessage(job.product));
|
|
678
|
-
// caseLaw: NOT A REQUEST FIELD ANY MORE (
|
|
680
|
+
// caseLaw: NOT A REQUEST FIELD ANY MORE (ruling 2026-08-06). It is what a Full country search IS.
|
|
679
681
|
// REFUSED rather than ignored, and that is the whole point: a flag accepted and dropped is the
|
|
680
682
|
// "accepted, then quietly narrower" shape — a requester who sent it believes they bought the deep
|
|
681
683
|
// reading. The sentence names the product that carries it, so the refusal is one edit away from a
|
|
@@ -752,8 +754,26 @@ export function validateJob(job, { atClaim = false } = {}) {
|
|
|
752
754
|
let known = false;
|
|
753
755
|
let roster = [];
|
|
754
756
|
try {
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
+
const key = String(job.profileKey).trim();
|
|
758
|
+
let profiles = loadProfiles();
|
|
759
|
+
known = profiles.has(key);
|
|
760
|
+
// THE ROSTER MOVES WHILE THIS PROCESS RUNS, and until this re-read it could not see that.
|
|
761
|
+
//
|
|
762
|
+
// `loadProfiles()` answers from a module cache with no expiry and no invalidation hook, filled
|
|
763
|
+
// when this process started. The engine door is a long-lived process of its own, so a company
|
|
764
|
+
// created through the portal AFTER it booted was refused here — "names no known customer" — while
|
|
765
|
+
// its file sat on disk and the portal had already told the person they could search under it.
|
|
766
|
+
// That is the whole of the defect the credential re-mint was meant to remove: the grant wall
|
|
767
|
+
// stopped refusing and this did not, so the person met the same dead end one step further in.
|
|
768
|
+
//
|
|
769
|
+
// ON THE MISS ONLY, so the ordinary request pays nothing: a hit is answered from the cache exactly
|
|
770
|
+
// as before, and a directory read happens just when the alternative is a wrong refusal. This is
|
|
771
|
+
// what "the door checks the company exists on disk now" has to mean for a process that has been
|
|
772
|
+
// up for a week.
|
|
773
|
+
if (!known) {
|
|
774
|
+
profiles = loadProfiles({ force: true });
|
|
775
|
+
known = profiles.has(key);
|
|
776
|
+
}
|
|
757
777
|
roster = [...profiles.keys()].sort();
|
|
758
778
|
} catch { known = true; }
|
|
759
779
|
// NAME THE ROSTER THIS PROCESS CAN SEE, always.
|