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/providers/jx/README.md
CHANGED
|
@@ -9,7 +9,8 @@ validated at parse.
|
|
|
9
9
|
**One vendor, one billing mode, decided by the run and not by this directory.** The lanes go through
|
|
10
10
|
`engine.runTurn()` — the same door all fourteen agentic stages use — via
|
|
11
11
|
[`../../driver/engine/jx-turn.mjs`](../../driver/engine/jx-turn.mjs). Whatever program the customer configured
|
|
12
|
-
(`CLEAROTRON_AI`) and whatever billing mode the run is on (API key or
|
|
12
|
+
(`CLEAROTRON_AI`) and whatever billing mode the run is on (subscription, API key or, for Claude, a cloud account)
|
|
13
|
+
carries these calls too.
|
|
13
14
|
That is the owner's standing rule — *one LLM provider only ever, API or auth, no mix* — and until 2026-08-20
|
|
14
15
|
these three lanes were the one place in the product that broke it: they POSTed to the Anthropic Messages API on
|
|
15
16
|
`ANTHROPIC_API_KEY` at a hardcoded cheap tier no matter what the rest of the run was doing.
|
|
@@ -106,15 +106,20 @@ export function envelopeFromTurnText(text, toolName) {
|
|
|
106
106
|
* so the ORDER of those checks lives here once rather than three times.
|
|
107
107
|
*
|
|
108
108
|
* `turn` is supplied by the driver and is injectable for tests:
|
|
109
|
-
* async ({prompt, kind}) => { ok, text, truncated, usage:{input,output}, model, vendor, authMode, cause }
|
|
109
|
+
* async ({prompt, kind}) => { ok, text, truncated, usage:{input,output}, model, vendor, authMode, cloud, cause }
|
|
110
110
|
*
|
|
111
111
|
* ATTRIBUTION RIDES EVERY RETURN, including the failures. A degrade still spent tokens, and without the
|
|
112
112
|
* model, vendor and billing mode beside them they cannot be attributed in the run's rollup — which is
|
|
113
113
|
* the half of that says a run must be able to state who did the work.
|
|
114
|
+
*
|
|
115
|
+
* `cloud` is part of that attribution: under the cloud billing mode it names the account that paid
|
|
116
|
+
* ("foundry", "vertex", "bedrock", "gateway"), and it is null under every other mode. The ledger stamp
|
|
117
|
+
* reads it from THIS return, so a field left off here reached every native-language record as
|
|
118
|
+
* `cloud: null` while the main steps of the same run said which cloud paid.
|
|
114
119
|
*/
|
|
115
120
|
export async function runJxTurn({ body, turn, kind, started, truncatedCause, parse }) {
|
|
116
121
|
const t0 = Number.isFinite(started) ? started : Date.now();
|
|
117
|
-
const blank = { tookMs: 0, model: null, vendor: null, authMode: null, usage: null };
|
|
122
|
+
const blank = { tookMs: 0, model: null, vendor: null, authMode: null, cloud: null, usage: null };
|
|
118
123
|
if (typeof turn !== "function") return { ok: false, cause: `${kind}: no turn runner was supplied`, ...blank };
|
|
119
124
|
|
|
120
125
|
const { prompt, toolName } = promptFromRequest(body);
|
|
@@ -124,7 +129,7 @@ export async function runJxTurn({ body, turn, kind, started, truncatedCause, par
|
|
|
124
129
|
|
|
125
130
|
const attribution = {
|
|
126
131
|
tookMs: Date.now() - t0,
|
|
127
|
-
model: r?.model ?? null, vendor: r?.vendor ?? null, authMode: r?.authMode ?? null,
|
|
132
|
+
model: r?.model ?? null, vendor: r?.vendor ?? null, authMode: r?.authMode ?? null, cloud: r?.cloud ?? null,
|
|
128
133
|
// Passed through WHOLE, and `null` stays null. The driver hands over the engine contract's canonical
|
|
129
134
|
// Usage ({input, output, cacheRead, cacheWrite, total}); re-shaping it to two fields here would drop
|
|
130
135
|
// cache and total tokens from the rollup, and a zeroed object in place of null would report a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trademark-oauth-mcp-bridge",
|
|
3
|
-
"version": "0.3.2-beta.
|
|
3
|
+
"version": "0.3.2-beta.9",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"private": true,
|
|
6
6
|
"description": "OAuth 2.1 MCP stdio bridge used by the engine's case-law gather stage (courtlistener / legaldatahunter).",
|
|
@@ -384,7 +384,7 @@ export function connotationQueriesOf(spec) {
|
|
|
384
384
|
* Build the sandbox task from the DICTATED spec — the term and platform keys reach Perplexity's program
|
|
385
385
|
* from code, never re-typed by the calling model (this is what kills re-typed-key corruption (VIBRANTE→VIBRNTE-class typos)).
|
|
386
386
|
* CRITICAL: this carries the PINNED pplx_sdk access idiom (the same one in
|
|
387
|
-
* skills/
|
|
387
|
+
* skills/clearance-common-law/perplexity-prompts.md). Without it the sandbox agent free-styles the SDK and
|
|
388
388
|
* dies with "TypeError: 'pplx_sdk.WebHit' object is not iterable" on every cell (the teal-vault /
|
|
389
389
|
* a 1-of-154 fold failure). The result object supports ITERATION + ATTRIBUTE
|
|
390
390
|
* access only. The per-cell try/except keeps one bad cell from aborting the whole grid.
|
|
@@ -56,4 +56,4 @@ past `OWNER_SCOPED_WINDOW` (400 rows). Approximate totals saturate at 10000 and
|
|
|
56
56
|
on which date, and what is still `null` on purpose. Then `src/core.js` for how a declared predicate
|
|
57
57
|
reaches the wire — `toSignaParams` → `buildSearchRequest` — because "the vendor supports it" and "our
|
|
58
58
|
executor sends it" are two different claims. The model-facing operator vocabulary lives in
|
|
59
|
-
[`../../driver/skills/
|
|
59
|
+
[`../../driver/skills/clearance-register/providers/signa.md`](../../driver/skills/clearance-register/providers/signa.md).
|
|
@@ -22,17 +22,16 @@
|
|
|
22
22
|
// punctuation. Declaring it would be declaring a capability the executor cannot serve, which is
|
|
23
23
|
// the one thing 's criteria forbid. Stays null; the slice defers, disclosed.
|
|
24
24
|
// * resultCeiling — there is no single number to put here, and the reason is worth the paragraph.
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
// it applies to the owner-scoped shape only.
|
|
25
|
+
// An unscoped term pages to exhaustion whatever its size, on every predicate — no ceiling. But add
|
|
26
|
+
// `filters.owner_name` and the SAME term stops dead at the owner-scoped window (400), which the
|
|
27
|
+
// vendor's own cursor refusal names. The bound is in ROWS rather than pages — the loop stops
|
|
28
|
+
// mid-page, before the page that would cross it — and it applies to the owner-scoped shape only.
|
|
30
29
|
//
|
|
31
30
|
// So the ceiling is a property of the QUERY SHAPE, not of the provider, and this field can hold
|
|
32
31
|
// only one number for both. `null` with the fact written down beats 400, which would turn every
|
|
33
|
-
// tractable band over
|
|
34
|
-
// it beats a number that is right for one shape and wrong for the other.
|
|
35
|
-
// OWNER_SCOPED_WINDOW below, which is the machine-readable half.
|
|
32
|
+
// tractable band over that window into a crowd and throw away the bands this vendor will happily
|
|
33
|
+
// page to exhaustion; and it beats a number that is right for one shape and wrong for the other.
|
|
34
|
+
// See OWNER_SCOPED_WINDOW below, which is the machine-readable half.
|
|
36
35
|
//
|
|
37
36
|
// The total is a separate matter: it saturates at 10000 and flags itself
|
|
38
37
|
// approximate there — a fact about the count, not about the window.
|
|
@@ -107,10 +106,9 @@ export const CAPABILITIES = Object.freeze({
|
|
|
107
106
|
// was none — the flag is now set by buildSearchRequest on EVERY call, where no call site can forget
|
|
108
107
|
// it, because under this seam a response without a total cannot support a completeness claim.
|
|
109
108
|
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
// makes the cheap probe cheap.
|
|
109
|
+
// Every narrow band answers an exact total, and an empty band answers `total_count: 0,
|
|
110
|
+
// approximate: false` — an exact zero, the only kind this repository may render. `limit: 1` returns
|
|
111
|
+
// the same total as the paged query, which is what makes the cheap probe cheap.
|
|
114
112
|
//
|
|
115
113
|
// The vendor also flags some totals `total_count_approximate` — always at exactly 10000, so it is a
|
|
116
114
|
// saturation marker rather than an estimate. normalizeSearchResponse reports those as UNKNOWN, never
|
|
@@ -175,10 +173,11 @@ export const CAPABILITIES = Object.freeze({
|
|
|
175
173
|
// portfolio": that is `filters.owner_id`, which takes a resolved `own_…` id and 400s on an unknown
|
|
176
174
|
// one rather than answering zero.
|
|
177
175
|
//
|
|
178
|
-
// A zero here is a real zero and not a filter failing silently. Verified
|
|
179
|
-
// like one:
|
|
180
|
-
//
|
|
181
|
-
// which does
|
|
176
|
+
// A zero here is a real zero and not a filter failing silently. Verified on the case that looked
|
|
177
|
+
// like one: a term × owner × territory intersection came back empty while the bare owner and the
|
|
178
|
+
// bare term each answered — because the register holds those marks under a differently styled
|
|
179
|
+
// applicant name, which the owner clause does reach. Both clauses are applied; the empty set is
|
|
180
|
+
// the answer.
|
|
182
181
|
owner: "owner_name",
|
|
183
182
|
}),
|
|
184
183
|
|
|
@@ -195,38 +194,32 @@ export const CAPABILITIES = Object.freeze({
|
|
|
195
194
|
|
|
196
195
|
// ── WHICH BINDING LAYERS DOES A SEARCH SCOPED TO THIS OFFICE ACTUALLY RETURN? ──────────
|
|
197
196
|
//
|
|
198
|
-
// 's first task, answered for this provider by
|
|
199
|
-
//
|
|
197
|
+
// 's first task, answered for this provider by driving the three scopings against each other rather
|
|
198
|
+
// than by reading the documentation. One term, one limit, France:
|
|
200
199
|
//
|
|
201
|
-
// filters.offices
|
|
202
|
-
// filters.jurisdictions
|
|
203
|
-
// filters.jurisdictions
|
|
204
|
-
// 101 rows — FR national + EM/direct_regional + WO
|
|
200
|
+
// filters.offices the national register ALONE
|
|
201
|
+
// filters.jurisdictions + territory_match direct national + the territory's Madrid legs
|
|
202
|
+
// filters.jurisdictions + territory_match protection national + REGIONAL + Madrid
|
|
205
203
|
//
|
|
206
|
-
// and the control that proves `protection` adds a
|
|
207
|
-
//
|
|
204
|
+
// and the control that proves `protection` adds a LAYER rather than simply more rows: a territory
|
|
205
|
+
// that sits under no regional register answers the same either way.
|
|
208
206
|
//
|
|
209
207
|
// STAGE 2 MOVED THE TABLE, BECAUSE IT MOVED THE QUERY. `toSignaParams` now sends
|
|
210
208
|
// `filters.jurisdictions` + `territory_match: "protection"` instead of `filters.offices`, so a
|
|
211
|
-
// territory's whole stack of rights comes back in ONE call.
|
|
212
|
-
//
|
|
209
|
+
// territory's whole stack of rights comes back in ONE call. Every cell below was then asked
|
|
210
|
+
// directly — `filing_route` per territory rather than sampled — and what that walk established is:
|
|
213
211
|
//
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
// CA 9894 0 814 WO 0 0 9150
|
|
219
|
-
// AU 10000+ 0 1734 SG 2899 0 1323
|
|
220
|
-
// NO 1354 0 1149
|
|
212
|
+
// · every territory reaches its Madrid layer;
|
|
213
|
+
// · every EU member reaches the EU register, and no non-member does;
|
|
214
|
+
// · a `regional` absence is not a gap — no regional register binds Switzerland or Canada, so
|
|
215
|
+
// there is no regional layer for `bindingLayersFor` to ask about.
|
|
221
216
|
//
|
|
222
|
-
//
|
|
223
|
-
// regional layer for `bindingLayersFor` to ask about. Every territory reaches its Madrid layer, and
|
|
224
|
-
// every EU member reaches the EU register — which is the whole of what Stage 2 asked for.
|
|
217
|
+
// Which is the whole of what Stage 2 asked for.
|
|
225
218
|
//
|
|
226
219
|
// WHAT THE OFFICE FILTER DID, kept because it is why the change was necessary and because the day
|
|
227
|
-
// someone reverts the query shape this table becomes false rather than merely stale:
|
|
228
|
-
//
|
|
229
|
-
//
|
|
220
|
+
// someone reverts the query shape this table becomes false rather than merely stale: scoped to a
|
|
221
|
+
// national office, a France search returns that national register alone — with the EU trade mark
|
|
222
|
+
// that blocks use in France sitting in a register nobody queried.
|
|
230
223
|
//
|
|
231
224
|
// (The office filter is not uniform either — `uspto` and `ipi` return madrid-derived rows because
|
|
232
225
|
// those registers hold the national legs of IRs themselves, while `inpi-fr` and `ukipo` return
|
|
@@ -267,12 +260,12 @@ export const CAPABILITIES = Object.freeze({
|
|
|
267
260
|
// The owner surface exists (predicates.owner above) and composes with a text query in ONE request,
|
|
268
261
|
// so the intersection is the vendor's rather than something reassembled here from two sweeps.
|
|
269
262
|
//
|
|
270
|
-
// EVIDENCED ON THE DETERMINISTIC SHAPE, AND ONLY THERE, on purpose. `match: contains
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
//
|
|
263
|
+
// EVIDENCED ON THE DETERMINISTIC SHAPE, AND ONLY THERE, on purpose. Under `match: contains`, adding
|
|
264
|
+
// `filters.owner_name` to a term returns a PROPER SUBSET of that term alone — which is what an
|
|
265
|
+
// intersection has to be. The ranked shape does NOT show that: under `strategies` the same pair
|
|
266
|
+
// answers MORE with the extra clause than without it. Recall under `similar` is not fixed, so ranked
|
|
267
|
+
// totals are not comparable across two requests and cannot evidence a set relation in either
|
|
268
|
+
// direction.
|
|
276
269
|
//
|
|
277
270
|
// Recorded because the ranked pair is the measurement a reader would take first, and taken alone it
|
|
278
271
|
// would look like the filter being ignored — a wrong conclusion, reached from real numbers.
|
|
@@ -283,9 +276,9 @@ export const CAPABILITIES = Object.freeze({
|
|
|
283
276
|
// reason it is a value and not still a null: fetch a record whose script is non-Latin, read its
|
|
284
277
|
// mark text verbatim, search those exact characters back.
|
|
285
278
|
//
|
|
286
|
-
// record
|
|
287
|
-
// query
|
|
288
|
-
// result
|
|
279
|
+
// record one whose `mark_text_script` is non-Latin, read verbatim
|
|
280
|
+
// query that record's own mark_text, `match: "exact"`
|
|
281
|
+
// result it recalls itself — the same id comes back, and nothing else
|
|
289
282
|
//
|
|
290
283
|
// The characters are the index key, so `true`. providers/_shared/script-form.mjs may now send a
|
|
291
284
|
// native-script slice here instead of deferring it.
|
|
@@ -297,7 +290,7 @@ export const CAPABILITIES = Object.freeze({
|
|
|
297
290
|
// guessing at it here would put the shrug back in a different field.
|
|
298
291
|
//
|
|
299
292
|
// The blind spot to note for the reader who comes to widen this: the search ROW carries no script
|
|
300
|
-
// field at all
|
|
293
|
+
// field at all; `mark_text_script` is a FULL-RECORD field. A sweep over
|
|
301
294
|
// search rows therefore finds no scripts anywhere and reads as "the index holds none" — which is
|
|
302
295
|
// how this probe failed on its first attempt, and it failed by returning an empty set, not an error.
|
|
303
296
|
nativeScriptIndex: true,
|
|
@@ -117,8 +117,8 @@ const DETERMINISTIC_MATCH = new Set(["similar", "exact", "starts_with", "ends_wi
|
|
|
117
117
|
// filter keys outright — `HTTP 400 Unrecognized key: status` — so every status-filtered Signa search
|
|
118
118
|
// failed on the wire, and it survived review because the kernel does not pass one and no fixture
|
|
119
119
|
// carried one. The real names are `status_primary` (pending|active|inactive|unknown) and
|
|
120
|
-
// `status_stage`. `status_primary:["active"]` narrows
|
|
121
|
-
// control
|
|
120
|
+
// `status_stage`. `status_primary:["active"]` narrows a band as a status filter should; the bogus-key
|
|
121
|
+
// control is refused identically, which is what proves the rejection is about the NAME and not the value.
|
|
122
122
|
//
|
|
123
123
|
// Two branches building the same object by hand is what let one of them be wrong for two months, so
|
|
124
124
|
// there is now one function and both branches call it.
|
|
@@ -136,7 +136,11 @@ function buildFilters(p) {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
export function buildSearchRequest(p) {
|
|
139
|
-
|
|
139
|
+
// `query` is OMITTED, not sent undefined, when the entry is owner-only: an explicit `query: undefined`
|
|
140
|
+
// serializes away anyway, but writing it conditionally is what makes the owner-only shape legible here
|
|
141
|
+
// rather than an accident of JSON.stringify.
|
|
142
|
+
const body = {};
|
|
143
|
+
if (String(p.query ?? "").trim()) body.query = p.query;
|
|
140
144
|
const match = typeof p.match === "string" ? p.match.trim() : "";
|
|
141
145
|
if (match && DETERMINISTIC_MATCH.has(match)) {
|
|
142
146
|
body.match = match; // sending strategies alongside is a 4xx, not a preference
|
|
@@ -240,7 +244,7 @@ export function normalizeRecord(rec, officeHint = null) {
|
|
|
240
244
|
imageAvailable: rec.has_media ?? null,
|
|
241
245
|
resolved_link: null, // Signa exposes no per-record public URL; renderer shows "verify at office"
|
|
242
246
|
// ── WHICH LAYER THIS RIGHT SITS ON, carried as data ( →) ──────────────────────────
|
|
243
|
-
// The normalizer read
|
|
247
|
+
// The normalizer read under half the fields a search row carries. Among the ones it dropped were the
|
|
244
248
|
// four that say what KIND of right a record is — and those are not extras, they are the whole
|
|
245
249
|
// vocabulary the binding-layer disclosure is written in. A France search that returns an EUTM and
|
|
246
250
|
// a Madrid IR alongside French national marks could not say so, because the three arrived
|
|
@@ -281,7 +285,23 @@ export function normalizeRecord(rec, officeHint = null) {
|
|
|
281
285
|
|
|
282
286
|
// Light search row (record_id + display fields). Signa returns FULL records on search, but we project a
|
|
283
287
|
// lean row here and let record_fetch return the full normalized record (mirrors corsearch/clarivate).
|
|
288
|
+
//
|
|
289
|
+
// THIS ROW IS ALSO THE BAND ROW, so it carries the band contract's key names. `screenSource: "search-row"`
|
|
290
|
+
// means the kernel lands these rows in the named band as they are — there is no screen call to lift
|
|
291
|
+
// fields across — and every band consumer (band-shape, named-band, the digest, the placement form, the
|
|
292
|
+
// house-mark ownership check) reads `owner_name`, `classes` and `application_date`. This row used to carry
|
|
293
|
+
// only `owner`, `nice_classes` and `filing_date`, and `owner` read a flat `owner_name` the vendor does not
|
|
294
|
+
// send: the owner is `owners[].name`, exactly as normalizeRecord above already reads it. Every Signa
|
|
295
|
+
// record therefore reached the fold with no owner, every register placement failed
|
|
296
|
+
// `placement_owner_missing` in the render, and a pass that had tiered 141 in-class identical and
|
|
297
|
+
// near-identical records delivered none of them while reporting success.
|
|
298
|
+
//
|
|
299
|
+
// The signa-named keys stay: rowScreen and the search tool's own output read them.
|
|
284
300
|
function normalizeSearchRow(rec) {
|
|
301
|
+
const owner0 = Array.isArray(rec.owners) ? rec.owners[0] : null;
|
|
302
|
+
const owner = rec.owner_name ?? owner0?.name ?? null;
|
|
303
|
+
const classes = normalizeClasses(rec);
|
|
304
|
+
const filed = toIso(rec.filing_date);
|
|
285
305
|
return {
|
|
286
306
|
record_id: rec.id ? makeRef(rec.jurisdiction_code || rec.office_code, rec.id) : null,
|
|
287
307
|
id: rec.id,
|
|
@@ -289,9 +309,13 @@ function normalizeSearchRow(rec) {
|
|
|
289
309
|
mark_text: rec.mark_text ?? null,
|
|
290
310
|
status: pickStatusText(rec),
|
|
291
311
|
status_class: statusClassOf(rec),
|
|
292
|
-
nice_classes:
|
|
293
|
-
|
|
294
|
-
|
|
312
|
+
nice_classes: classes,
|
|
313
|
+
classes,
|
|
314
|
+
owner,
|
|
315
|
+
owner_name: owner,
|
|
316
|
+
owner_country: owner0?.country_code ?? owner0?.country ?? null,
|
|
317
|
+
filing_date: filed,
|
|
318
|
+
application_date: filed,
|
|
295
319
|
registration_date: toIso(rec.registration_date),
|
|
296
320
|
relevance_score: Number.isInteger(rec.relevance_score) ? rec.relevance_score : null,
|
|
297
321
|
raw: rec,
|
|
@@ -312,9 +336,9 @@ export function isSearchResponseBody(body) {
|
|
|
312
336
|
// ── THE TOTAL, AND THE ONE CASE WHERE THE VENDOR'S NUMBER IS NOT A COUNT ───────────────────────────
|
|
313
337
|
//
|
|
314
338
|
// `options.include_total` returns `pagination.total_count` AND `pagination.total_count_approximate`.
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
339
|
+
// Every narrow band answers an exact total and — the case that matters — an empty band answers
|
|
340
|
+
// `total_count: 0, approximate: false`, an EXACT zero, which is the only kind this repository is
|
|
341
|
+
// allowed to render.
|
|
318
342
|
//
|
|
319
343
|
// An approximate total of exactly 10000 is a saturation marker rather than an estimate.
|
|
320
344
|
// The vendor is saying "at least ten thousand", and it says so on the broad sweeps (a bare owner
|
|
@@ -419,8 +443,16 @@ function loadFixture(name) {
|
|
|
419
443
|
// the roster unwidened, which is the direction to err in before an open-source cut. Hence a fallback
|
|
420
444
|
// LIST rather than one hardcoded term.
|
|
421
445
|
const DETERMINISTIC_FALLBACK_TERMS = ["nike", "swoosh"];
|
|
422
|
-
function resolveSearchFixture({ query, strategies, match }) {
|
|
446
|
+
function resolveSearchFixture({ query, strategies, match, owner }) {
|
|
423
447
|
const q = String(query || "").toLowerCase();
|
|
448
|
+
// AN OWNER-ONLY SEARCH HAS NO QUERY, so every lookup below — which keys on the term — would miss and
|
|
449
|
+
// the mock would answer "no fixture for query=". That is not a harmless gap: it is the reason the
|
|
450
|
+
// owner-only path could not be driven offline, and a test that cannot reach `doSearch` gets closed by
|
|
451
|
+
// asserting the element predicate instead, which passes while the request gate still refuses.
|
|
452
|
+
if (!q && String(owner || "").trim()) {
|
|
453
|
+
const o = String(owner).trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
454
|
+
return loadFixture(`signa-search-owner-${o}`) || loadFixture("signa-search-owner");
|
|
455
|
+
}
|
|
424
456
|
const det = typeof match === "string" ? match.trim() : "";
|
|
425
457
|
if (det) {
|
|
426
458
|
return loadFixture(`signa-search-match-${det}-${q}`)
|
|
@@ -432,16 +464,54 @@ function resolveSearchFixture({ query, strategies, match }) {
|
|
|
432
464
|
return null;
|
|
433
465
|
}
|
|
434
466
|
|
|
467
|
+
// ── WHAT COUNTS AS SOMETHING TO SEARCH ────────────────────────────────────────────────────────────
|
|
468
|
+
//
|
|
469
|
+
// THE DEFECT THIS CLOSES. Two gates decided whether a plan entry had anything to search, and both
|
|
470
|
+
// answered on a free-text query or a names list alone. Neither counted an OWNER. An owner portfolio
|
|
471
|
+
// sweep carries an owner and no query by definition — that is what the shape is — so every one was
|
|
472
|
+
// refused before it was ever dispatched: `doSearch` returned "query is required", and the kernel's
|
|
473
|
+
// element gate returned the enumerate contract's missing-element error without calling the provider.
|
|
474
|
+
// The run continued and disclosed the slices as unsearched, so nothing failed and a client's report
|
|
475
|
+
// said the incumbent portfolios "could not be reached" when nothing had asked for them.
|
|
476
|
+
//
|
|
477
|
+
// It was never a capability gap. `buildFilters` already maps `owner` to this vendor's owner filter and
|
|
478
|
+
// its note names the three populations — term alone, OWNER ALONE, both — as genuinely different from
|
|
479
|
+
// each other. `ownerWindowCeiling` exists solely to cap paging for owner-scoped searches, which is not
|
|
480
|
+
// something written for a shape that cannot be dispatched, and it defers to `isOwnerScoped`, the
|
|
481
|
+
// KERNEL's own predicate, whose note names a bare-owner sweep as one of the two shapes it serves.
|
|
482
|
+
// Only the two element gates were never taught that an owner is an element.
|
|
483
|
+
//
|
|
484
|
+
// ONE DEFINITION, because this file already carries the lesson: `buildFilters` exists because two
|
|
485
|
+
// hand-written copies of the same object let one of them be wrong for two months. Two hand-written
|
|
486
|
+
// copies of "does this carry an element" would drift the same way, and the drift would be silent in
|
|
487
|
+
// the same direction — a shape one gate admits and the other refuses reads as a provider error.
|
|
488
|
+
export const hasSearchElement = (p) => Boolean(
|
|
489
|
+
String(p?.query ?? "").trim()
|
|
490
|
+
|| (Array.isArray(p?.names) && p.names.filter(Boolean).length)
|
|
491
|
+
|| (typeof p?.owner === "string" && p.owner.trim()));
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* What this request was FOR, for a log line and an error message. An owner-only search has no query,
|
|
495
|
+
* and "query=undefined" in a refusal is how a reader concludes the caller forgot one.
|
|
496
|
+
*/
|
|
497
|
+
export const searchTargetLabel = (p = {}) => (String(p.query ?? "").trim()
|
|
498
|
+
? `query=${p.query}`
|
|
499
|
+
: (typeof p.owner === "string" && p.owner.trim() ? `owner=${p.owner.trim()}` : "no element"));
|
|
500
|
+
|
|
501
|
+
/** The refusal, naming every element that would have been accepted. */
|
|
502
|
+
export const MISSING_ELEMENT_ERROR = "ERROR: signa_enumerate — a query, names[] or owner is required.";
|
|
503
|
+
|
|
435
504
|
// ── Search ─────────────────────────────────────────────────────────────────────────────────────────
|
|
436
505
|
export async function doSearch(apiKey, base, params, tctx, { mock = false } = {}) {
|
|
437
|
-
|
|
506
|
+
// Refuses only when there is NOTHING to search. An owner alone is a search (see hasSearchElement).
|
|
507
|
+
if (!hasSearchElement(params)) return { type: "text", text: MISSING_ELEMENT_ERROR };
|
|
438
508
|
if (mock) {
|
|
439
509
|
const fx = resolveSearchFixture(params);
|
|
440
|
-
if (!fx) return { type: "text", text: `ERROR (mock): no fixture for
|
|
510
|
+
if (!fx) return { type: "text", text: `ERROR (mock): no fixture for ${searchTargetLabel(params)} strategy=${(params.strategies || ["exact"])[0]}` };
|
|
441
511
|
return { type: "text", text: JSON.stringify({ mock: true, ...normalizeSearchResponse(fx, params.query) }, null, 2) };
|
|
442
512
|
}
|
|
443
513
|
const body = buildSearchRequest(params);
|
|
444
|
-
const r = await signaFetch(apiKey, base, "/v1/trademarks", { method: "POST", body, tctx: { ...tctx, target:
|
|
514
|
+
const r = await signaFetch(apiKey, base, "/v1/trademarks", { method: "POST", body, tctx: { ...tctx, target: searchTargetLabel(params).slice(0, 120) } });
|
|
445
515
|
if (!r.ok) {
|
|
446
516
|
const msg = r.body?.error?.detail ?? r.body?.message ?? (r.raw ? r.raw.slice(0, 200) : "");
|
|
447
517
|
return { type: "text", text: `ERROR: signa_search HTTP ${r.status}: ${msg}` };
|
|
@@ -451,11 +521,11 @@ export async function doSearch(apiKey, base, params, tctx, { mock = false } = {}
|
|
|
451
521
|
// in exactly this case there is no second net. An unparsed 200 was the shortest path in the codebase
|
|
452
522
|
// from a cut connection to `state:"enumerated"` with zero records. The guard is unchanged; its reason
|
|
453
523
|
// is corrected — it used to rest on this provider having no count at all, which retired.
|
|
454
|
-
if (r.parseError) return { type: "text", text: unparsedBodyError("signa_search", r, `
|
|
524
|
+
if (r.parseError) return { type: "text", text: unparsedBodyError("signa_search", r, ` ${searchTargetLabel(params).slice(0, 120)}`) };
|
|
455
525
|
// Parsing is not answering: a 200 carrying an error envelope is the same shortest path one JSON
|
|
456
526
|
// envelope away — no data[], zero rows, the loop ends, enumerated. See isSearchResponseBody.
|
|
457
527
|
if (!isSearchResponseBody(r.body)) {
|
|
458
|
-
return { type: "text", text: nonAnswerBodyError("signa_search", r, "a search response (no data[] — the key every /v1/trademarks answer carries)", `
|
|
528
|
+
return { type: "text", text: nonAnswerBodyError("signa_search", r, "a search response (no data[] — the key every /v1/trademarks answer carries)", ` ${searchTargetLabel(params).slice(0, 120)}`) };
|
|
459
529
|
}
|
|
460
530
|
return { type: "text", text: JSON.stringify(normalizeSearchResponse(r.body, params.query), null, 2) };
|
|
461
531
|
}
|
|
@@ -528,14 +598,14 @@ export function toSignaParams(p = {}) {
|
|
|
528
598
|
// in the French register, and was never searched.
|
|
529
599
|
//
|
|
530
600
|
// `filters.jurisdictions` + `territory_match: "protection"` asks the territory question instead:
|
|
531
|
-
// every right with effect there, whatever register it sits on.
|
|
532
|
-
//
|
|
533
|
-
//
|
|
534
|
-
//
|
|
601
|
+
// every right with effect there, whatever register it sits on. On the same term, an office-scoped
|
|
602
|
+
// France search returns the national register and NOTHING else — no EU right, no Madrid leg — while
|
|
603
|
+
// the protection-scoped one returns the national rows unchanged plus both of the layers the first
|
|
604
|
+
// never saw.
|
|
535
605
|
//
|
|
536
|
-
// and the control that shows `protection` adds a
|
|
537
|
-
// under no regional register
|
|
538
|
-
// same. Every one of the eleven covered territories reaches its Madrid layer this way; the EU
|
|
606
|
+
// and the control that shows `protection` adds a LAYER rather than merely more rows: a territory
|
|
607
|
+
// under no regional register gains no regional rows either way, while its Madrid layer arrives all
|
|
608
|
+
// the same. Every one of the eleven covered territories reaches its Madrid layer this way; the EU
|
|
539
609
|
// members additionally reach the EU register.
|
|
540
610
|
//
|
|
541
611
|
// IT IS ONE CALL, NOT THREE. No extra queries, no extra spend — which is why this half of Stage 2
|
|
@@ -552,7 +622,7 @@ export function toSignaParams(p = {}) {
|
|
|
552
622
|
// caller that translates before handing params in — the shape a probe or a driver adapter naturally
|
|
553
623
|
// takes — gets it applied twice. On the second pass `regions` is still present, so an earlier draft
|
|
554
624
|
// re-added `offices` beside the `jurisdictions` it had just produced. The API accepts both and the
|
|
555
|
-
// office filter WINS: a France order went back to
|
|
625
|
+
// office filter WINS: a France order went back to the national rows alone, with `territory_match:
|
|
556
626
|
// "protection"` sitting in the body doing nothing. The expansion silently undid itself and the run
|
|
557
627
|
// reported `state: "enumerated"` either way. Caught by comparing before/after on a live call and
|
|
558
628
|
// finding them identical — the one check that could see it.
|
|
@@ -662,9 +732,9 @@ export function toSignaParams(p = {}) {
|
|
|
662
732
|
/**
|
|
663
733
|
* THE ONE QUERY SHAPE WITH A NARROWER RESULT WINDOW.
|
|
664
734
|
*
|
|
665
|
-
* `filters.owner_name` caps paging at 400 ROWS on this vendor; nothing else does.
|
|
666
|
-
*
|
|
667
|
-
*
|
|
735
|
+
* `filters.owner_name` caps paging at 400 ROWS on this vendor; nothing else does. On one term at one
|
|
736
|
+
* limit, both unscoped predicates paged to exhaustion and the owner-scoped one stopped dead at the
|
|
737
|
+
* window, which the vendor's own cursor refusal names.
|
|
668
738
|
*
|
|
669
739
|
* `null` for every other shape, and that is the load-bearing half. Returning 400 across the board would
|
|
670
740
|
* turn every tractable band over 400 into a sanctioned crowd — an UNDER-SEARCH wearing a crowd
|
|
@@ -684,8 +754,10 @@ const { enumerate: __enumerate } = makeEnumerate({
|
|
|
684
754
|
// own count/search reconciliation exists to adjudicate on the "endpoint" seam. One source, one
|
|
685
755
|
// number. (`makeCountProbe` throws unless "endpoint" supplies one, so this is asserted, not assumed.)
|
|
686
756
|
count: null,
|
|
687
|
-
|
|
688
|
-
|
|
757
|
+
// The SAME predicate `doSearch` uses, not a second copy of it. An owner is an element here: see
|
|
758
|
+
// hasSearchElement for the defect this closes and why one definition rather than two.
|
|
759
|
+
hasAnyElement: hasSearchElement,
|
|
760
|
+
missingElementError: MISSING_ELEMENT_ERROR,
|
|
689
761
|
capabilities: { ...CAPABILITIES.kernel },
|
|
690
762
|
ceilingFor: ownerWindowCeiling,
|
|
691
763
|
// The kernel's default cheap-probe params are CORSEARCH'S — `{limit:1, fields:["uri"]}` — and `uri`
|
|
@@ -807,6 +879,11 @@ export async function doCountHits(apiKey, base, params, tctx, { mock = false } =
|
|
|
807
879
|
text: JSON.stringify({
|
|
808
880
|
total_hits: exact ? parsed.total_hits : null,
|
|
809
881
|
total_approximate: parsed.total_approximate === true,
|
|
882
|
+
// THE FLOOR TRAVELS AS DATA, not only inside the note below. The normalizer already keeps it —
|
|
883
|
+
// `total_floor` on the parsed body — and it was reaching the driver in prose only, so the one
|
|
884
|
+
// number that makes a saturated band answerable had to be read back out of a sentence to be used.
|
|
885
|
+
// A reader that parses it out of the note is a reader that breaks when the note is reworded.
|
|
886
|
+
total_floor: Number.isFinite(parsed.total_floor) ? parsed.total_floor : null,
|
|
810
887
|
present: exact ? parsed.total_hits > 0 : ((parsed.results?.length ?? 0) > 0 || parsed.has_more === true),
|
|
811
888
|
seen: parsed.results?.length ?? 0,
|
|
812
889
|
note: exact
|
|
@@ -155,7 +155,7 @@ what an index actually holds, including whether the 1884 backfile is in it (a da
|
|
|
155
155
|
identical on every other number and is missing a century).
|
|
156
156
|
|
|
157
157
|
This replaces only the *register* half of a clearance
|
|
158
|
-
— the reasoning engine still needs its own subscription or
|
|
158
|
+
— the reasoning engine still needs its own subscription, API key or cloud account, and the unregistered-use half
|
|
159
159
|
still wants `PERPLEXITY_API_KEY`.
|
|
160
160
|
|
|
161
161
|
**It refuses rather than answering zero.** An absent index, a schema with no rows, or an index whose
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
//
|
|
5
5
|
// ── THE CONTRACT, AND HOW MUCH OF IT IS VERIFIED ─────────────────────────────────────────────────
|
|
6
6
|
// The endpoints, the auth header and the product id below are NOT inherited by analogy from another
|
|
7
|
-
// provider (see skills/
|
|
7
|
+
// provider (see skills/clearance-register/providers/README.md on why that rule exists). They come from
|
|
8
8
|
// USPTO's own OpenAPI description of the Open Data Portal, cross-checked against live probes from this
|
|
9
9
|
// machine:
|
|
10
10
|
//
|
package/scripts/README.md
CHANGED
|
@@ -20,6 +20,7 @@ The axis is *what the script assumes exists*, not where it happens to be run.
|
|
|
20
20
|
| `report-print-check.mjs` | Shows what the exported PDF actually renders under print media. Run by CI. |
|
|
21
21
|
| `report-overflow-check.mjs` | The same two report lanes SIDEWAYS, under screen media — the print check cannot see a width, because print sets the watermark to `display:none` and the watermark was the overflow. Run by CI. |
|
|
22
22
|
| `portal-lifecycle-check.mjs` | Asks whether the portal names a brand owner and whether a person can manage their searches. Run by CI. |
|
|
23
|
+
| `portal-strings.mjs` | Reads every string the portal can show out of its source, as a reader meets it. `--check <approved list>` refuses a string on a screen that is neither approved nor in `portal-ui/strings-backlog.json`; `--backlog-shrinks --base <ref>` refuses a backlog line the base did not have. The approved list is kept with the designs, outside this repository. CI runs the backlog half. |
|
|
23
24
|
| `validate-profiles.mjs` | Validates a customer-config store's profile bundles. Read-only. |
|
|
24
25
|
| `record-carry-probe.mjs` | Traces retrieval → findings over any finished run dir. Read-only. |
|
|
25
26
|
| `placement-diff.mjs` | Shows what the placement tier did between two runs of one matter. |
|