clearotron 0.3.2-beta.7 → 0.3.2-beta.9
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 +58 -26
- package/CONTRIBUTING.md +8 -8
- package/INSTALL.md +148 -81
- package/README.md +3 -3
- package/SECURITY.md +3 -3
- package/bin/brandowner.mjs +3 -3
- package/bin/framework-preflight.mjs +1 -1
- package/bin/onboard.mjs +637 -216
- package/bin/start.mjs +151 -27
- package/bin/update.mjs +58 -11
- package/build-info.json +2 -2
- package/docs/DELIVERY.md +2 -1
- package/docs/INTAKE.md +1 -1
- package/docs/ONBOARDING.md +1 -1
- package/docs/architecture/03-run-lifecycle.md +6 -6
- package/docs/architecture/04-configuration-reference.md +32 -14
- package/docs/architecture/05-config-governance.md +23 -8
- package/docs/architecture/05-customer-profiles.md +2 -2
- package/docs/architecture/06-operations-runbook.md +3 -3
- package/docs/architecture/08-development-guide.md +6 -6
- package/docs/configuration.md +5 -5
- package/docs/decisions/0003-credential-model.md +1 -1
- package/docs/writing-standard.md +4 -0
- package/driver/CHANGELOG.md +124 -0
- package/driver/README.md +3 -3
- package/driver/band-size.mjs +59 -0
- package/driver/binding-layers.mjs +1 -1
- package/driver/citation-census.json +3 -3
- package/driver/{prelim-variants-record.mjs → clearance-variants-record.mjs} +24 -24
- package/driver/common-law-receipts.mjs +2 -2
- package/driver/company-bundle.mjs +3 -3
- package/driver/compose-read.mjs +8 -14
- package/driver/config-inventory.mjs +112 -9
- package/driver/consumption-ledger.mjs +2 -2
- package/driver/contract-arm2-baseline.json +2 -5
- package/driver/contract-dictation-registry.mjs +19 -19
- package/driver/contract-e3-backlog.mjs +43 -43
- package/driver/contract-e3-baseline.json +14 -14
- package/driver/contract-vocabulary.mjs +68 -27
- package/driver/deliver-trigger.sh +16 -16
- package/driver/demo-container.mjs +3 -3
- package/driver/dev-portal.mjs +3 -3
- package/driver/disposition-call.mjs +1 -1
- package/driver/door-gates.mjs +41 -7
- package/driver/doubt-ledger.mjs +2 -2
- package/driver/drainer-identity.mjs +34 -8
- package/driver/driver.config.mjs +367 -104
- package/driver/engine/CONTRACT.md +10 -3
- package/driver/engine/README.md +2 -2
- package/driver/engine/anthropic-agent.mjs +77 -21
- package/driver/engine/auth.mjs +129 -10
- package/driver/engine/jx-turn.mjs +7 -6
- package/driver/engine/mcp/README.md +1 -1
- package/driver/engine/mcp/dispositions-server.mjs +3 -3
- package/driver/engine/mcp/gather-config.mjs +9 -9
- package/driver/engine/mcp/perplexity-server.mjs +2 -2
- package/driver/engine/mcp/recording-server.mjs +18 -5
- package/driver/engine/openai-agent.mjs +4 -2
- package/driver/engine/probe.mjs +110 -23
- package/driver/enqueue-schema.mjs +6 -2
- package/driver/findings-model.mjs +6 -3
- package/driver/flag-snapshot.mjs +34 -8
- package/driver/form-neighbourhood.mjs +54 -7
- package/driver/framework.mjs +4 -4
- package/driver/gateway.mjs +36 -24
- package/driver/jx-lanes.mjs +23 -4
- package/driver/jx-units.mjs +7 -4
- package/driver/jx.mjs +34 -4
- package/driver/knockout-review-record.mjs +56 -4
- package/driver/known-conflicts.mjs +1 -1
- package/driver/matter-frame-record.mjs +90 -1
- package/driver/named-band.mjs +1 -1
- package/driver/ordinary-words.mjs +51 -0
- package/driver/outbox-backoff.mjs +31 -16
- package/driver/package.json +1 -1
- package/driver/partial-payload-baseline.json +2 -2
- package/driver/phase0.mjs +3 -3
- package/driver/pipeline-knockout.mjs +5 -5
- package/driver/pipeline.mjs +396 -81
- package/driver/placement-form.mjs +77 -1
- package/driver/placement-model.mjs +1 -1
- package/driver/portal-config-view.mjs +30 -1
- package/driver/portal-report.mjs +107 -6
- package/driver/portal-service.mjs +80 -14
- package/driver/portal-upstream.mjs +1 -1
- package/driver/predelivery-lint.mjs +12 -2
- package/driver/preserve-merge.mjs +3 -3
- package/driver/product-rows.mjs +2 -2
- package/driver/products.mjs +1 -1
- package/driver/profiles/README.md +3 -3
- package/driver/profiles/demo-brand-owner.json +2 -2
- package/driver/profiles.mjs +55 -17
- package/driver/progress.mjs +18 -8
- package/driver/provider-usage.mjs +8 -8
- package/driver/publish/index.mjs +154 -8
- package/driver/publish/knockout.mjs +39 -5
- package/driver/publish/pool-admin.mjs +1 -1
- package/driver/publish/publish-inputs.mjs +18 -2
- package/driver/publish/render-knockout.mjs +184 -31
- package/driver/publish/render.mjs +323 -93
- package/driver/publish/report-data.mjs +4 -1
- package/driver/publish/report-topbar.mjs +58 -0
- package/driver/publish/search-depth.mjs +133 -4
- package/driver/publish/templates/report.css +78 -4
- package/driver/publish/xlsx.mjs +20 -1
- package/driver/queue-order.mjs +2 -2
- package/driver/recording-agreement.mjs +1 -1
- package/driver/reference-score.mjs +1 -1
- package/driver/register-availability.mjs +2 -2
- package/driver/register-count.mjs +50 -5
- package/driver/register-coverage.mjs +161 -1
- package/driver/register-digest-record.mjs +236 -11
- package/driver/register-grant-vocabulary.mjs +1 -1
- package/driver/register-plan.mjs +189 -2
- package/driver/registry-fidelity.mjs +3 -3
- package/driver/repair-composers.mjs +1 -1
- package/driver/repair-contract.mjs +1 -1
- package/driver/replay-archive.mjs +6 -6
- package/driver/report-overview-record.mjs +2 -2
- package/driver/result-noun-fields.mjs +2 -2
- package/driver/run-economics.mjs +41 -10
- package/driver/run-requirements.mjs +173 -9
- package/driver/runner.mjs +5 -5
- package/driver/scope-facts.mjs +20 -5
- package/driver/scope-ledger.mjs +5 -5
- package/driver/search-policy.mjs +22 -12
- package/driver/skills/README.md +15 -15
- package/driver/skills/blind-frame/SKILL.md +2 -2
- package/driver/skills/case-law-citation/SKILL.md +4 -4
- package/driver/skills/case-law-citation/sources/eurlex.md +1 -1
- package/driver/skills/{prelim-common-law → clearance-common-law}/SKILL.md +22 -22
- package/driver/skills/{prelim-common-law → clearance-common-law}/perplexity-prompts.md +1 -1
- package/driver/skills/{prelim-register → clearance-register}/SKILL.md +10 -10
- package/driver/skills/{prelim-register → clearance-register}/digest.md +2 -2
- package/driver/skills/{prelim-register → clearance-register}/providers/README.md +1 -1
- package/driver/skills/{prelim-register → clearance-register}/providers/clarivate.md +37 -35
- package/driver/skills/{prelim-register → clearance-register}/providers/corsearch.md +20 -11
- package/driver/skills/{prelim-register → clearance-register}/providers/signa.md +5 -5
- package/driver/skills/{prelim-register → clearance-register}/register-recipes.md +3 -3
- package/driver/skills/{prelim-register → clearance-register}/status-rules.md +2 -2
- package/driver/skills/{prelim-register → clearance-register}/stealth-filer-indicators.md +1 -1
- package/driver/skills/{prelim-register → clearance-register}/unit.md +2 -2
- package/driver/skills/{prelim-search → clearance-search}/SKILL.md +31 -31
- package/driver/skills/{prelim-search → clearance-search}/delivery-contract.md +1 -1
- package/driver/skills/{prelim-search → clearance-search}/phase2-execution.md +18 -18
- package/driver/skills/{prelim-search → clearance-search}/synthesis-rules.md +7 -7
- package/driver/skills/{prelim-variants → clearance-variants}/SKILL.md +18 -18
- package/driver/skills/{prelim-variants → clearance-variants}/transliteration-scripts.md +5 -5
- package/driver/skills/frame-diff/SKILL.md +1 -1
- package/driver/skills/knockout-assess/SKILL.md +10 -7
- package/driver/skills/matter-frame/SKILL.md +3 -3
- package/driver/skills/narrative-refutation/SKILL.md +9 -9
- package/driver/skills/placement-inquiry/SKILL.md +5 -5
- package/driver/stage-context.mjs +1 -1
- package/driver/stages-knockout.mjs +4 -4
- package/driver/stages.mjs +65 -61
- package/driver/status-snapshot.mjs +2 -2
- package/driver/suite-census.json +340 -136
- package/driver/surface-exit-verdict.mjs +58 -0
- package/driver/systemd/README.md +9 -6
- package/driver/systemd/clearotron-worker.service +1 -1
- package/driver/terminal-clamp.mjs +109 -1
- package/driver/tokens.mjs +169 -3
- package/driver/unit-environment.mjs +42 -15
- package/driver/unit-inventory.mjs +19 -2
- package/driver/usage-ledger.mjs +1 -1
- package/driver/variant-manifest-model.mjs +4 -4
- package/driver/verify-knockout.mjs +27 -0
- package/driver/verify.mjs +94 -6
- package/driver/whatif-queue.mjs +1 -1
- package/driver/wordlists/en.txt +63906 -0
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/README.md +1 -1
- package/mcp-server/lib/README.md +1 -1
- package/mcp-server/lib/options.mjs +8 -7
- package/mcp-server/lib/plan.mjs +18 -2
- package/mcp-server/lib/runs.mjs +1 -1
- package/mcp-server/lib/usage.mjs +3 -3
- package/mcp-server/lib/whatif.mjs +1 -1
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +18 -1
- package/package.json +12 -11
- package/portal-ui/dist/assets/{index-CVOIvdhc.css → index-CtvwLCti.css} +207 -3
- package/portal-ui/dist/assets/{index-5UyqAyNM.js → index-EVaSo5-g.js} +1580 -527
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/README.md +1 -1
- package/providers/_shared/enumerate.mjs +6 -6
- package/providers/_shared/execute-plan.mjs +3 -3
- package/providers/_shared/ledger.mjs +119 -5
- package/providers/_shared/provider-text.mjs +2 -2
- package/providers/_shared/screen.mjs +2 -2
- package/providers/_shared/script-form.mjs +3 -3
- package/providers/_shared/territory-codes.mjs +23 -3
- package/providers/clarivate/README.md +1 -1
- package/providers/clarivate/src/capabilities.js +12 -12
- package/providers/clarivate/src/core.js +37 -43
- package/providers/corsearch/README.md +1 -1
- package/providers/corsearch/src/capabilities.js +5 -5
- package/providers/corsearch/src/core.js +3 -3
- package/providers/jx/README.md +2 -1
- package/providers/jx/src/turn-envelope.mjs +8 -3
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/perplexity/src/core.js +1 -1
- package/providers/signa/README.md +1 -1
- package/providers/signa/src/capabilities.js +42 -49
- package/providers/signa/src/core.js +106 -29
- package/providers/uspto-local/README.md +1 -1
- package/providers/uspto-local/src/sync.js +1 -1
- package/scripts/README.md +1 -0
- package/scripts/ask-ai-render-check.mjs +127 -1
- package/scripts/authority-boundary-probe.mjs +8 -6
- package/scripts/backfill-started-at.mjs +2 -2
- package/scripts/census-merge-driver.mjs +33 -2
- package/scripts/citation-anchor-report.mjs +181 -0
- package/scripts/dead-names.mjs +1 -1
- package/scripts/deprecate-below.mjs +66 -8
- package/scripts/drain-preflight.mjs +1 -1
- package/scripts/e2e.mjs +174 -0
- package/scripts/env-audit.mjs +39 -6
- package/scripts/env-classify.mjs +20 -2
- package/scripts/freeze-example-run.mjs +61 -18
- package/scripts/generated-files-are-current.mjs +69 -4
- package/scripts/live-surface-check.mjs +124 -41
- package/scripts/markdown-link-check.mjs +1 -1
- package/scripts/merge-shape-check.mjs +242 -0
- package/scripts/mint-names-in-force.mjs +5 -5
- package/scripts/mint-offered-territories.mjs +72 -0
- package/scripts/mint-public-residue.mjs +2 -2
- package/scripts/mint-reference-strip-backlog.mjs +2 -2
- package/scripts/mint-suite-census.mjs +75 -2
- package/scripts/mint-writing-standard-backlog.mjs +2 -2
- package/scripts/purge-runs.mjs +7 -7
- package/scripts/reconcile-runs.mjs +2 -2
- package/scripts/release-approve-parked.mjs +20 -2
- package/scripts/release-await-cut.mjs +120 -1
- package/scripts/release-note-required.mjs +76 -8
- package/scripts/report-header-render-check.mjs +164 -0
- package/scripts/settings-render-check.mjs +75 -2
- package/scripts/test-full.mjs +96 -3
- package/scripts/test-run.mjs +10 -0
- package/shared/brand.mjs +27 -0
- package/shared/connect-clients.mjs +39 -11
- package/shared/deployment-box.mjs +7 -2
- package/shared/driver-dir.mjs +1 -1
- package/shared/env-aliases.mjs +1 -1
- package/shared/identifier-scan.mjs +65 -9
- package/shared/identifier-sentinels.mjs +22 -0
- package/shared/names-in-force.mjs +4 -2
- package/shared/offered-territories.json +738 -0
- package/shared/pre-rename-spellings.mjs +53 -0
- package/shared/reference-guard-classes.mjs +40 -2
- package/shared/stdio-connect.mjs +39 -4
- package/shared/tree-commit.mjs +48 -0
- /package/driver/skills/{prelim-register → clearance-register}/providers/euipo.md +0 -0
- /package/driver/skills/{prelim-register → clearance-register}/providers/free-tier.md +0 -0
- /package/driver/skills/{prelim-register → clearance-register}/providers/uspto-local.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/field-doctrine-pharma.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/firm-wide-reasoning.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/report-prose.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework-demo.manifest.json +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework-demo.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework-triage.manifest.json +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework-triage.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework.manifest.json +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/template-formatting.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/templates/email/generic.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/templates/search-request-form.html +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/worked-examples-demo.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/worked-examples.md +0 -0
package/driver/gateway.mjs
CHANGED
|
@@ -34,7 +34,7 @@ import { MODEL_FILE as BLIND_FRAME_MODEL_FILE } from "./blind-frame-record.mjs";
|
|
|
34
34
|
import { FLAGS_FILE as SKEPTIC_FLAGS_FILE } from "./skeptic-record.mjs";
|
|
35
35
|
import { MODEL_FILE as FRAME_DIFF_MODEL_FILE, PROSE_FILE as FRAME_DIFF_PROSE_FILE } from "./frame-diff-record.mjs";
|
|
36
36
|
import { MATTER_CONTEXT_FILE } from "./matter-frame-record.mjs";
|
|
37
|
-
import { MODEL_FILE as VARIANT_MODEL_FILE, PROSE_FILE as VARIANT_PROSE_FILE } from "./
|
|
37
|
+
import { MODEL_FILE as VARIANT_MODEL_FILE, PROSE_FILE as VARIANT_PROSE_FILE } from "./clearance-variants-record.mjs";
|
|
38
38
|
import { PROSE_FILE as REPORT_OVERVIEW_FILE } from "./report-overview-record.mjs";
|
|
39
39
|
import { NARRATIVE_FILE, FINDINGS_FILE, refusalsFor } from "./synthesis-record.mjs";
|
|
40
40
|
import { FINDINGS_FILE as REGISTER_FINDINGS_FILE, refusalsFor as registerDigestRefusalsFor } from "./register-digest-record.mjs";
|
|
@@ -96,8 +96,8 @@ export const TOOL_WRITTEN_ARTIFACTS = new Map([
|
|
|
96
96
|
// scope-ledger.json is deliberately ABSENT: it was already driver-written before this conversion (the
|
|
97
97
|
// driver derived it), so it is outside this conversion's claim. What changed is where its values come
|
|
98
98
|
// from, not who writes it.
|
|
99
|
-
[VARIANT_MODEL_FILE, { tool: "
|
|
100
|
-
[VARIANT_PROSE_FILE, { tool: "
|
|
99
|
+
[VARIANT_MODEL_FILE, { tool: "record_clearance_variants", what: "the variant manifest" }],
|
|
100
|
+
[VARIANT_PROSE_FILE, { tool: "record_clearance_variants", what: "the variant manifest" }],
|
|
101
101
|
// Conversion 4 — ONE basename, and the first row whose artifact a CLIENT reads. report-overview.md is
|
|
102
102
|
// the delivered report's front-matter and its Actions section; assembleReportMd splices the code-built
|
|
103
103
|
// sections into it and publishes the result. So a repair that arrives naming this file and gets handed
|
|
@@ -261,6 +261,7 @@ import { unionDispositionForm, formSidecarPath } from "./disposition-union.mjs";
|
|
|
261
261
|
import { unionCoverageForm } from "./coverage-union.mjs";
|
|
262
262
|
import { coverageFormStamp, readCoverageForm, readCoverageFormInput, writeCoverageForm } from "./coverage-form-io.mjs";
|
|
263
263
|
import { unionPlacementForm } from "./placement-union.mjs";
|
|
264
|
+
import { placementRenderAccount } from "./placement-form.mjs";
|
|
264
265
|
import { placementFormStamp, readPlacementForm, readPlacementFormInput, readSubmittedPlacementForm, writePlacementForm, renderPlacementsFile } from "./placement-form-io.mjs";
|
|
265
266
|
// The register-axis vocabulary, quoted verbatim into the coverage-form axis hint. ONE source: the same
|
|
266
267
|
// constant `rowIsSettled` refuses against, so the hint can never name a set the gate does not accept.
|
|
@@ -741,7 +742,12 @@ export function syncPlacementForm(files) {
|
|
|
741
742
|
// never put an unparseable deliverable on disk, and on a throw the previous file is left alone.
|
|
742
743
|
const r = renderPlacementsFile(runDir, u.form.rows);
|
|
743
744
|
if (!r.ok) note(`[placement-form] placements.json NOT re-rendered: ${r.error} — the previous file stands and the omission is on the form`);
|
|
744
|
-
|
|
745
|
+
// WHO OWES EACH ROW THE RENDER LEFT OUT — the same account validators.placement judges with, carried onto
|
|
746
|
+
// the attempt row so an omission is never read without its cause.
|
|
747
|
+
const a = placementRenderAccount(u.form.rows);
|
|
748
|
+
const account = { unjudged: a.unjudged.length, registerSelected: a.register_selected,
|
|
749
|
+
registerRendered: a.register_rendered, registerFacts: a.register_facts };
|
|
750
|
+
return { ...u, rendered: r.ok ? r.placements : null, render_error: r.error, account };
|
|
745
751
|
}
|
|
746
752
|
return null;
|
|
747
753
|
}
|
|
@@ -817,7 +823,7 @@ async function runStageLadder(name, opts, stageCodexHome = null) {
|
|
|
817
823
|
followup = false, // #5b: this run is a warm-resume / followup (escalation, envelope close, frame-reopen
|
|
818
824
|
// sweep) — a hard-wall timeout breaks after ONE attempt (a 1.5× extension can't fit
|
|
819
825
|
// an already-over-budget resume; the caller records the coverage-limited deferral).
|
|
820
|
-
excludeTools, bandSize, // copper-lattice re-route: tool names dropped from this stage's allowedTools
|
|
826
|
+
excludeTools, bandSize, derivedLimit = null, // copper-lattice re-route: tool names dropped from this stage's allowedTools
|
|
821
827
|
} = opts;
|
|
822
828
|
if (!message) throw new Error(`runStage(${name}): message is required`);
|
|
823
829
|
if (!sessionKey) throw new Error(`runStage(${name}): sessionKey is required`);
|
|
@@ -1145,17 +1151,17 @@ async function runStageLadder(name, opts, stageCodexHome = null) {
|
|
|
1145
1151
|
// it; the log was already saying what the code believed.
|
|
1146
1152
|
//
|
|
1147
1153
|
// TWO FIELDS, NEVER COLLAPSED INTO ONE:
|
|
1148
|
-
// modelUsed — the requested resolution
|
|
1149
|
-
//
|
|
1150
|
-
//
|
|
1151
|
-
//
|
|
1152
|
-
//
|
|
1153
|
-
//
|
|
1154
|
-
//
|
|
1155
|
-
// state in which a requested value is dressed as an observed one.
|
|
1154
|
+
// modelUsed — the requested resolution, unchanged in meaning and value: run-economics.mjs and
|
|
1155
|
+
// tokens.mjs both read it, and a field that quietly changes meaning is its own corruption.
|
|
1156
|
+
// modelActual — the id the WIRE reported (engine tuple `modelWire`), or NULL when the stream never
|
|
1157
|
+
// said: an engine that does not emit one (codex), a turn killed before any event, a
|
|
1158
|
+
// spawn error. It NEVER falls back to the requested alias, and `modelBasis` ("actual" or
|
|
1159
|
+
// "unknown") never dresses a requested value as an observed one. `providerReported` is
|
|
1160
|
+
// the provider word the same stream gave (tuple `providerWire`), null on the same terms.
|
|
1156
1161
|
const modelRequested = engine.resolveModelId ? engine.resolveModelId(model) : resolveModel(model);
|
|
1157
1162
|
const modelActual = (typeof turn.modelWire === "string" && turn.modelWire) ? turn.modelWire : null;
|
|
1158
1163
|
const modelBasis = modelActual ? "actual" : "unknown";
|
|
1164
|
+
const providerReported = (typeof turn.providerWire === "string" && turn.providerWire) ? turn.providerWire : null;
|
|
1159
1165
|
// WHETHER THE OBSERVED ID NAMES A FIXED BUILD. `modelBasis: "actual"` says the provider answered,
|
|
1160
1166
|
// not that the answer is pinned: two of the three tiers come back as undated aliases the provider
|
|
1161
1167
|
// may repoint, and recorded beside a dated one they read identically. null when there is nothing to
|
|
@@ -1171,9 +1177,13 @@ async function runStageLadder(name, opts, stageCodexHome = null) {
|
|
|
1171
1177
|
// "written before anybody asked", which is the distinction the field exists for. One spawn per
|
|
1172
1178
|
// binary per process; a probe never throws, because taking down a dispatch to record a version
|
|
1173
1179
|
// would be a worse defect than the gap it closes.
|
|
1180
|
+
// `source` says WHICH copy served (explicit / path / installed), and it is attached here, outside the
|
|
1181
|
+
// probe's cache: that cache is keyed by the file, and one file can be reached by more than one route.
|
|
1174
1182
|
const cli = (() => {
|
|
1175
|
-
try {
|
|
1176
|
-
|
|
1183
|
+
try {
|
|
1184
|
+
const pre = preflightEngineBinary(process.env);
|
|
1185
|
+
return { ...probeCliVersion(pre?.resolved ?? null), source: pre?.source ?? null };
|
|
1186
|
+
} catch (e) { return { version: null, probe: "unreadable", why: String(e?.message ?? e).slice(0, 160), source: null }; }
|
|
1177
1187
|
})();
|
|
1178
1188
|
if (modelActual) lastModelWire = modelActual; // — never overwritten with null
|
|
1179
1189
|
// The comparison is by FAMILY (driver.config modelFamily), because `--model haiku` legitimately comes
|
|
@@ -1294,7 +1304,7 @@ async function runStageLadder(name, opts, stageCodexHome = null) {
|
|
|
1294
1304
|
attempt, key, agent, model,
|
|
1295
1305
|
modelUsed: (lastModelUsed = engine.resolveModelId ? engine.resolveModelId(model) : resolveModel(model)),
|
|
1296
1306
|
// Same billing stamp as the attempt row, written on the same terms — see the note there.
|
|
1297
|
-
engine: engine.name, writeBoundary: writeBoundaryOf(engine), authMode: auth.mode, apiBilled: auth.apiBilled === true,
|
|
1307
|
+
engine: engine.name, writeBoundary: writeBoundaryOf(engine), authMode: auth.mode, apiBilled: auth.apiBilled === true, cloud: auth.cloud ?? null,
|
|
1298
1308
|
code: rt.code, wall: rt.wall, timeoutSec: effTimeout,
|
|
1299
1309
|
// — same rename as the attempt row above. This row already carries the driver's verdict as
|
|
1300
1310
|
// `repairOutcome` (only "repaired" is success), so it needs no `ok`; what it lacked was any mark
|
|
@@ -1638,8 +1648,8 @@ async function runStageLadder(name, opts, stageCodexHome = null) {
|
|
|
1638
1648
|
// modelMismatch — true/false when both sides name a family, null when either does not.
|
|
1639
1649
|
// Written even on the rows where they are null, so "this engine cannot report" stays visibly
|
|
1640
1650
|
// different from "this record predates the gauge".
|
|
1641
|
-
modelActual, modelBasis, modelSnapshot, modelMismatch,
|
|
1642
|
-
cliVersion: cli.version, cliVersionProbe: cli.probe, ...(cli.why ? { cliVersionWhy: cli.why } : {}),
|
|
1651
|
+
modelActual, modelBasis, modelSnapshot, modelMismatch, providerReported,
|
|
1652
|
+
cliVersion: cli.version, cliVersionProbe: cli.probe, ...(cli.why ? { cliVersionWhy: cli.why } : {}), cliSource: cli.source,
|
|
1643
1653
|
// W3 billing telemetry: which engine ran + the RESOLVED billing mode (subscription vs api-key). This
|
|
1644
1654
|
// records INTENT (the mode the engine was configured to bill under), not independent billing evidence
|
|
1645
1655
|
// — the actual proof is the provider console (claude's stream also reports apiKeySource; codex does
|
|
@@ -1653,7 +1663,7 @@ async function runStageLadder(name, opts, stageCodexHome = null) {
|
|
|
1653
1663
|
// "every telemetry field is written unconditionally, so 'did not happen' stays distinguishable
|
|
1654
1664
|
// from 'not recorded'" (instrumentation-house-rule.test.mjs). A run must be able to STATE that it
|
|
1655
1665
|
// billed subscription, not merely fail to state that it billed API.
|
|
1656
|
-
engine: engine.name, writeBoundary: writeBoundaryOf(engine), authMode: auth.mode, apiBilled: auth.apiBilled === true,
|
|
1666
|
+
engine: engine.name, writeBoundary: writeBoundaryOf(engine), authMode: auth.mode, apiBilled: auth.apiBilled === true, cloud: auth.cloud ?? null,
|
|
1657
1667
|
// build 2 — THIS attempt is the fresh dispatch bought by discarding a warm session that
|
|
1658
1668
|
// reproduced its own failure. Exact, not cumulative: a `warmEscalatedAt > 0` test would mark
|
|
1659
1669
|
// every later attempt too the moment the ladder is deepened, and the row would stop meaning
|
|
@@ -1695,7 +1705,7 @@ async function runStageLadder(name, opts, stageCodexHome = null) {
|
|
|
1695
1705
|
// destroyed. `unresolved` counts selections the fold does not hold. RECORDING ONLY.
|
|
1696
1706
|
placements: lastPlacementUnion ? { settled: lastPlacementUnion.settled, outstanding: lastPlacementUnion.outstanding,
|
|
1697
1707
|
carried: lastPlacementUnion.carried, total: lastPlacementUnion.total, seatRows: lastPlacementUnion.seat_rows,
|
|
1698
|
-
unresolved: lastPlacementUnion.unresolved, rendered: lastPlacementUnion.rendered } : undefined,
|
|
1708
|
+
unresolved: lastPlacementUnion.unresolved, rendered: lastPlacementUnion.rendered, account: lastPlacementUnion.account } : undefined,
|
|
1699
1709
|
//: how many in-dispatch form repairs THIS attempt bought (absent = none). Its own rows
|
|
1700
1710
|
// sit immediately above with the defect each one was dispatched to fix.
|
|
1701
1711
|
formRepairs: formRepairsThisAttempt || undefined,
|
|
@@ -1743,6 +1753,7 @@ async function runStageLadder(name, opts, stageCodexHome = null) {
|
|
|
1743
1753
|
// null (not 0, and not absent) on an engine that cannot report them, so "this adapter does not
|
|
1744
1754
|
// measure" stays visibly different from "this turn called no tools" — see toolGauge.
|
|
1745
1755
|
...toolGauge(turn), band: bandSize ?? undefined,
|
|
1756
|
+
inputBytes: derivedLimit?.inputBytes ?? undefined, derivedLimitSec: derivedLimit?.sec ?? undefined,
|
|
1746
1757
|
// AD-4 emitted-vs-landed, UNCONDITIONAL (was success-only, which made a failed attempt's mid-write
|
|
1747
1758
|
// artifact invisible): `output` = what LANDED on disk after this attempt (null when the stage has no
|
|
1748
1759
|
// expected file); `wrote` = whether THIS attempt emitted it (see the computation above the runDir
|
|
@@ -1776,8 +1787,8 @@ async function runStageLadder(name, opts, stageCodexHome = null) {
|
|
|
1776
1787
|
event: "attempt", stage: name, attempt, of: maxRetries + 1, ok: !fail, fail: fail ?? null,
|
|
1777
1788
|
//: the spine carries the same pair as the per-stage log, or the two disagree about what
|
|
1778
1789
|
// ran. `model` stays the requested resolution (its existing readers); `modelActual` is the wire.
|
|
1779
|
-
model: modelRequested, modelActual, modelBasis, modelSnapshot, modelMismatch,
|
|
1780
|
-
cliVersion: cli.version, cliVersionProbe: cli.probe, ...(cli.why ? { cliVersionWhy: cli.why } : {}),
|
|
1790
|
+
model: modelRequested, modelActual, modelBasis, modelSnapshot, modelMismatch, providerReported,
|
|
1791
|
+
cliVersion: cli.version, cliVersionProbe: cli.probe, ...(cli.why ? { cliVersionWhy: cli.why } : {}), cliSource: cli.source,
|
|
1781
1792
|
wrote, warm: warm || undefined, warmEscalated: attempt === warmEscalatedAt || undefined,
|
|
1782
1793
|
rescued: rescued ?? undefined, killed: killed || undefined,
|
|
1783
1794
|
quiescentMs: Number.isFinite(quiescentMs) ? Math.round(quiescentMs) : undefined, // — see the per-stage row
|
|
@@ -1789,7 +1800,7 @@ async function runStageLadder(name, opts, stageCodexHome = null) {
|
|
|
1789
1800
|
// archived runs actually reads, and the question "has this box ever billed API" could not be
|
|
1790
1801
|
// answered from it because the pair was only ever on the per-stage log. Written unconditionally,
|
|
1791
1802
|
// like everything else here: a subscription run states `false`.
|
|
1792
|
-
authMode: auth.mode, apiBilled: auth.apiBilled === true,
|
|
1803
|
+
authMode: auth.mode, apiBilled: auth.apiBilled === true, cloud: auth.cloud ?? null,
|
|
1793
1804
|
//: the spine carries the POINTER and the sha, not the text — enough to find the file and
|
|
1794
1805
|
// to tell two attempts apart without opening either.
|
|
1795
1806
|
dispatch: dispatch?.file ?? null, dispatchSha: dispatch?.sha ?? null,
|
|
@@ -1806,6 +1817,7 @@ async function runStageLadder(name, opts, stageCodexHome = null) {
|
|
|
1806
1817
|
wall, outputTokens: usage?.output ?? null, tokensPerSec: tokensPerSec(usage, wall),
|
|
1807
1818
|
// — the spine carries them too, or a round has to join two files to ask why a stage was slow.
|
|
1808
1819
|
...toolGauge(turn), band: bandSize ?? undefined,
|
|
1820
|
+
inputBytes: derivedLimit?.inputBytes ?? undefined, derivedLimitSec: derivedLimit?.sec ?? undefined,
|
|
1809
1821
|
formRepairs: formRepairsThisAttempt || undefined, //, see the stage row above
|
|
1810
1822
|
});
|
|
1811
1823
|
} catch { /* telemetry best-effort — never fail a turn over a journal line */ }
|
|
@@ -2082,7 +2094,7 @@ function refusalsInWindow(files, runDir, from, to) {
|
|
|
2082
2094
|
}
|
|
2083
2095
|
|
|
2084
2096
|
function rel(p) {
|
|
2085
|
-
const i = p.indexOf("/prelim-search/");
|
|
2097
|
+
const i = Math.max(p.indexOf("/clearance-search/"), p.indexOf("/prelim-search/")); // either spelling of the studio segment
|
|
2086
2098
|
return i >= 0 ? p.slice(i + 1) : p;
|
|
2087
2099
|
}
|
|
2088
2100
|
|
package/driver/jx-lanes.mjs
CHANGED
|
@@ -183,10 +183,10 @@ export function decideJxLanes({ job, profile, searchPolicy } = {}) {
|
|
|
183
183
|
*
|
|
184
184
|
* ── THE GATE THAT WAS DECIDING, AND DECIDING OFF ────────────────────────────────────────────────────
|
|
185
185
|
*
|
|
186
|
-
* This function opened with `resolvedPolicy.level !== "
|
|
186
|
+
* This function opened with `resolvedPolicy.level !== "clearance"`. `resolveSearchPolicy` now returns
|
|
187
187
|
* `level` = THE PRODUCT ID for all four searches, so that leg was false on every live run and the whole
|
|
188
188
|
* recommendation was dead — silently, with both callers (pipeline.mjs and runner.mjs) live and 3,754
|
|
189
|
-
* driver tests green, because the one test that covered it hand-fed `{ level: "
|
|
189
|
+
* driver tests green, because the one test that covered it hand-fed `{ level: "clearance" }`, a value
|
|
190
190
|
* nothing produces any more. A retired slug does not stop deciding when it is retired; it starts
|
|
191
191
|
* deciding one way.
|
|
192
192
|
*
|
|
@@ -280,6 +280,25 @@ export const jxBillingStamp = (executorSource, result = null) => {
|
|
|
280
280
|
// NO VENDOR ON THE RESULT MEANS NO DISPATCH HAPPENED. A fixture, an injected executor, or a
|
|
281
281
|
// configuration the engine door refused all return without one, and none of them is provider-billed.
|
|
282
282
|
// Saying so in its own words beats "unknown", which is indistinguishable from an unstamped legacy row.
|
|
283
|
-
if (executorSource !== "engine" || !result?.vendor) return { engine: "not-provider-billed", authMode: "not-provider-billed" };
|
|
284
|
-
return { engine: result.vendor, authMode: result.authMode ?? "not-provider-billed" };
|
|
283
|
+
if (executorSource !== "engine" || !result?.vendor) return { engine: "not-provider-billed", authMode: "not-provider-billed", cloud: null };
|
|
284
|
+
return { engine: result.vendor, authMode: result.authMode ?? "not-provider-billed", cloud: result.cloud ?? null }; // cloud: which account a cloud mode bills
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// ── What a jx ledger row says about the model ─────────────────────────────────────────────────────────
|
|
288
|
+
// The `model` on these rows is the id the program reported for the turn (jx-turn.mjs reads it off the
|
|
289
|
+
// wire), not a tier asked for, so a row that names one also carries it as `modelActual`, the name every
|
|
290
|
+
// attempt row uses for a served id. That is what the report's list of models reads; without it, a model
|
|
291
|
+
// that did only this work was left off the report.
|
|
292
|
+
//
|
|
293
|
+
// A TURN THAT RAN AND NAMED NO MODEL STILL RAN. The Claude program answered it itself, or the stream never
|
|
294
|
+
// said, or the turn was killed first: the program reports no id. Such a row used to carry neither field,
|
|
295
|
+
// and every reader took a row with no `model` for a call that was never made, so the turn's attempt and
|
|
296
|
+
// any tokens it reported fell out of the run's totals, and a run made only of such turns read as one where
|
|
297
|
+
// nothing was looked at. It now records `modelActual: null`, the stage rows' own words for "a turn ran and
|
|
298
|
+
// named no model", and tokens.mjs counts it as an attempt. Only a real dispatch writes it, by the rule
|
|
299
|
+
// jxBillingStamp states: no vendor on the result means no dispatch, so a fixture, an injected executor or
|
|
300
|
+
// a configuration the engine door refused writes neither field.
|
|
301
|
+
export const jxModelFields = (executorSource, result = null) => {
|
|
302
|
+
if (result?.model) return { model: result.model, modelActual: result.model };
|
|
303
|
+
return jxBillingStamp(executorSource, result).engine === "not-provider-billed" ? {} : { modelActual: null };
|
|
285
304
|
};
|
package/driver/jx-units.mjs
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
import { readFileSync, writeFileSync, renameSync, appendFileSync, mkdirSync, existsSync } from "node:fs";
|
|
35
35
|
import { join } from "node:path";
|
|
36
36
|
import { driverDir } from "../shared/driver-dir.mjs"; //
|
|
37
|
-
import { LANGUAGE_LANES, SERP_LANES, isMirrorHost, canonicalTerm, jxBillingStamp } from "./jx-lanes.mjs";
|
|
37
|
+
import { LANGUAGE_LANES, SERP_LANES, isMirrorHost, canonicalTerm, jxBillingStamp, jxModelFields } from "./jx-lanes.mjs";
|
|
38
38
|
import { jxKey, MAX_LANE_ATTEMPTS } from "./jx.mjs";
|
|
39
39
|
import { abbrev } from "./repair-contract.mjs";
|
|
40
40
|
import { kebab } from "./search-policy.mjs";
|
|
@@ -137,11 +137,14 @@ function foldRetryable(ctx, lane) {
|
|
|
137
137
|
// spend real tokens that no per-run total ever sees (they did, until 2026-07-28).
|
|
138
138
|
//
|
|
139
139
|
// …and the BILLING PATH rides with them — see jxBillingStamp in jx-lanes.mjs.
|
|
140
|
+
//
|
|
141
|
+
// …and so does what the turn said about the model: the id it reported, or that it ran and named none —
|
|
142
|
+
// see jxModelFields in jx-lanes.mjs.
|
|
140
143
|
function ledgerRow(runDir, row) {
|
|
141
144
|
try { appendFileSync(driverDir(runDir, "jx-completions.jsonl"), JSON.stringify(row) + "\n"); } catch { /* receipts best-effort */ }
|
|
142
145
|
}
|
|
143
146
|
|
|
144
|
-
const runPrefix = (run) => `
|
|
147
|
+
const runPrefix = (run) => `clearance-${run?.slug ?? "run"}-${run?.codename ?? "local"}-`;
|
|
145
148
|
|
|
146
149
|
// ── Executor chains (the resolveJxExecutor idiom: injected → CLEAROTRON_JX_FIXTURES → live) ─────────────
|
|
147
150
|
export function resolveSerpExecutor(opts, { mark, lane }) {
|
|
@@ -392,7 +395,7 @@ export async function runJxSerpGrid(ctx, job, opts = {}, { runLog = () => {}, no
|
|
|
392
395
|
ledgerRow(run.runDir, { ts: new Date().toISOString(), lane, mark: markName, unit: "serp-judge", executor: judgeSource,
|
|
393
396
|
...jxBillingStamp(judgeSource, jr),
|
|
394
397
|
took_ms: jr?.tookMs ?? (Date.now() - started), ok: Boolean(jr?.ok), judged: jr?.ok ? (jr.judgments?.length ?? 0) : 0,
|
|
395
|
-
...(
|
|
398
|
+
...jxModelFields(judgeSource, jr),
|
|
396
399
|
...(jr?.usage ? { usage: jr.usage } : {}), ...(jr?.ok ? {} : { cause: String(jr?.cause ?? "unknown").slice(0, 300) }) });
|
|
397
400
|
if (!jr?.ok) { judgeDegraded = String(jr?.cause ?? "unknown").slice(0, 200); break; }
|
|
398
401
|
const byId = new Map(jr.judgments.map((j) => [j.id, j]));
|
|
@@ -524,7 +527,7 @@ export async function runJxNativeread(ctx, job, opts = {}, { runLog = () => {},
|
|
|
524
527
|
ledgerRow(run.runDir, { ts: new Date().toISOString(), lane, mark: markName, unit: "nativeread", executor: source,
|
|
525
528
|
...jxBillingStamp(source, r),
|
|
526
529
|
took_ms: r?.tookMs ?? (Date.now() - started), ok: Boolean(r?.ok), items: r?.ok ? (r.items?.length ?? 0) : 0,
|
|
527
|
-
...(
|
|
530
|
+
...jxModelFields(source, r),
|
|
528
531
|
...(r?.usage ? { usage: r.usage } : {}), ...(r?.ok ? {} : { cause: String(r?.cause ?? "unknown").slice(0, 300) }) });
|
|
529
532
|
if (!r?.ok) {
|
|
530
533
|
degradeUnit(run.runDir, key, st.attempts, r?.cause ?? "unknown");
|
package/driver/jx.mjs
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { readFileSync, writeFileSync, renameSync, appendFileSync } from "node:fs";
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { driverDir } from "../shared/driver-dir.mjs"; //
|
|
17
|
-
import { decideJxLanes, candidateRefusal, canonicalTerm, romanizationSpellings, LANGUAGE_LANES, jxBillingStamp } from "./jx-lanes.mjs";
|
|
17
|
+
import { decideJxLanes, candidateRefusal, canonicalTerm, romanizationSpellings, LANGUAGE_LANES, jxBillingStamp, jxModelFields } from "./jx-lanes.mjs";
|
|
18
18
|
import { cnipaSubgroupsForClasses, cnipaEditionLabel } from "./jx-subclass.mjs"; // — replaces the hand-written seed table
|
|
19
19
|
import { kebab } from "./search-policy.mjs";
|
|
20
20
|
import { JX_PROVIDERS } from "./driver.config.mjs";
|
|
@@ -186,6 +186,13 @@ export function deriveJxSliceStatement({ sidecar, units = null, env = process.en
|
|
|
186
186
|
//
|
|
187
187
|
// PURE. Takes the parsed sidecar and the slice statement `deriveJxSliceStatement` just produced.
|
|
188
188
|
export function deriveLaneDepthVerdicts({ sidecar, slices } = {}) {
|
|
189
|
+
// EITHER SHAPE, AND THAT IS NOT POLITENESS. This reads `slices.candidates`, and the publish path handed
|
|
190
|
+
// it `deriveJxSliceStatement`'s whole return — `{executes, slices}` — so the lookup found nothing and
|
|
191
|
+
// every lane reported `ran: null` whatever it had done. Nothing threw and nothing was logged: the
|
|
192
|
+
// failure arrived as a delivered client report saying a search had not run when it had. The two shapes
|
|
193
|
+
// are told apart with certainty rather than guessed at — a statement carries a `slices` object and a
|
|
194
|
+
// slice map carries slice names — so a caller cannot pass the wrong one and be quietly misread.
|
|
195
|
+
const bySlice = slices?.slices && typeof slices.slices === "object" ? slices.slices : slices;
|
|
189
196
|
const declared = sidecar?.lanes ?? {};
|
|
190
197
|
const out = {};
|
|
191
198
|
for (const lane of Object.keys(declared)) {
|
|
@@ -194,10 +201,10 @@ export function deriveLaneDepthVerdicts({ sidecar, slices } = {}) {
|
|
|
194
201
|
// lane that gains a slice later is covered without this function being edited — and so a lane that
|
|
195
202
|
// has none is a measured fact rather than a hardcoded assumption about ja/ko.
|
|
196
203
|
const deep = JX_SLICES.filter((s) => s.slice > 1 && s.lane === lane);
|
|
197
|
-
const deepStates = deep.map((s) => ({ name: s.name, state:
|
|
204
|
+
const deepStates = deep.map((s) => ({ name: s.name, state: bySlice?.[s.name]?.state ?? null, why: bySlice?.[s.name]?.why ?? null }));
|
|
198
205
|
const deepRan = deepStates.filter((d) => d.state === "ran").map((d) => d.name);
|
|
199
206
|
// Slice 1 is the multi-lane one: its per-lane state lives in `.lanes`, never in a scalar `.state`.
|
|
200
|
-
const candidatesState =
|
|
207
|
+
const candidatesState = bySlice?.candidates?.lanes?.[lane] ?? null;
|
|
201
208
|
|
|
202
209
|
const ran = deepRan.length ? "full" : candidatesState === "ran" ? "candidates" : null;
|
|
203
210
|
const shortfall = asked === "full" && ran !== "full";
|
|
@@ -225,6 +232,27 @@ export function deriveLaneDepthVerdicts({ sidecar, slices } = {}) {
|
|
|
225
232
|
return out;
|
|
226
233
|
}
|
|
227
234
|
|
|
235
|
+
/**
|
|
236
|
+
* THE VERDICT A PUBLISHED REPORT SHOULD CARRY: the one the RUN stated, not a fresh one. PURE.
|
|
237
|
+
*
|
|
238
|
+
* `stateJxFold` writes `fold.depth` at delivery, derived from this run's own environment — which is the
|
|
239
|
+
* reason the seam exists: the arms are environment, so a verdict derived on some later box speaks for
|
|
240
|
+
* that box and not for the run. Publishing derived its own, and the two disagreed on a delivered client
|
|
241
|
+
* report: the run said the ja lane ran as a candidate lane, the publisher said no lane ran, and the page
|
|
242
|
+
* printed "Not run this run" over a search that had happened.
|
|
243
|
+
*
|
|
244
|
+
* Deriving stays, for a run delivered before the stamp existed — an old artifact still gets the best
|
|
245
|
+
* answer its record supports, rather than none.
|
|
246
|
+
*
|
|
247
|
+
* @param {object|null} sidecar parsed _driver/jx-lanes.json
|
|
248
|
+
* @param {object|null} units parsed _driver/jx/units.json, or null
|
|
249
|
+
*/
|
|
250
|
+
export function laneDepthOfRun({ sidecar, units = null, env = process.env } = {}) {
|
|
251
|
+
const stamped = sidecar?.fold?.depth;
|
|
252
|
+
if (stamped && typeof stamped === "object" && !Array.isArray(stamped)) return stamped;
|
|
253
|
+
return deriveLaneDepthVerdicts({ sidecar, slices: deriveJxSliceStatement({ sidecar, units, env }) });
|
|
254
|
+
}
|
|
255
|
+
|
|
228
256
|
const flag = (name) => ["1", "true", "yes", "on"].includes(String(process.env[name] ?? "").trim().toLowerCase());
|
|
229
257
|
// — the private copy is gone; laneArmed (driver.config.mjs) is the one reader.
|
|
230
258
|
|
|
@@ -512,7 +540,9 @@ export async function runJxCandidateFold(ctx, job, opts = {}, { runLog = () => {
|
|
|
512
540
|
const row = { ts: new Date().toISOString(), lane, mark: markName, executor: source, ...jxBillingStamp(source, r),
|
|
513
541
|
took_ms: r?.tookMs ?? (Date.now() - started), ok: Boolean(r?.ok),
|
|
514
542
|
candidates: r?.ok ? (r.candidates?.length ?? 0) : 0,
|
|
515
|
-
|
|
543
|
+
// the model the turn reported, as `model` and `modelActual`, or `modelActual: null` for a turn
|
|
544
|
+
// that ran and named none — see jxModelFields.
|
|
545
|
+
...jxModelFields(source, r),
|
|
516
546
|
...(r?.usage ? { usage: r.usage } : {}), ...(r?.ok ? {} : { cause: String(r?.cause ?? "unknown").slice(0, 300) }) };
|
|
517
547
|
try { appendFileSync(ledgerPath, JSON.stringify(row) + "\n"); } catch { /* receipts best-effort */ }
|
|
518
548
|
// Before the degraded-lane `continue` below: a lane that FAILED still ran a turn, and the model
|
|
@@ -43,6 +43,9 @@ import { captureCall, stampVerdict } from "./call-capture.mjs";
|
|
|
43
43
|
import { refuseUndeclared as refuseUndeclaredShared, lastAccepted, acceptedEnvelope } from "./preserve-merge.mjs";
|
|
44
44
|
import { knockoutVisibleProse } from "./predelivery-lint.mjs"; // the walk is the authority on what a reader sees
|
|
45
45
|
import { plainRegisterFlags } from "./plain-register.mjs"; // the pinned rule, one copy
|
|
46
|
+
// THE HINT'S fold, and it is imported rather than rebuilt — see nearestAddress. It is a comparison key
|
|
47
|
+
// for saying WHICH line was probably meant; it never decides whether an address resolves.
|
|
48
|
+
import { queryKey } from "./connotation-search.mjs";
|
|
46
49
|
|
|
47
50
|
const SCHEMA_VERSION = 1;
|
|
48
51
|
|
|
@@ -142,6 +145,42 @@ export function addressKey(at) {
|
|
|
142
145
|
return [a.field, a.mark ?? "", a.index ?? "", a.ordinal ?? ""].join(ADDRESS_SEP);
|
|
143
146
|
}
|
|
144
147
|
|
|
148
|
+
/**
|
|
149
|
+
* THE ADDRESS A SEAT PROBABLY MEANT, FOR THE REFUSAL TO NAME — and for nothing else.
|
|
150
|
+
*
|
|
151
|
+
* The lookup above stays EXACT and that is a ruling, not an oversight (owner, 2026-09-16). Punctuation
|
|
152
|
+
* can be part of a mark's identity, so two marks differing by an apostrophe form are not assumed to be
|
|
153
|
+
* one mark and a rewrite is never applied by analogy. What was wrong was the refusal, not the matching:
|
|
154
|
+
* a seat that re-typed a name with a right single quotation mark where the record has an apostrophe was
|
|
155
|
+
* told its rewrite "names no line on this record", which describes the record rather than the difference,
|
|
156
|
+
* and the rewrite was dropped with nobody able to see why. One character, invisible in most fonts.
|
|
157
|
+
*
|
|
158
|
+
* So this names the nearest and changes nothing. It folds ONLY typographic form — `queryKey`, the same
|
|
159
|
+
* key the meaning gate was folded onto after a production clearance stopped on exactly this character —
|
|
160
|
+
* and it is imported rather than copied, because two private folds are how the two gates drift apart.
|
|
161
|
+
* That key deliberately does not fold accents, so it cannot quietly propose that Café and Cafe are the
|
|
162
|
+
* same mark.
|
|
163
|
+
*
|
|
164
|
+
* It requires an EXACT match on every other part of the address — field, index, ordinal — so the hint is
|
|
165
|
+
* always the same line of the same record, differing only in how the mark's name was typed. A candidate
|
|
166
|
+
* whose mark is byte-identical is not a hint: the caller only asks when the exact lookup already failed,
|
|
167
|
+
* and pointing at the address that was just refused would say nothing.
|
|
168
|
+
*
|
|
169
|
+
* @returns {object|null} the address on this record, or null when nothing differs by form alone
|
|
170
|
+
*/
|
|
171
|
+
export function nearestAddress(at, knownAts) {
|
|
172
|
+
const want = at ?? {};
|
|
173
|
+
if (want.mark == null) return null; // no name to have mistyped
|
|
174
|
+
const same = (a, b) => (a ?? "") === (b ?? "");
|
|
175
|
+
for (const k of knownAts ?? []) {
|
|
176
|
+
if (!k || k.field !== want.field) continue;
|
|
177
|
+
if (!same(k.index, want.index) || !same(k.ordinal, want.ordinal)) continue;
|
|
178
|
+
const kn = String(k.mark ?? ""), wn = String(want.mark ?? "");
|
|
179
|
+
if (kn !== wn && queryKey(kn) === queryKey(wn)) return k;
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
145
184
|
/** Is this a well-formed address for a field the pass may reach? The reason, or null. */
|
|
146
185
|
export function addressFault(at) {
|
|
147
186
|
if (!at || typeof at !== "object" || Array.isArray(at)) return "an address must be an object";
|
|
@@ -296,15 +335,21 @@ export function validateKnockoutReviewFile(file, text) {
|
|
|
296
335
|
try { merged = JSON.parse(readFileSync(findingsFile, "utf8")); }
|
|
297
336
|
catch (e) { return { ok: false, reason: `the merged record will not parse, so no address could be checked: ${e.message}` }; }
|
|
298
337
|
|
|
299
|
-
const
|
|
338
|
+
const knownAts = knockoutVisibleProse(merged).map((v) => v.at);
|
|
339
|
+
const known = new Set(knownAts.map((a) => addressKey(a)));
|
|
300
340
|
const owned = new Set(knockoutVisibleProse(merged).filter((v) => v.at?.engineOwned).map((v) => addressKey(v.at)));
|
|
341
|
+
// Appended to a refusal, never consulted before one: the address still has to match exactly.
|
|
342
|
+
const hint = (at) => {
|
|
343
|
+
const n = nearestAddress(at, knownAts);
|
|
344
|
+
return n ? ` — this record carries "${n.mark}" at that line, which differs from "${at.mark}" only in how it is typed; the two are not assumed to be the same mark, so re-send the rewrite naming the record's spelling` : "";
|
|
345
|
+
};
|
|
301
346
|
for (const r of doc.rewrites ?? []) {
|
|
302
347
|
const key = addressKey(r.at);
|
|
303
348
|
if (owned.has(key)) return { ok: false, reason: `the rewrite for ${r.at.field} names a caveat the engine wrote, not a seat — it is not offered and cannot be changed` };
|
|
304
|
-
if (!known.has(key)) return { ok: false, reason: `the rewrite for ${r.at.field}${r.at.mark ? ` on "${r.at.mark}"` : ""} names no line on this record` };
|
|
349
|
+
if (!known.has(key)) return { ok: false, reason: `the rewrite for ${r.at.field}${r.at.mark ? ` on "${r.at.mark}"` : ""} names no line on this record${hint(r.at)}` };
|
|
305
350
|
}
|
|
306
351
|
for (const d of doc.declined ?? []) {
|
|
307
|
-
if (!known.has(addressKey(d.at))) return { ok: false, reason: `the declined row for ${d.at.field}${d.at.mark ? ` on "${d.at.mark}"` : ""} names no line on this record` };
|
|
352
|
+
if (!known.has(addressKey(d.at))) return { ok: false, reason: `the declined row for ${d.at.field}${d.at.mark ? ` on "${d.at.mark}"` : ""} names no line on this record${hint(d.at)}` };
|
|
308
353
|
}
|
|
309
354
|
return { ok: true };
|
|
310
355
|
}
|
|
@@ -340,7 +385,14 @@ export function applyKnockoutReview(merged, review) {
|
|
|
340
385
|
for (const r of review?.rewrites ?? []) {
|
|
341
386
|
const key = addressKey(r.at);
|
|
342
387
|
const seen = byKey.get(key);
|
|
343
|
-
if (!seen) {
|
|
388
|
+
if (!seen) {
|
|
389
|
+
// Same hint as the validator's, for the same reason: the apply path is reached on a record this
|
|
390
|
+
// pass did not validate, and an unresolved row that cannot say WHY is the state that cost a
|
|
391
|
+
// rewrite silently. It still changes nothing — `unresolved` is a report.
|
|
392
|
+
const near = nearestAddress(r.at, [...byKey.values()].map((v) => v.at));
|
|
393
|
+
unresolved.push({ at: r.at, why: near ? `no such line on this record — it carries "${near.mark}" there, differing from "${r.at.mark}" only in how it is typed` : "no such line on this record" });
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
344
396
|
if (seen.at?.engineOwned) { refused.push({ at: r.at, why: "the engine wrote this caveat, not a seat" }); continue; }
|
|
345
397
|
const at = r.at, text = String(r.text);
|
|
346
398
|
// THE WALK IS THE BOUNDS CHECK. `seen` came from this same record, so a resolved address names a
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// (copper-causeway could not see teal-conduit's VENERET: different noref slugs for the same VENZY).
|
|
9
9
|
// The store therefore lives one level up, keyed by the MARK:
|
|
10
10
|
//
|
|
11
|
-
// <studioRoot>/_known-conflicts/<kebab(mark)>.json (studioRoot = workspace-<agent>/studio/
|
|
11
|
+
// <studioRoot>/_known-conflicts/<kebab(mark)>.json (studioRoot = workspace-<agent>/studio/clearance-search)
|
|
12
12
|
//
|
|
13
13
|
// One file per searched mark name; workspace-per-agent keeps customers separated. Each file keeps the
|
|
14
14
|
// EXACT inner shape the tripwire already reads ({schema_version, marks:{"<mark key>":[rows]}}), so
|
|
@@ -141,6 +141,16 @@ export function renderMatterFrame(model) {
|
|
|
141
141
|
// is owed against — so the frame states it where a reader can see it rather than only in a field.
|
|
142
142
|
if ((model.ratified_forms ?? []).length > 1)
|
|
143
143
|
out.push(`- **Ratified forms:** ${model.ratified_forms.join(", ")}`);
|
|
144
|
+
// THE EXCLUSION IS EVIDENCED ON THE DOCUMENT A READER SEES, and it is stated as a PROPOSAL because
|
|
145
|
+
// that is what it is at this point in the run. A reader meeting "excluded" here would believe the
|
|
146
|
+
// search was narrowed on the frame's authority; the driver's verification has not run yet, and if it
|
|
147
|
+
// cannot confirm the client's ownership the element is searched in full and this line is the only
|
|
148
|
+
// place the question was ever raised.
|
|
149
|
+
if (model.house_element_candidate)
|
|
150
|
+
out.push(`- **Client's own element, proposed for exclusion:** ${model.house_element_candidate.element}`
|
|
151
|
+
+ ` — the analysis would be limited to ${model.house_element_candidate.remainder}.`
|
|
152
|
+
+ ` Basis: ${model.house_element_candidate.owner_basis}.`
|
|
153
|
+
+ " Excluded only if the driver confirms the client's own registrations on the register; otherwise searched in full.");
|
|
144
154
|
out.push("");
|
|
145
155
|
|
|
146
156
|
// `Search channels:` — domains only; the grid site-restricts to them and the general web is always
|
|
@@ -172,9 +182,10 @@ export function renderMatterFrame(model) {
|
|
|
172
182
|
*/
|
|
173
183
|
/** The shape this tool declares, at every depth — what the ACCEPTOR enforces. */
|
|
174
184
|
const DECLARED = Object.freeze({
|
|
175
|
-
"": ["prose_body", "scope_basis", "scope_jurisdictions", "excluded_jurisdictions", "search_channels", "meaning_angles", "meaning_angles_none", "intake_asks", "identified_classes", "ratified_forms"],
|
|
185
|
+
"": ["prose_body", "scope_basis", "scope_jurisdictions", "excluded_jurisdictions", "search_channels", "meaning_angles", "meaning_angles_none", "intake_asks", "identified_classes", "ratified_forms", "house_element_candidate"],
|
|
176
186
|
intake_asks: ["ask", "owner"],
|
|
177
187
|
identified_classes: ["class", "reason"],
|
|
188
|
+
house_element_candidate: ["element", "remainder", "owner_basis"],
|
|
178
189
|
});
|
|
179
190
|
|
|
180
191
|
/** Refuse an undeclared key by path, at depth. Shared walk; the table above is what is this tool's. */
|
|
@@ -218,6 +229,26 @@ export function frameRatifiedForms(runDir) {
|
|
|
218
229
|
return (Array.isArray(rows) ? rows : []).map((r) => String(r ?? "").trim()).filter(Boolean);
|
|
219
230
|
}
|
|
220
231
|
|
|
232
|
+
/**
|
|
233
|
+
* The house element this run's frame PROPOSED, or null. IMPURE (reads the run's own accepted call).
|
|
234
|
+
*
|
|
235
|
+
* A PROPOSAL, AND THE CALLER MUST TREAT IT AS ONE. Nothing here has been checked against the register:
|
|
236
|
+
* the frame runs before the plan, holds no band tool, and is reporting how it reads the matter. The
|
|
237
|
+
* caller verifies ownership by owner-scoped lookup and writes its own receipt; the plan excludes on
|
|
238
|
+
* that receipt. A caller that excluded on this return would be dropping an element from the search on a
|
|
239
|
+
* model's say-so, which is the one direction that reaches a client as a clean answer over unswept
|
|
240
|
+
* ground rather than as a visible failure.
|
|
241
|
+
*
|
|
242
|
+
* Null on every archived and replayed run whose accepted call predates the field, so none of them moves.
|
|
243
|
+
*/
|
|
244
|
+
export function frameHouseElementCandidate(runDir) {
|
|
245
|
+
const h = lastAcceptedMatterFrame(runDir)?.house_element_candidate;
|
|
246
|
+
const element = String(h?.element ?? "").trim();
|
|
247
|
+
const remainder = String(h?.remainder ?? "").trim();
|
|
248
|
+
if (!element || !remainder) return null;
|
|
249
|
+
return { element, remainder, owner_basis: String(h?.owner_basis ?? "").trim() };
|
|
250
|
+
}
|
|
251
|
+
|
|
221
252
|
/** The last ACCEPTED call for this run, or null. */
|
|
222
253
|
export function lastAcceptedMatterFrame(runDir) {
|
|
223
254
|
return lastAccepted(matterFrameCallPaths(String(runDir ?? "")).accepted, readFileSync);
|
|
@@ -252,6 +283,15 @@ export function mergeMatterFrameCall(stored, received) {
|
|
|
252
283
|
// omission here is a repair that did not mention them, never a decision to withdraw them.
|
|
253
284
|
identified_classes: keepIfAbsent(received?.identified_classes, base.identified_classes),
|
|
254
285
|
ratified_forms: keepIfAbsent(received?.ratified_forms, base.ratified_forms),
|
|
286
|
+
// KEEP-IF-ABSENT, and the direction of its failure is the OPPOSITE of the two above — which is
|
|
287
|
+
// worth saying, because the reasoning that protects them does not transfer and a reader who assumed
|
|
288
|
+
// it did would mis-rank this key. Dropping the identified classes NARROWS the next compile, towards
|
|
289
|
+
// missing rights. Dropping this one WIDENS it: the house element goes back to being searched as a
|
|
290
|
+
// conflict axis, which is the band this field exists to shrink, so a partial call that lost it costs
|
|
291
|
+
// a slower run and the report's one sentence explaining what was excluded and why — never coverage.
|
|
292
|
+
// It is kept because a repair turn that did not mention the element is not a withdrawal of it, which
|
|
293
|
+
// is the same rule, reached by a different road.
|
|
294
|
+
house_element_candidate: keepIfAbsent(received?.house_element_candidate, base.house_element_candidate),
|
|
255
295
|
};
|
|
256
296
|
}
|
|
257
297
|
|
|
@@ -346,6 +386,54 @@ export function acceptMatterFrame(params, { instructedScope = null } = {}) {
|
|
|
346
386
|
ratified_forms.push(form);
|
|
347
387
|
}
|
|
348
388
|
|
|
389
|
+
// ── THE CLIENT'S OWN HOUSE ELEMENT — A CANDIDATE, NEVER A DECISION ───────────────────────────────
|
|
390
|
+
//
|
|
391
|
+
// THE DEFECT (production run, 2026-09-16). The mark was the client's own famous house mark followed by
|
|
392
|
+
// a tagline, and the plan treated the house element as a conflict axis: exact, variants, one-letter
|
|
393
|
+
// mutations, transliterations, incumbent checks. Over half the band came from that element. The
|
|
394
|
+
// reviewing lawyer's method for the same matter was three queries — the whole phrase, the shorter
|
|
395
|
+
// phrase, the last word alone — because an element the client already owns outright is not what the
|
|
396
|
+
// analysis is about. The engine planned thirty-six.
|
|
397
|
+
//
|
|
398
|
+
// WHY THE FIELD IS NAMED `candidate`, AND WHY THE NAME IS LOAD-BEARING. This frame CANNOT verify
|
|
399
|
+
// ownership: `BAND_READING_STAGES` is placement-inquiry, register-digest and synthesis, and the band
|
|
400
|
+
// does not exist yet when the frame runs. So everything here is the seat's reading of the matter, and
|
|
401
|
+
// an exclusion taken on a seat's say-so is an unsearched element justified by an assertion — a clean
|
|
402
|
+
// report over ground nobody swept, which is the one failure that reaches a client as a wrong answer
|
|
403
|
+
// rather than as no answer. The driver verifies against the register by owner and writes the receipt;
|
|
404
|
+
// the plan excludes on the RECEIPT and never on this field. Requirement 4 ("when the frame cannot
|
|
405
|
+
// verify, it does not exclude, and says so") is then the write order rather than a branch someone has
|
|
406
|
+
// to remember: no receipt, no exclusion.
|
|
407
|
+
//
|
|
408
|
+
// TYPED RATHER THAN PARSED, for the reason `identified_classes` gives and measures: a list derived
|
|
409
|
+
// from judgment prose dropped the primary entry in 19 of 21 runs.
|
|
410
|
+
let house_element_candidate = null;
|
|
411
|
+
if (params?.house_element_candidate !== undefined && params?.house_element_candidate !== null) {
|
|
412
|
+
const h = params.house_element_candidate;
|
|
413
|
+
const element = str(h?.element), remainder = str(h?.remainder), owner_basis = str(h?.owner_basis);
|
|
414
|
+
if (!element)
|
|
415
|
+
return { ok: false, reason: "matterframe_house_element_empty: name the element of the mark the client already owns, or omit the field entirely — a blank row is not an answer" };
|
|
416
|
+
if (!owner_basis)
|
|
417
|
+
return { ok: false, reason: `matterframe_house_element_basis_missing:${element} — say why you read this as the client's own registered element. It is not taken on your word (the driver verifies it against the register by owner), but the reader of the report is owed the ground, and an unverifiable basis is how a wrong exclusion would be argued for` };
|
|
418
|
+
// ── THE FLOOR, AND IT IS ON THE POPULATION RATHER THAN ON THE RULE ────────────────────────────
|
|
419
|
+
//
|
|
420
|
+
// The catastrophic direction here is naming too MUCH as the house element: mark "ACME WIDGETS",
|
|
421
|
+
// element "ACME WIDGETS", remainder nothing — and the plan becomes three queries that do not exist.
|
|
422
|
+
// Ownership can verify perfectly in that case, so requirement 4 does not catch it and no refusal
|
|
423
|
+
// downstream would either: "queries on the house element: 0" is satisfied by a plan with no queries
|
|
424
|
+
// at all. So the remainder is checked for being something a search can be built on, here, where the
|
|
425
|
+
// claim is made.
|
|
426
|
+
if (!remainder)
|
|
427
|
+
return { ok: false, reason: `matterframe_house_element_no_remainder:${element} — excluding it would leave nothing to search. The remainder is what the analysis is about; if the mark IS the client's own element with nothing distinctive after it, there is no exclusion to make and the field is omitted` };
|
|
428
|
+
if (remainder.toLowerCase() === element.toLowerCase())
|
|
429
|
+
return { ok: false, reason: `matterframe_house_element_remainder_same:${element} — the remainder must be the part of the mark that is NOT the house element` };
|
|
430
|
+
// The whole mark cannot be the house element by another spelling: an element that swallows the
|
|
431
|
+
// remainder leaves the same empty plan, arriving as two fields that merely look different.
|
|
432
|
+
if (element.toLowerCase().includes(remainder.toLowerCase()))
|
|
433
|
+
return { ok: false, reason: `matterframe_house_element_swallows_remainder:${element} — the element you named contains the remainder, so excluding it excludes the whole mark` };
|
|
434
|
+
house_element_candidate = { element, remainder, owner_basis };
|
|
435
|
+
}
|
|
436
|
+
|
|
349
437
|
const model = {
|
|
350
438
|
schema_version: SCHEMA_VERSION,
|
|
351
439
|
instructed_scope: instructedScope ?? null,
|
|
@@ -358,6 +446,7 @@ export function acceptMatterFrame(params, { instructedScope = null } = {}) {
|
|
|
358
446
|
intake_asks,
|
|
359
447
|
identified_classes,
|
|
360
448
|
ratified_forms,
|
|
449
|
+
house_element_candidate,
|
|
361
450
|
};
|
|
362
451
|
return { ok: true, model, content: renderMatterFrame(model) };
|
|
363
452
|
}
|
package/driver/named-band.mjs
CHANGED
|
@@ -137,7 +137,7 @@ export function parseNamedBand(raw) {
|
|
|
137
137
|
// byte-identical to a slice the plan deliberately counted without fetching. Measured on a real
|
|
138
138
|
// run: four capability-gap blocks carried `error:true, deferred:true` into this function and
|
|
139
139
|
// reached record-carry.json with both fields gone and a sentence claiming the run "has a hit
|
|
140
|
-
// COUNT for this slice". register-plan.mjs:
|
|
140
|
+
// COUNT for this slice". register-plan.mjs:1594 validatePlanFeasibility already enforces the same rule one layer up
|
|
141
141
|
// ("a transient must not ship indistinguishable from a sanctioned descriptor") — it reads the
|
|
142
142
|
// RAW blocks, which is why it could. Every consumer that reads THIS projection could not.
|
|
143
143
|
// Conditional like the four keys above, so old bands carry neither key and nothing shifts.
|