clearotron 0.3.2-beta.8 → 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 +34 -3
- package/CONTRIBUTING.md +8 -8
- package/INSTALL.md +6 -6
- package/SECURITY.md +3 -3
- package/bin/brandowner.mjs +3 -3
- package/bin/framework-preflight.mjs +1 -1
- package/bin/start.mjs +18 -4
- 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 +6 -3
- package/docs/architecture/05-config-governance.md +6 -1
- 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 +48 -0
- package/driver/README.md +3 -3
- 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/consumption-ledger.mjs +2 -2
- package/driver/contract-arm2-baseline.json +2 -3
- package/driver/contract-dictation-registry.mjs +19 -19
- package/driver/contract-e3-backlog.mjs +19 -19
- package/driver/contract-e3-baseline.json +14 -14
- package/driver/contract-vocabulary.mjs +24 -17
- 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/doubt-ledger.mjs +2 -2
- package/driver/drainer-identity.mjs +34 -8
- package/driver/driver.config.mjs +95 -45
- 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 +5 -5
- package/driver/enqueue-schema.mjs +6 -2
- package/driver/findings-model.mjs +5 -2
- package/driver/flag-snapshot.mjs +6 -3
- package/driver/form-neighbourhood.mjs +54 -7
- package/driver/framework.mjs +4 -4
- package/driver/gateway.mjs +12 -6
- package/driver/jx-lanes.mjs +2 -2
- package/driver/jx-units.mjs +1 -1
- package/driver/jx.mjs +30 -2
- package/driver/knockout-review-record.mjs +56 -4
- package/driver/known-conflicts.mjs +1 -1
- package/driver/named-band.mjs +1 -33
- 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 +206 -68
- package/driver/placement-form.mjs +77 -1
- package/driver/placement-model.mjs +1 -1
- package/driver/portal-report.mjs +92 -5
- package/driver/portal-service.mjs +34 -8
- package/driver/portal-upstream.mjs +1 -1
- 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 +110 -5
- package/driver/publish/knockout.mjs +29 -4
- package/driver/publish/pool-admin.mjs +1 -1
- package/driver/publish/publish-inputs.mjs +18 -2
- package/driver/publish/render-knockout.mjs +115 -24
- package/driver/publish/render.mjs +153 -34
- package/driver/publish/search-depth.mjs +133 -4
- package/driver/publish/templates/report.css +60 -3
- package/driver/publish/xlsx.mjs +8 -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-count.mjs +50 -5
- package/driver/register-coverage.mjs +67 -0
- package/driver/register-grant-vocabulary.mjs +1 -1
- package/driver/register-plan.mjs +19 -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/run-requirements.mjs +3 -3
- package/driver/runner.mjs +2 -2
- 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 +53 -53
- package/driver/status-snapshot.mjs +2 -2
- package/driver/suite-census.json +218 -92
- package/driver/surface-exit-verdict.mjs +58 -0
- package/driver/systemd/README.md +2 -2
- package/driver/systemd/clearotron-worker.service +1 -1
- package/driver/terminal-clamp.mjs +2 -0
- 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 +67 -6
- package/driver/whatif-queue.mjs +1 -1
- package/driver/wordlists/en.txt +63906 -0
- package/mcp-server/CHANGELOG.md +4 -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 +3 -0
- package/package.json +12 -11
- package/portal-ui/dist/assets/{index-CVOIvdhc.css → index-CtvwLCti.css} +207 -3
- package/portal-ui/dist/assets/{index-6jzO9HiX.js → index-EVaSo5-g.js} +1459 -482
- 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/oauth-mcp-bridge/CHANGELOG.md +4 -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/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 +4 -4
- 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 +27 -0
- package/scripts/env-classify.mjs +20 -2
- package/scripts/freeze-example-run.mjs +20 -10
- 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/shared/brand.mjs +27 -0
- package/shared/connect-clients.mjs +39 -11
- 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 +3 -1
- 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
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
-->
|
|
50
50
|
<link rel="preconnect" href="https://api.fontshare.com" crossorigin />
|
|
51
51
|
<link href="https://api.fontshare.com/v2/css?f[]=satoshi@400,500,700,900&display=swap" rel="stylesheet" />
|
|
52
|
-
<script type="module" crossorigin src="/portal/assets/index-
|
|
53
|
-
<link rel="stylesheet" crossorigin href="/portal/assets/index-
|
|
52
|
+
<script type="module" crossorigin src="/portal/assets/index-EVaSo5-g.js"></script>
|
|
53
|
+
<link rel="stylesheet" crossorigin href="/portal/assets/index-CtvwLCti.css">
|
|
54
54
|
</head>
|
|
55
55
|
<body>
|
|
56
56
|
<div id="root"></div>
|
package/portal-ui/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "portal-ui",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.3.2-beta.
|
|
5
|
+
"version": "0.3.2-beta.9",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
7
7
|
"description": "The unified trademark portal UI. One address, one login: who you are decides what you see. Built as a static bundle, served by driver/portal-service.mjs — the browser never reaches profile-service or recipe-service.",
|
|
8
8
|
"engines": {
|
package/providers/README.md
CHANGED
|
@@ -141,7 +141,7 @@ Case-law setup is an OAuth flow, **not** an environment variable — see
|
|
|
141
141
|
|
|
142
142
|
## Adding or changing a register adapter
|
|
143
143
|
|
|
144
|
-
Read [`../driver/skills/
|
|
144
|
+
Read [`../driver/skills/clearance-register/providers/README.md`](../driver/skills/clearance-register/providers/README.md)
|
|
145
145
|
first — it carries the mandatory empirical-verification checklist. The short version: **never inherit a
|
|
146
146
|
capability claim by analogy from another provider.** Every operator, filter, pagination shape and
|
|
147
147
|
composition rule is probed against the live API before it is written down, and the observed figure is
|
|
@@ -60,8 +60,8 @@ import { clipProviderText } from "./provider-text.mjs"; // — keep the discri
|
|
|
60
60
|
|
|
61
61
|
// ── — HOW MUCH OF A PROVIDER ERROR SURVIVES INTO THE BAND BLOCK ───────────────────────────────
|
|
62
62
|
//
|
|
63
|
-
// Both were 140, and 140 is where the defect lived:
|
|
64
|
-
// and its verdict — `are not allowed` — is the last two words. Cut at 140 it arrived
|
|
63
|
+
// Both were 140, and 140 is where the defect lived: one register's Near/Adj refusal runs just past that
|
|
64
|
+
// bound and its verdict — `are not allowed` — is the last two words. Cut at 140 it arrived truncated,
|
|
65
65
|
// That structural predicate could not match, and a refusal that recurs byte-identically forever was
|
|
66
66
|
// filed as weather and retried on every future run of that shape.
|
|
67
67
|
//
|
|
@@ -299,9 +299,9 @@ export function makeEnumerate(deps) {
|
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
// ── count-first per-CLASS rescue ──────────────────────────────────────────────────────────────────
|
|
302
|
-
// The per-term rescue's exact sibling, on the OTHER axis a stack can crowd along. A multi-class
|
|
303
|
-
//
|
|
304
|
-
//
|
|
302
|
+
// The per-term rescue's exact sibling, on the OTHER axis a stack can crowd along. A multi-class owner
|
|
303
|
+
// query crowded over the ceiling and shipped as one blind count — a single unopened class leg became
|
|
304
|
+
// the whole of the residual risk story — when per-class counts would have made
|
|
305
305
|
// EVERY leg individually enumerable. So: an OWNER-SCOPED query (a bare-owner sweep or an owner×term
|
|
306
306
|
// slice — the only shapes whose crowds are portfolio-shaped rather than name-shaped) that crowds over
|
|
307
307
|
// the ceiling across >1 class is counted per class with the SAME shared count kernel, and every
|
|
@@ -371,7 +371,7 @@ export function makeEnumerate(deps) {
|
|
|
371
371
|
// A provider with no total anywhere cannot run a count-first rescue — there is nothing to count.
|
|
372
372
|
const countFirst = countProbe !== "none";
|
|
373
373
|
// The per-class rescue's trigger shape: an owner-scoped query (bare-owner sweep or owner×term
|
|
374
|
-
// slice) spanning >1 class — the portfolio-shaped crowd
|
|
374
|
+
// slice) spanning >1 class — the portfolio-shaped crowd a single blind count dies as. The per-term rescue
|
|
375
375
|
// keeps precedence on multi-name stacks (its accounting is the finer truth there).
|
|
376
376
|
const ownerScoped = isOwnerScoped(params);
|
|
377
377
|
const splitClasses = ownerScoped
|
|
@@ -172,9 +172,9 @@ export const unresolvedOwnerCountReason = (term, coveredBy) =>
|
|
|
172
172
|
*
|
|
173
173
|
* WHAT IT NO LONGER CLAIMS. It used to say "this owner is answered record-by-record by the owner×term
|
|
174
174
|
* slice(s) …". `covered_by` is stamped at PLAN COMPILE time and names the slices that were DICTATED for
|
|
175
|
-
* this owner — it says nothing about whether they enumerated. Measured across a delivered round
|
|
176
|
-
* for
|
|
177
|
-
* was asserting coverage it is not in a position to observe
|
|
175
|
+
* this owner — it says nothing about whether they enumerated. Measured across a delivered round, it was
|
|
176
|
+
* true for a minority of owners and false for the rest, and the hits it covered were the largest single
|
|
177
|
+
* class in the artifact. The descriptor was asserting coverage it is not in a position to observe.
|
|
178
178
|
*
|
|
179
179
|
* The pointer is the part worth keeping, so it stays and the CLAIM around it goes: these are where the
|
|
180
180
|
* records were sought, and their own state is what says whether they were found. PURE.
|
|
@@ -29,9 +29,10 @@
|
|
|
29
29
|
//
|
|
30
30
|
// Telemetry is fully isolated in try/catch: a ledger failure must NEVER affect a search.
|
|
31
31
|
|
|
32
|
-
import { appendFileSync, mkdirSync } from "node:fs";
|
|
33
|
-
import { dirname } from "node:path";
|
|
34
|
-
import {
|
|
32
|
+
import { appendFileSync, mkdirSync, statSync, openSync, readSync, closeSync, readFileSync, writeFileSync, renameSync, rmdirSync } from "node:fs";
|
|
33
|
+
import { dirname, basename } from "node:path";
|
|
34
|
+
import { createHash } from "node:crypto";
|
|
35
|
+
import { ledgerPath, RUN_RECORD_LOG_FILE } from "./ledger-path.mjs";
|
|
35
36
|
|
|
36
37
|
export const CALL_LOG_PATH = ledgerPath("call");
|
|
37
38
|
// Spec A1 (citation fidelity): every fetched record's BODY is persisted alongside the call ledger, so the
|
|
@@ -97,7 +98,7 @@ export function makeLedger(provider) {
|
|
|
97
98
|
// written into the row.
|
|
98
99
|
const dest = typeof tctx?.recordLog === "string" && tctx.recordLog.trim()
|
|
99
100
|
? tctx.recordLog.trim() : RECORD_LOG_PATH;
|
|
100
|
-
|
|
101
|
+
const row = {
|
|
101
102
|
ts: new Date().toISOString(),
|
|
102
103
|
provider,
|
|
103
104
|
agentId: tctx?.agentId ?? null,
|
|
@@ -105,8 +106,121 @@ export function makeLedger(provider) {
|
|
|
105
106
|
sessionId: tctx?.sessionId ?? null,
|
|
106
107
|
target,
|
|
107
108
|
body,
|
|
108
|
-
}
|
|
109
|
+
};
|
|
110
|
+
if (basename(dest) === RUN_RECORD_LOG_FILE) writeRecordOnce(dest, row);
|
|
111
|
+
else append(dest, JSON.stringify(row));
|
|
109
112
|
} catch { /* record persistence must never break a search */ }
|
|
110
113
|
};
|
|
111
114
|
return { logCall, logRecordBody, tctxOf };
|
|
112
115
|
}
|
|
116
|
+
|
|
117
|
+
// ── EACH REGISTER RECORD ONCE PER RUN ─────────────────────────────────────────────────────────────────
|
|
118
|
+
//
|
|
119
|
+
// A record that several queries return — an OR-list, a compound, the mark itself — was appended once per
|
|
120
|
+
// query. Measured on a delivered four-letter run: 2,898 lines for 2,098 distinct records, 800 of them a
|
|
121
|
+
// record already in the file, 21.6 MB of a 106 MB ledger, and the band server reads the whole file on a
|
|
122
|
+
// cache miss. 786 of the 800 were byte-identical bodies.
|
|
123
|
+
//
|
|
124
|
+
// So a body for a record already written is not appended. A body that DIFFERS from the one written (a
|
|
125
|
+
// status moved between two queries) replaces it ONCE, in place, and the replacement says so: `refreshed:
|
|
126
|
+
// true` and the timestamp of the body it replaced. Once only, so a field that varies between answers
|
|
127
|
+
// cannot rewrite the file on every query. Every field stays, the vendor's raw copy included.
|
|
128
|
+
//
|
|
129
|
+
// SEVERAL PROCESSES WRITE ONE RUN'S LEDGER — the register servers of parallel stages and the driver's own
|
|
130
|
+
// fetches — so "already written" is read off the FILE, never off this process's memory alone: each write
|
|
131
|
+
// indexes whatever was appended since this process last looked. Writes take a lock directory beside the
|
|
132
|
+
// file, so an in-place replacement (written to a temporary file and renamed over, which readers see
|
|
133
|
+
// atomically) cannot drop a line another process was appending. A lock that cannot be had in time falls
|
|
134
|
+
// back to a plain append: a duplicate line is the old behaviour, a lost record is not acceptable.
|
|
135
|
+
//
|
|
136
|
+
// Run-scoped ledgers only. The box-wide fallback file holds many runs, and a record one run fetched is not
|
|
137
|
+
// a record another run holds.
|
|
138
|
+
const RUN_INDEXES = new Map(); // dest → { offset, byTarget: Map<key, { hash, refreshed, ts }> }
|
|
139
|
+
const targetKey = (t) => String(t ?? "").toLowerCase(); // the key every reader of this file matches on
|
|
140
|
+
const bodyHash = (b) => createHash("sha1").update(JSON.stringify(b ?? null)).digest("hex");
|
|
141
|
+
|
|
142
|
+
function indexRow(ix, line) {
|
|
143
|
+
if (!line.trim()) return;
|
|
144
|
+
try {
|
|
145
|
+
const r = JSON.parse(line);
|
|
146
|
+
ix.byTarget.set(targetKey(r?.target), { hash: bodyHash(r?.body), refreshed: r?.refreshed === true, ts: r?.ts ?? null });
|
|
147
|
+
} catch { /* a torn or foreign line indexes nothing, and is left where it is */ }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Index every whole line appended since this process last looked; a file that shrank is re-read. */
|
|
151
|
+
function indexOf(dest) {
|
|
152
|
+
let size = 0, ino = null;
|
|
153
|
+
try { const st = statSync(dest); size = st.size; ino = st.ino; } catch { size = 0; }
|
|
154
|
+
let ix = RUN_INDEXES.get(dest);
|
|
155
|
+
// A replacement elsewhere renames a NEW file over this one: same path, different inode, and an offset
|
|
156
|
+
// into the old file means nothing in the new one. Re-read from the start.
|
|
157
|
+
if (!ix || size < ix.offset || ix.ino !== ino) { ix = { offset: 0, ino, byTarget: new Map() }; RUN_INDEXES.set(dest, ix); }
|
|
158
|
+
if (size <= ix.offset) return ix;
|
|
159
|
+
const fd = openSync(dest, "r");
|
|
160
|
+
try {
|
|
161
|
+
const CHUNK = 8 * 1024 * 1024;
|
|
162
|
+
let carry = Buffer.alloc(0);
|
|
163
|
+
let pos = ix.offset;
|
|
164
|
+
while (pos < size) {
|
|
165
|
+
const buf = Buffer.alloc(Math.min(CHUNK, size - pos));
|
|
166
|
+
const n = readSync(fd, buf, 0, buf.length, pos);
|
|
167
|
+
if (n <= 0) break;
|
|
168
|
+
pos += n;
|
|
169
|
+
const data = carry.length ? Buffer.concat([carry, buf.subarray(0, n)]) : buf.subarray(0, n);
|
|
170
|
+
const last = data.lastIndexOf(0x0a);
|
|
171
|
+
if (last < 0) { carry = data; continue; }
|
|
172
|
+
for (const line of data.subarray(0, last).toString("utf8").split("\n")) indexRow(ix, line);
|
|
173
|
+
carry = data.subarray(last + 1); // a line still being written waits for the next look
|
|
174
|
+
ix.offset = pos - carry.length;
|
|
175
|
+
}
|
|
176
|
+
} finally { closeSync(fd); }
|
|
177
|
+
return ix;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const sleepMs = (ms) => { try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); } catch { /* no sleep available */ } };
|
|
181
|
+
|
|
182
|
+
/** Run `fn` holding the ledger's lock; false (and `fn` not run) when the lock could not be had in time. */
|
|
183
|
+
function withLedgerLock(dest, fn) {
|
|
184
|
+
const lock = `${dest}.lock`;
|
|
185
|
+
const deadline = Date.now() + 3000;
|
|
186
|
+
for (;;) {
|
|
187
|
+
try { mkdirSync(dirname(dest), { recursive: true }); mkdirSync(lock); break; }
|
|
188
|
+
catch {
|
|
189
|
+
// A holder that died leaves the directory behind; nothing holds this lock for more than a rewrite.
|
|
190
|
+
try { if (Date.now() - statSync(lock).mtimeMs > 30_000) { rmdirSync(lock); continue; } } catch { continue; }
|
|
191
|
+
if (Date.now() > deadline) return false;
|
|
192
|
+
sleepMs(10);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
try { fn(); } finally { try { rmdirSync(lock); } catch { /* already gone */ } }
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function writeRecordOnce(dest, row) {
|
|
200
|
+
const done = withLedgerLock(dest, () => {
|
|
201
|
+
const ix = indexOf(dest);
|
|
202
|
+
const key = targetKey(row.target);
|
|
203
|
+
const hash = bodyHash(row.body);
|
|
204
|
+
const prev = ix.byTarget.get(key);
|
|
205
|
+
if (prev && (prev.hash === hash || prev.refreshed)) return; // written already; replaced at most once
|
|
206
|
+
if (!prev) {
|
|
207
|
+
append(dest, JSON.stringify(row));
|
|
208
|
+
indexOf(dest);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
// A CHANGED BODY REPLACES THE ONE WRITTEN, in place, recorded as such. Written whole to a temporary
|
|
212
|
+
// file and renamed over, so a reader sees the old file or the new one and never half of either.
|
|
213
|
+
const next = { ...row, refreshed: true, refreshed_from: prev.ts ?? null };
|
|
214
|
+
const kept = readFileSync(dest, "utf8").split("\n").filter((line) => {
|
|
215
|
+
if (!line.trim()) return false;
|
|
216
|
+
try { return targetKey(JSON.parse(line)?.target) !== key; } catch { return true; }
|
|
217
|
+
});
|
|
218
|
+
kept.push(JSON.stringify(next));
|
|
219
|
+
const tmp = `${dest}.${process.pid}.tmp`;
|
|
220
|
+
writeFileSync(tmp, kept.join("\n") + "\n");
|
|
221
|
+
renameSync(tmp, dest);
|
|
222
|
+
RUN_INDEXES.delete(dest);
|
|
223
|
+
indexOf(dest);
|
|
224
|
+
});
|
|
225
|
+
if (!done) append(dest, JSON.stringify(row));
|
|
226
|
+
}
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
// ── WHY TAIL-PRESERVING, RATHER THAN A BIGGER NUMBER ────────────────────────────────────────────────
|
|
19
19
|
//
|
|
20
20
|
// Raising 140 to 400 fixes this string and leaves the defect. Vendor messages put the STATUS at the
|
|
21
|
-
// front and the DISCRIMINATOR at the back —
|
|
22
|
-
//
|
|
21
|
+
// front and the DISCRIMINATOR at the back — the operator that is not allowed, the bound that was
|
|
22
|
+
// exceeded, the maximum that was passed. A head-only clip is therefore biased against exactly the part
|
|
23
23
|
// a classifier needs, at whatever length it is set to. The bias is the bug; the number is not.
|
|
24
24
|
//
|
|
25
25
|
// So: keep the head, keep the tail, elide the middle. The head carries the HTTP status and the office
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
export const BATCH_SCREEN_CHUNK = 100; // corsearch brand-json page size; clarivate /text caps at exactly 100 too.
|
|
11
11
|
|
|
12
|
-
// Live/dead status map for brand-json's vocabulary
|
|
13
|
-
//
|
|
12
|
+
// Live/dead status map for brand-json's vocabulary, counted across a screening sample so that every token
|
|
13
|
+
// the endpoint uses is mapped and none is left to chance. FAIL-OPEN: only Invalid/Expired are confidently dead;
|
|
14
14
|
// Valid/Pending/GracePeriod are LIVE (Pending = a live in-progress application; GracePeriod = the post-expiry
|
|
15
15
|
// renewal window — both real senior-rights risks that must NEVER be batch-dropped); anything else (Unknown or
|
|
16
16
|
// an unrecognized token) is AMBIGUOUS → the skill must fall through to record_fetch, never auto-drop on it.
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
// hand-written check. That is what `capabilities.nativeScriptIndex` is:
|
|
17
17
|
//
|
|
18
18
|
// true — the index holds the CHARACTERS. A native-script term is a legitimate, productive query
|
|
19
|
-
// and must be sent (the
|
|
20
|
-
//
|
|
21
|
-
// archived executed bands returned
|
|
19
|
+
// and must be sent (the comparison in driver/jx.mjs): native-script terms answer on such a
|
|
20
|
+
// provider and answer nothing on a romanisation index; and
|
|
21
|
+
// archived executed bands returned records on native characters across Han, Katakana,
|
|
22
22
|
// Cyrillic and Greek. Guarding it here would convert evidenced coverage into deferrals —
|
|
23
23
|
// the OPPOSITE defect, and a worse one, because a deferral is at least visible.
|
|
24
24
|
// false — the index holds only the TRANSLITERATION. Sending the characters is a silent zero.
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
// territories by display name ("United States", "European Union" — driver/compose-read.mjs
|
|
7
7
|
// PROMPT_TERRITORIES, mirroring portal-ui composerLevers), and those names flowed verbatim into
|
|
8
8
|
// `region:` clauses because corsearch's offices.translate is an ISO passthrough that ASSUMES codes.
|
|
9
|
-
//
|
|
10
|
-
// classified the failure transient and burned its park budget re-sending
|
|
9
|
+
// That provider answers an unknown multi-word region value as a server error rather than a bad request,
|
|
10
|
+
// so auto-recovery classified the failure transient and burned its park budget re-sending it.
|
|
11
11
|
// The run died at fan-in with terminalKind:repeat-signature.
|
|
12
12
|
//
|
|
13
13
|
// The register wire vocabulary is CODES (providers/corsearch/src/index.js: "UPPERCASE 2-letter
|
|
@@ -98,6 +98,26 @@ export const TERRITORY_TO_CODE = Object.freeze({
|
|
|
98
98
|
// two Benelux members — and they must not depend on which internationalisation data a customer's
|
|
99
99
|
// runtime happens to carry. What is derived is a WIDENING: it can only add names that would otherwise
|
|
100
100
|
// have resolved to nothing, and a build whose data is thinner loses names it never promised.
|
|
101
|
+
// A WITHDRAWN CODE ANSWERS TO THE SAME NAME AND IS REACHED FIRST. The runtime still knows the codes ISO
|
|
102
|
+
// has retired, and it gives them the CURRENT country's name: `VD` (North Vietnam, withdrawn 1977) and
|
|
103
|
+
// `VN` both answer "Vietnam". The scan runs AA to ZZ, so the dead code claims the name and the live one
|
|
104
|
+
// finds it taken — seven territories resolved that way, Vietnam, Yemen, Serbia, Zimbabwe, Vanuatu,
|
|
105
|
+
// Myanmar and Curaçao. None of them is a name this engine holds a jurisdiction for, so a requester who
|
|
106
|
+
// wrote "Vietnam" was pointed at a code no register can answer, and one who wrote "Vietnam" AND "VN" had
|
|
107
|
+
// two countries and was refused a search they had described correctly.
|
|
108
|
+
//
|
|
109
|
+
// Asking the runtime to canonicalise the region fixes all seven from the runtime's own data rather than
|
|
110
|
+
// from a hand-written list of retirements that would go stale the next time ISO withdraws one. A code
|
|
111
|
+
// that is current canonicalises to itself, so this is inert for every other entry — measured over the
|
|
112
|
+
// whole AA-ZZ scan, the only entries that move are those seven.
|
|
113
|
+
//
|
|
114
|
+
// It does not touch the direct-code branch in normalizeTerritory, where UK deliberately stays UK: that
|
|
115
|
+
// is an alias the providers own, not a withdrawal, and canonicalising there would take the decision
|
|
116
|
+
// away from the translate step that is supposed to make it.
|
|
117
|
+
const canonicalRegion = (code) => {
|
|
118
|
+
try { return new Intl.Locale(`und-${code}`).region || code; } catch { return code; }
|
|
119
|
+
};
|
|
120
|
+
|
|
101
121
|
const DERIVED_NAME_TO_CODE = (() => {
|
|
102
122
|
const out = Object.create(null);
|
|
103
123
|
let names;
|
|
@@ -109,7 +129,7 @@ const DERIVED_NAME_TO_CODE = (() => {
|
|
|
109
129
|
try { name = names.of(code); } catch { continue; }
|
|
110
130
|
if (!name || name === code || /^unknown/i.test(name)) continue;
|
|
111
131
|
const key = strip(name);
|
|
112
|
-
if (key && !(key in TERRITORY_TO_CODE) && !(key in out)) out[key] = code;
|
|
132
|
+
if (key && !(key in TERRITORY_TO_CODE) && !(key in out)) out[key] = canonicalRegion(code);
|
|
113
133
|
}
|
|
114
134
|
}
|
|
115
135
|
return out;
|
|
@@ -57,4 +57,4 @@ canonical schema, and the header names the traps — `EU` vs `EM`, `CONTAINS` on
|
|
|
57
57
|
hard 400, and `cardinalityRefusal`, the third way this provider says "that would match too much". Then
|
|
58
58
|
`src/core.js` for how those declarations are built into requests. The model-facing operator vocabulary
|
|
59
59
|
lives in
|
|
60
|
-
[`../../driver/skills/
|
|
60
|
+
[`../../driver/skills/clearance-register/providers/clarivate.md`](../../driver/skills/clearance-register/providers/clarivate.md).
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
//
|
|
7
7
|
// This contract is what the plan compiler reads, and the core implements exactly what is declared here:
|
|
8
8
|
//
|
|
9
|
-
// * classFilter is "native", NOT "fanout". INT_CLASS_NUMBER value "9 OR 28 OR 41 OR 42"
|
|
10
|
-
// byte the deduplicated result of the old per-class fan-out. The fan-out is DELETED
|
|
11
|
-
// classes now cost ONE call.
|
|
9
|
+
// * classFilter is "native", NOT "fanout". An INT_CLASS_NUMBER value of "9 OR 28 OR 41 OR 42" answers
|
|
10
|
+
// byte-for-byte the deduplicated result of the old per-class fan-out. The fan-out is DELETED
|
|
11
|
+
// (phase 4): N classes now cost ONE call.
|
|
12
12
|
// * predicates.default is a TRUE contains, expressed as an INFIX TERM WILDCARD (`*TERM*`) on
|
|
13
13
|
// WORD_MARK_SPECIFICATION — NOT the bare EQUALS the old MATCH_MODE_TO_FIELD.default emitted (which
|
|
14
14
|
// lost recall), and NOT the CONTAINS operator (supported on the mark field, but see the owner note).
|
|
@@ -105,12 +105,12 @@ export const CAPABILITIES = Object.freeze({
|
|
|
105
105
|
// Stage 0.5 uses it on NEITHER, so the number means the same thing on every deployment. See the
|
|
106
106
|
// matching note in providers/corsearch/src/capabilities.js and driver/register-count.mjs.
|
|
107
107
|
countStatusFilter: "live",
|
|
108
|
-
// JSON body, not a URI: the bound is the parser's document-nesting cap
|
|
109
|
-
//
|
|
108
|
+
// JSON body, not a URI: the bound is the parser's own document-nesting cap, which the vendor names in
|
|
109
|
+
// the refusal it answers a stack wider than this with. Not a URI length, so widening is not the fix.
|
|
110
110
|
maxOrWidth: 500,
|
|
111
111
|
// ONE call: INT_CLASS_NUMBER value "9 OR 28 OR 41 OR 42" (or "9,28,41,42") = the deduplicated union.
|
|
112
112
|
classFilter: "native",
|
|
113
|
-
// POST /text, EXACTLY 100 ids per call
|
|
113
|
+
// POST /text, EXACTLY 100 ids per call — a longer list is refused — and the call is BILLED: screening an
|
|
114
114
|
// enumerated band also fully hydrates it. (test:true is obfuscated + unbilled + NOT persisted to the
|
|
115
115
|
// record ledger: dev only, it can never back a real finding.)
|
|
116
116
|
screenSource: "billed-record-fetch",
|
|
@@ -127,8 +127,8 @@ export const CAPABILITIES = Object.freeze({
|
|
|
127
127
|
// Semantically it is the SAME CONDITION as `resultCeiling` — this query would match too much — and the
|
|
128
128
|
// engine already knows what to do with that: record a count+sample descriptor and hand it to judgment.
|
|
129
129
|
// It arrives as a 500 on the COUNT PROBE instead of a 400 on the search, so it landed in the generic
|
|
130
|
-
// provider-error arm and became a hard coverage hole
|
|
131
|
-
//
|
|
130
|
+
// provider-error arm and became a hard coverage hole — a small but real share of one delivery's slices,
|
|
131
|
+
// every one of them an owner or owner×term slice against a very large portfolio.
|
|
132
132
|
//
|
|
133
133
|
// AND THE EXISTING WIDTH DEFENCE CANNOT HELP, which is why nobody noticed the gap. `maxOrWidth`
|
|
134
134
|
// chunks against NESTING DEPTH. Splitting a wide OR-stack into narrower ones does nothing about
|
|
@@ -152,9 +152,9 @@ export const CAPABILITIES = Object.freeze({
|
|
|
152
152
|
// compiled to an ADJ chain — `*CORAL ADJ PUP*` — which is an ordered phrase match.
|
|
153
153
|
exact: "EXACT_WORD_MARK_SPECIFICATION", // case-insensitive but PUNCTUATION-SENSITIVE → strip punctuation client-side
|
|
154
154
|
default: "WORD_MARK_SPECIFICATION:*TERM*", // a TRUE contains via the term wildcard, not the recall-losing bare EQUALS
|
|
155
|
-
wildcardPrefix: "WORD_MARK_SPECIFICATION:TERM*", // native `*` in the value
|
|
156
|
-
wildcardSuffix: "WORD_MARK_SPECIFICATION:*TERM",
|
|
157
|
-
wildcardInfix: "WORD_MARK_SPECIFICATION:*TERM*", //
|
|
155
|
+
wildcardPrefix: "WORD_MARK_SPECIFICATION:TERM*", // native `*` in the value
|
|
156
|
+
wildcardSuffix: "WORD_MARK_SPECIFICATION:*TERM",
|
|
157
|
+
wildcardInfix: "WORD_MARK_SPECIFICATION:*TERM*", // the widest of the three
|
|
158
158
|
phonetic: "PHONETIC_WORD_MARK_SPECIFICATION",
|
|
159
159
|
owner: "APPLICANT_NAME", // EQUALS (+ wildcards); CONTAINS is a hard 400 — never emit it
|
|
160
160
|
}),
|
|
@@ -231,7 +231,7 @@ export const CAPABILITIES = Object.freeze({
|
|
|
231
231
|
//
|
|
232
232
|
// The entry-level RESCUE is the other half and it comes first: substituteRomanizedNames swaps the
|
|
233
233
|
// plan entry's `romanizedTerms` in (and relaxes the predicate to contains, because `exact` on a
|
|
234
|
-
// transliteration is itself a silent zero
|
|
234
|
+
// transliteration is itself a silent zero, where the same term under contains answers). A slice
|
|
235
235
|
// rescued that way is answerable and is never refused; only a native term with no romanisation defers.
|
|
236
236
|
nativeScriptIndex: false,
|
|
237
237
|
// No phoneme expansion knob: PHONETIC_WORD_MARK_SPECIFICATION is the whole surface; the client cannot
|
|
@@ -120,7 +120,7 @@ export const OWNER_FIELD = "APPLICANT_NAME";
|
|
|
120
120
|
/**
|
|
121
121
|
* match_mode → { name, operator, pre, post, … }.
|
|
122
122
|
*
|
|
123
|
-
* `default` is a TRUE contains (`*term
|
|
123
|
+
* `default` is a TRUE contains (`*term*`) — the old EQUALS mapping silently lost
|
|
124
124
|
* recall and is gone. `exact` strips punctuation client-side (the EXACT field is case-insensitive but
|
|
125
125
|
* punctuation-SENSITIVE). `wildcard` passes the caller's metacharacters through untouched — no
|
|
126
126
|
* BEGINS_WITH/ENDS_WITH remapping is needed because `*`/`?` are native in the value; its only
|
|
@@ -159,24 +159,23 @@ export const MATCH_MODE_TO_FIELD = Object.freeze({
|
|
|
159
159
|
// the space is NOT is a phrase match — and a phrase operator exists, absent from the vendor's schema but
|
|
160
160
|
// solid on the wire:
|
|
161
161
|
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
162
|
+
// · ordered adjacency IS phrase-contains — the tokens match in the order written, and the reversed
|
|
163
|
+
// pair matches nothing. A space cannot express that, because a space is order-blind.
|
|
164
|
+
// · it chains past two tokens, and the record that shows it was READ rather than inferred (/search
|
|
165
|
+
// for the hit, /text for the mark itself).
|
|
166
|
+
// · it survives an OR-stack, and the stack answers the exact sum of its legs — so ADJ binds TIGHTER
|
|
167
|
+
// than OR and no parentheses are needed.
|
|
168
168
|
//
|
|
169
|
-
// A phrase is therefore EXPRESSIBLE, so doctrine 2 says express it. Precision beats the space's AND
|
|
170
|
-
//
|
|
169
|
+
// A phrase is therefore EXPRESSIBLE, so doctrine 2 says express it. Precision beats the space's AND,
|
|
170
|
+
// which matters: the AND would have quietly widened every multi-word slice.
|
|
171
171
|
//
|
|
172
172
|
// Two caveats, both recorded rather than hidden:
|
|
173
|
-
// · There is NO string anchor for a phrase
|
|
174
|
-
//
|
|
173
|
+
// · There is NO string anchor for a phrase: `BEGINS_WITH` on a multi-word value degrades to per-token
|
|
174
|
+
// AND and answers WIDER than the phrase operator. So multi-word `starts_with`/`ends_with` become
|
|
175
175
|
// phrase-CONTAINS — a strict superset of what was asked. Safe in a clearance sweep, which fails by
|
|
176
176
|
// MISSING a mark and never by surfacing an extra one, but it is a widening and it is disclosed.
|
|
177
|
-
// · Punctuation is not indexed
|
|
178
|
-
//
|
|
179
|
-
// whole phrase — `TIKI ADJ & ADJ SLUSH` = 0.
|
|
177
|
+
// · Punctuation is not indexed — a hyphenated token matches as its split form — so a punctuation-only
|
|
178
|
+
// token is dropped from the chain. Kept, it matches nothing and would zero the whole phrase.
|
|
180
179
|
const PHRASE_OPERATOR = "ADJ";
|
|
181
180
|
// ── A TERM MUST BE ONE MARK, ON EVERY MARK-FIELD PREDICATE ────────────────────────────────────────
|
|
182
181
|
// The supplemental/cross-check lanes sometimes mint a LIST or a DESCRIPTION into the term field. All
|
|
@@ -316,7 +315,7 @@ export function compilePhraseValue(term, { pre = "", post = "", dropReserved = f
|
|
|
316
315
|
//
|
|
317
316
|
// `allowReserved` is set on the PHRASE path only, where the term is tokenised and each operator word is
|
|
318
317
|
// dropped with the adjacency across it widened — so "BLACK AND DECKER" goes out as
|
|
319
|
-
// `*BLACK ADJ2 DECKER*`
|
|
318
|
+
// `*BLACK ADJ2 DECKER*` — the register writes such a mark with the ampersand — rather than being refused.
|
|
320
319
|
// That is an EXPRESSION of the term, not a rewrite of it, and it is a SUPERSET of the literal phrase.
|
|
321
320
|
// `exact` still rejects, and correctly: that field answers HTTP 400 "Use of operators (AND, NOT, ADJ,
|
|
322
321
|
// NEAR) is not allowed in this field" and offers no escape at all. The owner field is not ADJ-joined,
|
|
@@ -333,23 +332,17 @@ export function stripPunctuation(term) {
|
|
|
333
332
|
}
|
|
334
333
|
|
|
335
334
|
// ── NON-LATIN MARK TERMS ARE NOT SEARCHABLE — AND THE FAILURE IS A SILENT ZERO ─────────────────────
|
|
336
|
-
// Compumark indexes a non-Latin filing by its ROMANISATION, never by its characters. The record
|
|
337
|
-
//
|
|
335
|
+
// Compumark indexes a non-Latin filing by its ROMANISATION, never by its characters. The record carries
|
|
336
|
+
// both — `markVerbalElementText` holds the characters, `markTransliteration` the romanisation — and only
|
|
337
|
+
// the romanisation is a search key. The characters answer nothing; the romanisation answers records that
|
|
338
|
+
// carry those very characters, so both halves are needed to see the behaviour at all.
|
|
338
339
|
//
|
|
339
|
-
//
|
|
340
|
+
// Universal, not a CJK-specific behaviour: every non-Latin record read across CN/TW/JP/KR/TH/GR/UA/EG/
|
|
341
|
+
// IL/SA carried a populated `markTransliteration`.
|
|
340
342
|
//
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
//
|
|
344
|
-
// 小米 → 0 XIAOMI → 57632
|
|
345
|
-
//
|
|
346
|
-
// Universal, not a CJK-specific behaviour — non-Latin records across CN/TW/JP/KR/TH/GR/UA/EG/IL/SA
|
|
347
|
-
// carried a populated markTransliteration (JP 7/7, KR 7/7, TW 12/12, TH 10/10, GR 6/6, UA 10/10,
|
|
348
|
-
// EG 11/11, SA 6/6, IL 1/1, CN 12/12).
|
|
349
|
-
//
|
|
350
|
-
// The romanisation is also STRICTLY BETTER for clearance than a character search would be:
|
|
351
|
-
// `HUA WEI BAO` returns 华威豹, 华味宝 AND 华为爆破 — three character sets, one pronunciation. Chinese
|
|
352
|
-
// squatting is overwhelmingly homophone-based, and a literal character match finds one of the three.
|
|
343
|
+
// The romanisation is also STRICTLY BETTER for clearance than a character search would be: one
|
|
344
|
+
// romanisation returns several distinct character sets that share its pronunciation. Chinese squatting
|
|
345
|
+
// is overwhelmingly homophone-based, and a literal character match finds one of them.
|
|
353
346
|
//
|
|
354
347
|
// So a native-script term reaching the wire is a CLIENT-SIDE REFUSAL, never a query: 0 with no error
|
|
355
348
|
// is the exact false-clean this provider swap exists to prevent, and it is the shape a caller is most
|
|
@@ -415,10 +408,11 @@ export function substituteRomanizedNames(e, pp, plan) {
|
|
|
415
408
|
//
|
|
416
409
|
// Both are EXPRESSIBLE, which is why neither is a reason to drop the name (same call as the
|
|
417
410
|
// apostrophe,). The shapes that work:
|
|
418
|
-
// a quoted name literal →
|
|
419
|
-
// an accented name literal →
|
|
420
|
-
//
|
|
421
|
-
//
|
|
411
|
+
// a quoted name literal → refused, the quotes stripped → answers, `?`-substituted → answers
|
|
412
|
+
// an accented name literal → refused, ASCII-folded → answers
|
|
413
|
+
//
|
|
414
|
+
// Folding never NARROWS, either: a name whose accents are already absent from the index answers the same
|
|
415
|
+
// either way, so the fold is safe to apply to every name rather than only the ones that fail.
|
|
422
416
|
//
|
|
423
417
|
// Strip rather than `?`-substitute: a bare space is an implicit AND on this field so the tokens still
|
|
424
418
|
// have to co-occur, and stripping cannot produce the `??` adjacency that a substitution would when two
|
|
@@ -542,8 +536,8 @@ export function resolveOffices(regions) {
|
|
|
542
536
|
/**
|
|
543
537
|
* ONE /search (or /count) body — the SAME SearchRequest shape for both endpoints.
|
|
544
538
|
*
|
|
545
|
-
* Multi-class is ONE searchField whose value is the class OR-list ("9 OR 28 OR 41 OR 42"),
|
|
546
|
-
*
|
|
539
|
+
* Multi-class is ONE searchField whose value is the class OR-list ("9 OR 28 OR 41 OR 42"), which
|
|
540
|
+
* answers the deduplicated union of the deleted 4-call per-class fan-out.
|
|
547
541
|
*/
|
|
548
542
|
// ── THE APOSTROPHE IS NOT SEARCHABLE IN APPLICANT_NAME ────────────────────────────────────────────
|
|
549
543
|
// An apostrophe is not searchable: "TRADER VIC'S", "MCDONALD'S CORPORATION" and
|
|
@@ -1150,7 +1144,7 @@ async function fetchText(apiKey, base, group, testMode, tctx) {
|
|
|
1150
1144
|
// Accepts record_ids that are EITHER synthetic `/mark/<office>/<guid>` refs OR bare guids (so the
|
|
1151
1145
|
// skill flow is identical to Corsearch: search → record_id → record_fetch(record_id)). Returns the
|
|
1152
1146
|
// NORMALIZED records and persists each (keyed by its synthetic ref) for the driver's A1 citation gate.
|
|
1153
|
-
// /text takes EXACTLY 100 ids per call
|
|
1147
|
+
// /text takes EXACTLY 100 ids per call and refuses a longer list, so the chunking happens here.
|
|
1154
1148
|
export async function doRecordFetch(apiKey, base, params, tctx) {
|
|
1155
1149
|
const inputs = Array.isArray(params.record_ids) ? params.record_ids : [];
|
|
1156
1150
|
if (inputs.length === 0) return { type: "text", text: "ERROR: clarivate_record_fetch — record_ids is required (non-empty array of refs or guids)." };
|
|
@@ -1262,10 +1256,10 @@ export async function doBatchScreen(apiKey, base, params, tctx) {
|
|
|
1262
1256
|
// names with per-office trademark counts, so the owner cross-check sweeps the applicant names the
|
|
1263
1257
|
// register actually holds instead of a guess at the entity's styling.
|
|
1264
1258
|
//
|
|
1265
|
-
// THRESHOLD (a judgment call, recorded): accept confidenceScore >= 50. The
|
|
1266
|
-
//
|
|
1267
|
-
//
|
|
1268
|
-
//
|
|
1259
|
+
// THRESHOLD (a judgment call, recorded): accept confidenceScore >= 50. The control that set it showed a
|
|
1260
|
+
// clean break at that value — the stylings above it held the portfolio, the ones below it held almost
|
|
1261
|
+
// nothing — rather than a gradient a cut-off would have to be invented for. The fixture's names are
|
|
1262
|
+
// invented and spelled out in fixtures/README.md. Over-inclusive is the SAFE direction here: a
|
|
1269
1263
|
// surplus applicant name adds marks a lawyer can discard, a missing one is invisible.
|
|
1270
1264
|
// And the expansion is strictly ADDITIVE — the caller's raw term is ALWAYS swept as well, so
|
|
1271
1265
|
// resolution can only ever gain recall, never lose it (a failed or empty resolution degrades to exactly
|
|
@@ -1389,8 +1383,8 @@ export async function expandOwnerTerms(apiKey, base, params, tctx) {
|
|
|
1389
1383
|
// against the provider's own owner vocabulary at all.
|
|
1390
1384
|
//
|
|
1391
1385
|
// That matters because APPLICANT_NAME EQUALS is not a full-string equality: a bare space in the value
|
|
1392
|
-
// is an IMPLICIT AND over the tokens
|
|
1393
|
-
//
|
|
1386
|
+
// is an IMPLICIT AND over the tokens, so a two-word owner term answers exactly what that owner's full
|
|
1387
|
+
// legal styling answers, and the mark field behaves the same way. So an un-resolved owner term is usually
|
|
1394
1388
|
// BROADER than the register's own styling, not narrower — which is why this went unnoticed. But an AND
|
|
1395
1389
|
// still requires EVERY token the caller wrote to appear in the applicant string, and a manifest names
|
|
1396
1390
|
// an owner the way the world writes it, not the way the register spells it. One token the register does
|
|
@@ -56,4 +56,4 @@ and discloses, and never silently degrades into a weaker search wearing the righ
|
|
|
56
56
|
`src/core.js`, beginning at `MATCH_MODE_PREFIX`: the match mode is a prefix character on a backtick-quoted
|
|
57
57
|
clause, and the contract's match-mode predicates are those keys unchanged. `predicates.owner` is the
|
|
58
58
|
exception — `owner:` is a real field clause, not a match mode. The model-facing operator vocabulary is in
|
|
59
|
-
[`../../driver/skills/
|
|
59
|
+
[`../../driver/skills/clearance-register/providers/corsearch.md`](../../driver/skills/clearance-register/providers/corsearch.md).
|
|
@@ -66,8 +66,8 @@ export const CAPABILITIES = Object.freeze({
|
|
|
66
66
|
// this provider's vocabulary is ISO, where the EU is `EU`. territory-codes.mjs states the rule the
|
|
67
67
|
// alias follows — "provider translate owns provider-specific spelling, e.g. EU→EM on clarivate" —
|
|
68
68
|
// and the binding-layer pass in register-plan.mjs names the EU register `EM` because that is the
|
|
69
|
-
// canonical office code, so without this the region clause would carry a value
|
|
70
|
-
//
|
|
69
|
+
// canonical office code, so without this the region clause would carry a value this provider answers
|
|
70
|
+
// as a server error rather than a bad request (the copper-bastion shape: a malformed region reads as
|
|
71
71
|
// transient and burns the park budget).
|
|
72
72
|
translate: (code) => {
|
|
73
73
|
const c = String(code ?? "").trim().toUpperCase();
|
|
@@ -114,9 +114,9 @@ export const CAPABILITIES = Object.freeze({
|
|
|
114
114
|
// sent — the shared executor's script-form refusal (providers/_shared/script-form.mjs) is switched
|
|
115
115
|
// off by this declaration, and switching it on would convert evidenced coverage into deferrals.
|
|
116
116
|
//
|
|
117
|
-
// Carried in driver/jx.mjs as the provider comparison:
|
|
118
|
-
//
|
|
119
|
-
// Corroborated by archived executed bands, which returned
|
|
117
|
+
// Carried in driver/jx.mjs as the provider comparison: native-script terms answer here and answer
|
|
118
|
+
// nothing on a romanisation index, which is the whole of the difference between the two.
|
|
119
|
+
// Corroborated by archived executed bands, which returned records on native characters
|
|
120
120
|
// across Han, Katakana, Cyrillic and Greek. Structurally corroborated too: `name` and
|
|
121
121
|
// `nameTransliteration` are TWO SEPARATE fields on a row (core.js returns both), so `name:` holds the
|
|
122
122
|
// mark AS FILED and the transliteration is an extra returned field, not the search key. assembleQuery
|
|
@@ -138,8 +138,8 @@ export function assembleQuery(p) {
|
|
|
138
138
|
// Filters (each value must be backtick-quoted)
|
|
139
139
|
if (Array.isArray(p.nice_classes)) for (const c of p.nice_classes) parts.push(clause("", "nice-class", c));
|
|
140
140
|
if (Array.isArray(p.registries)) for (const r of p.registries) parts.push(clause("", "registry", r));
|
|
141
|
-
// Regions go to the wire as CODES only (copper-bastion incident:
|
|
142
|
-
//
|
|
141
|
+
// Regions go to the wire as CODES only (copper-bastion incident: an unknown multi-word region value
|
|
142
|
+
// is answered as a server error rather than a bad request, so a display name here poisons every retry).
|
|
143
143
|
// Known display names are translated; Worldwide drops the clause; anything else fails loudly with
|
|
144
144
|
// a message the composing model can act on in-session.
|
|
145
145
|
if (Array.isArray(p.regions)) for (const r of p.regions) {
|
|
@@ -427,7 +427,7 @@ export async function doExpandPhoneme(sessionKey, params, tctx) {
|
|
|
427
427
|
// brand-json hydrates ~100 candidate URIs in ONE POST with the screening data the thin search row lacks
|
|
428
428
|
// (classes/status/owner/dates/jurisdictions/image/transliteration) — but NOT goodsAndServices. It replaces
|
|
429
429
|
// the per-candidate record_fetch for the SCREENING majority (status/class/owner keep-or-drop); finalists +
|
|
430
|
-
// any G&S-dependent decision still deep-fetch (the skill enforces that — skills/
|
|
430
|
+
// any G&S-dependent decision still deep-fetch (the skill enforces that — skills/clearance-register/unit.md).
|
|
431
431
|
// The tool is registered but left OUT of every agent's tools.allow until the live probe + Alex recall A/B +
|
|
432
432
|
// sign-off (the GATE) — so it is inert today.
|
|
433
433
|
|