clearotron 0.3.2 → 0.3.3-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +12 -0
- package/INSTALL.md +8 -0
- package/bin/onboard.mjs +109 -13
- package/bin/start.mjs +1 -1
- package/build-info.json +2 -2
- package/demo/MANIFEST.json +27 -0
- package/docs/INTAKE.md +8 -0
- package/docs/architecture/04-configuration-reference.md +29 -11
- package/driver/CHANGELOG.md +49 -0
- package/driver/citation-census.json +3 -3
- package/driver/clearance-variants-record.mjs +12 -1
- package/driver/common-law-coverage-status.mjs +113 -0
- package/driver/config-inventory.mjs +1 -1
- package/driver/contract-audit.mjs +1 -1
- package/driver/contract-e3-backlog.mjs +37 -37
- package/driver/contract-vocabulary.mjs +8 -8
- package/driver/coverage-form-io.mjs +3 -1
- package/driver/coverage-form.mjs +38 -11
- package/driver/coverage-ledger.mjs +37 -7
- package/driver/coverage-union.mjs +2 -2
- package/driver/crowd-context.mjs +19 -6
- package/driver/dev-portal.mjs +3 -3
- package/driver/drainer-identity.mjs +1 -1
- package/driver/driver.config.mjs +80 -9
- package/driver/engine/CONTRACT.md +3 -2
- package/driver/engine/anthropic-agent.mjs +34 -7
- package/driver/engine/mcp/clarivate-server.mjs +4 -2
- package/driver/engine/mcp/corsearch-server.mjs +3 -1
- package/driver/engine/mcp/coverage-server.mjs +1 -1
- package/driver/engine/mcp/dispositions-server.mjs +47 -5
- package/driver/engine/mcp/euipo-server.mjs +2 -0
- package/driver/engine/mcp/free-tier-server.mjs +2 -0
- package/driver/engine/mcp/gather-config.mjs +8 -2
- package/driver/engine/mcp/probe-server.mjs +37 -0
- package/driver/engine/mcp/proposal-fields.mjs +45 -0
- package/driver/engine/mcp/recording-server.mjs +30 -0
- package/driver/engine/mcp/signa-server.mjs +2 -0
- package/driver/engine/mcp/supplemental.mjs +89 -12
- package/driver/engine/mcp/unit-note-server.mjs +50 -0
- package/driver/engine/mcp/uspto-local-server.mjs +2 -0
- package/driver/engine/openai-agent.mjs +7 -0
- package/driver/engine/probe.mjs +67 -14
- package/driver/engine/tool-refusal.mjs +16 -0
- package/driver/enqueue-schema.mjs +2 -2
- package/driver/envelope-settle.mjs +82 -13
- package/driver/findings-model.mjs +4 -4
- package/driver/gateway.mjs +18 -2
- package/driver/manager-groups-verdict.mjs +1 -1
- package/driver/matter-frame-record.mjs +24 -7
- package/driver/named-band.mjs +1 -1
- package/driver/package.json +1 -1
- package/driver/partial-payload-baseline.json +12 -3
- package/driver/pipeline-knockout.mjs +3 -3
- package/driver/pipeline.mjs +154 -50
- package/driver/plan-run-agreement-verdict.mjs +49 -0
- package/driver/portal-service.mjs +8 -4
- package/driver/progress.mjs +14 -3
- package/driver/publish/index.mjs +41 -26
- package/driver/publish/report-data.mjs +4 -3
- package/driver/publish/xlsx.mjs +26 -4
- package/driver/queue-markers.mjs +44 -0
- package/driver/queue-watch-verdict.mjs +2 -2
- package/driver/reference-score.mjs +10 -2
- package/driver/register-availability.mjs +2 -2
- package/driver/register-plan.mjs +313 -21
- package/driver/roster-verdict.mjs +1 -1
- package/driver/runner.mjs +26 -2
- package/driver/settle-stamp.mjs +10 -3
- package/driver/skills/clearance-common-law/SKILL.md +2 -0
- package/driver/skills/clearance-register/SKILL.md +44 -3
- package/driver/skills/clearance-register/digest.md +5 -5
- package/driver/skills/clearance-register/providers/clarivate.md +1 -1
- package/driver/skills/clearance-register/unit.md +39 -0
- package/driver/skills/clearance-variants/SKILL.md +1 -1
- package/driver/skills/matter-frame/SKILL.md +4 -2
- package/driver/stages.mjs +12 -5
- package/driver/status-snapshot.mjs +2 -2
- package/driver/suite-census.json +293 -29
- package/driver/synthesis-record.mjs +80 -2
- package/driver/unit-file-drift.mjs +3 -3
- package/driver/unit-inventory.mjs +2 -2
- package/driver/unit-state-verdict.mjs +1 -1
- package/driver/updater-identity.mjs +2 -3
- package/driver/variant-manifest-model.mjs +11 -1
- package/driver/verify.mjs +5 -5
- package/driver/withheld-families.mjs +104 -0
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/lib/brief.mjs +16 -12
- package/mcp-server/lib/runs.mjs +1 -1
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +3 -2
- package/package.json +2 -2
- package/portal-ui/dist/assets/{index-DMthc7PQ.js → index-GBbbyQxc.js} +22 -4
- package/portal-ui/dist/index.html +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/_shared/count.mjs +2 -2
- package/providers/_shared/enumerate.mjs +15 -2
- package/providers/_shared/execute-plan.mjs +19 -1
- package/providers/_shared/plan-guards.mjs +40 -0
- package/providers/clarivate/src/capabilities.js +15 -5
- package/providers/clarivate/src/core.js +41 -5
- package/providers/corsearch/src/capabilities.js +4 -0
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/signa/src/capabilities.js +22 -8
- package/providers/signa/src/core.js +12 -1
- package/scripts/demo-evidence.mjs +114 -0
- package/scripts/e2e.mjs +1 -1
- package/scripts/engine-probe.mjs +6 -5
- package/scripts/env-audit.mjs +1 -1
- package/scripts/freeze-example-run.mjs +3 -3
- package/scripts/live-surface-check.mjs +32 -33
- package/scripts/mint-suite-census.mjs +66 -0
- package/scripts/package-size-budget.mjs +117 -0
- package/scripts/register-plan-shape.mjs +259 -0
- package/scripts/release-note-required.mjs +38 -1
- package/scripts/repo-writes.mjs +1 -1
- package/scripts/report-sections-render-check.mjs +7 -3
- package/scripts/score.mjs +7 -1
- package/scripts/settings-render-check.mjs +36 -0
- package/scripts/travelling-predicates.mjs +1 -1
- package/shared/identifier-scan.mjs +22 -5
- package/shared/scroll-settle.mjs +67 -0
|
@@ -288,7 +288,18 @@ export function makeEnumerate(deps) {
|
|
|
288
288
|
const tally = { "verified-zero": 0, enumerated: 0, crowd: 0, unenumerated: 0, error: 0 };
|
|
289
289
|
for (const v of Object.values(term_counts)) tally[v.disposition] += 1;
|
|
290
290
|
const unresolved = tally.crowd + tally.unenumerated + tally.error;
|
|
291
|
-
|
|
291
|
+
// A FULLY RESOLVED STACK IS A COMPLETE BAND, INCLUDING WHEN THE ANSWER IS ZERO. The comment below
|
|
292
|
+
// states the rule and the code then demanded a record anyway: `records.length > 0`. So a stack in
|
|
293
|
+
// which EVERY term resolved to verified-zero — nobody has filed any of these names — fell through
|
|
294
|
+
// to `incomplete`, which says nobody answered the question. The opposite is true: every term was
|
|
295
|
+
// asked and every one came back empty.
|
|
296
|
+
//
|
|
297
|
+
// It matters beyond the label. An `incomplete` parent is terminal for its when-guarded children, so
|
|
298
|
+
// a clean zero here held back everything waiting on it — and with the wider families now waiting on
|
|
299
|
+
// the identical question, that is the best case a matter can have (a mark nobody has registered)
|
|
300
|
+
// producing a run that searches almost nothing. The ordinary search path has always returned
|
|
301
|
+
// `enumerated` for a zero-record answer; this rescue path was the one place that did not.
|
|
302
|
+
if (unresolved === 0) {
|
|
292
303
|
// every term resolved to verified-zero or fully-enumerated ⇒ the union of per-term enumerations IS
|
|
293
304
|
// the complete stack (every record matching ≥1 name sits in some term's enumeration) — a true band.
|
|
294
305
|
return { type: "text", text: JSON.stringify({ state: "enumerated", total_hits: stackTotal, count: records.length, records, term_counts }, null, 2) };
|
|
@@ -344,7 +355,9 @@ export function makeEnumerate(deps) {
|
|
|
344
355
|
const tally = { "verified-zero": 0, enumerated: 0, crowd: 0, unenumerated: 0, error: 0 };
|
|
345
356
|
for (const v of Object.values(class_counts)) tally[v.disposition] += 1;
|
|
346
357
|
const unresolved = tally.crowd + tally.unenumerated + tally.error;
|
|
347
|
-
|
|
358
|
+
// Same rule, same reason, same correction as the OR-stack rescue above: a stack in which every class
|
|
359
|
+
// came back a verified zero is a complete band whose answer is zero, not a question nobody answered.
|
|
360
|
+
if (unresolved === 0) {
|
|
348
361
|
// every class resolved to verified-zero or fully-enumerated ⇒ the union of per-class enumerations
|
|
349
362
|
// IS the complete stack (every record carries ≥1 in-filter class) — a true band, the class rescue.
|
|
350
363
|
return { type: "text", text: JSON.stringify({ state: "enumerated", total_hits: stackTotal, count: records.length, records, class_counts }, null, 2) };
|
|
@@ -18,6 +18,7 @@ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
|
18
18
|
import { dirname } from "node:path";
|
|
19
19
|
import { nativeScriptIndexGap } from "./script-form.mjs";
|
|
20
20
|
import { entryTermIssues, goodsTermsList } from "./term-shape.mjs";
|
|
21
|
+
import { awaitsReadingTurn } from "./plan-guards.mjs";
|
|
21
22
|
import { faultText, guardToolCall } from "./transport-guard.mjs";
|
|
22
23
|
import { clipProviderText } from "./provider-text.mjs"; // — keep the discriminator
|
|
23
24
|
|
|
@@ -210,10 +211,13 @@ export function queryMarkTerms(e, query) {
|
|
|
210
211
|
return Array.isArray(e?.terms) ? e.terms : e?.term != null ? [e.term] : [];
|
|
211
212
|
}
|
|
212
213
|
|
|
214
|
+
// The goods words ride after the class tag (close-verify reads that tag): a goods-narrowed question shares
|
|
215
|
+
// predicate, term and classes with the identical one, and without them the two read as the same question.
|
|
213
216
|
export const describePlanEntry = (e) =>
|
|
214
217
|
`${e.predicate} ${e.terms ? e.terms.join(" OR ") : e.term}`
|
|
215
218
|
+ `${typeof e.owner === "string" && e.owner.trim() && String(e.predicate ?? "") !== "owner" ? ` owner:${e.owner.trim()}` : ""}`
|
|
216
|
-
+ ` [cl ${(e.nice_classes ?? []).join(",")}]
|
|
219
|
+
+ ` [cl ${(e.nice_classes ?? []).join(",")}]`
|
|
220
|
+
+ `${goodsTermsList(e).length ? ` goods:${goodsTermsList(e).join(" OR ")}` : ""}`;
|
|
217
221
|
|
|
218
222
|
// Compile one plan entry + its predicate params into the provider's query params (corsearch shapes by
|
|
219
223
|
// default: names/name/owners/owner + nice_classes + regions).
|
|
@@ -576,6 +580,20 @@ export function makeExecutePlan(deps) {
|
|
|
576
580
|
for (const e of targeted.filter((x) => !x.when)) await runEntry(e);
|
|
577
581
|
const skipped = [];
|
|
578
582
|
for (const e of targeted.filter((x) => x.when)) {
|
|
583
|
+
// A CROWD IS WHAT HOLDS A CHILD BACK, and only a crowd. `enumerated` is the state of a question
|
|
584
|
+
// that was answered — with records or with none: the ordinary search path returns it for a
|
|
585
|
+
// zero-record answer too, so a clean zero has always released its children here.
|
|
586
|
+
//
|
|
587
|
+
// Where that stopped being true was the RESCUE paths, and it is fixed there rather than here: a
|
|
588
|
+
// per-term or per-class stack in which every member came back a verified zero was falling through
|
|
589
|
+
// to `incomplete`, which says nobody answered. See enumerate.mjs — a fully resolved stack is a
|
|
590
|
+
// complete band whose answer is zero. `verified-zero` is a per-term DISPOSITION and never a band
|
|
591
|
+
// state (named-band.mjs BAND_STATES), so a guard testing for it here could never fire.
|
|
592
|
+
// THE READING-TURN WAIT: a family awaiting the reading turn is never released by a RESULT, so there is no
|
|
593
|
+
// state to read here and no seeded prior state that could release it on a warm followup either.
|
|
594
|
+
// The reading turn asks for it by minting a supplemental entry, which arrives as its own
|
|
595
|
+
// ungated entry — this one stands in the plan as the record of a question not asked.
|
|
596
|
+
if (awaitsReadingTurn(e.when)) { skipped.push(e.qid); continue; }
|
|
579
597
|
if (stateByQid.get(e.when.runs_if_enumerated) === "enumerated") await runEntry(e);
|
|
580
598
|
else skipped.push(e.qid); // crowd/failed parent is TERMINAL for the fringe — by design, never an error
|
|
581
599
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
|
|
3
|
+
//
|
|
4
|
+
// plan-guards.mjs — what a register plan entry's `when` guard means, read from one place.
|
|
5
|
+
//
|
|
6
|
+
// THE TWO WAITS A PLAN ENTRY CAN CARRY, and they are different questions.
|
|
7
|
+
//
|
|
8
|
+
// `runs_if_enumerated: <parent qid>` is a fact about a RESULT: this slice runs if its parent question
|
|
9
|
+
// came back as a list rather than a crowd. A crowd parent is terminal for it.
|
|
10
|
+
//
|
|
11
|
+
// `awaits_reading_turn: true` is a fact about JUDGMENT NOT YET MADE (ruled 2026-09-21): this family runs
|
|
12
|
+
// when the reading turn asks for it, having read the identical mark's own list and found it thin. No
|
|
13
|
+
// result releases it — not the identical question coming back as a comfortable list, not a clean
|
|
14
|
+
// zero, nothing. The ask arrives as a supplemental entry, so the guarded entry itself never runs: it
|
|
15
|
+
// stands in the plan as the record of a question deliberately not asked.
|
|
16
|
+
//
|
|
17
|
+
// WHY THIS IS ITS OWN MODULE, on the provider side, rather than a constant in the compiler. Both the
|
|
18
|
+
// COMPILER (driver/register-plan.mjs, which writes the guard and joins it back to the band) and the
|
|
19
|
+
// EXECUTOR (providers/_shared/execute-plan.mjs, which decides what to dispatch) must agree on what a
|
|
20
|
+
// guard means, and a guard the executor does not recognise is not a guard at all — the entry runs as
|
|
21
|
+
// though it were ungated, which is silently the opposite of what was asked for. The executor cannot
|
|
22
|
+
// import the compiler: nothing under providers/ imports driver/, and inverting that for one predicate
|
|
23
|
+
// would invert it for the whole provider tree. So the meaning lives here, where both sides can read
|
|
24
|
+
// it, and neither side carries a second copy to drift.
|
|
25
|
+
|
|
26
|
+
/** The guard a decision-10 family carries while it waits for the reading turn. Frozen: entries spread a copy. */
|
|
27
|
+
export const AWAITS_READING_TURN = Object.freeze({ awaits_reading_turn: true });
|
|
28
|
+
|
|
29
|
+
/** Is this guard the ruling-204 wait — the one no result can release? */
|
|
30
|
+
export const awaitsReadingTurn = (when) => when?.awaits_reading_turn === true;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The parent qid a guard waits on, or null where it waits on no question at all.
|
|
34
|
+
*
|
|
35
|
+
* Every caller that resolves a guard against a band, validates it against the plan's qids, or reports
|
|
36
|
+
* which question held an entry back goes through this. A ruling-204 wait names no qid, and a caller
|
|
37
|
+
* reading `when.runs_if_enumerated` directly would get `undefined` and then decide something about it
|
|
38
|
+
* — orphan it, look it up and miss, or print it.
|
|
39
|
+
*/
|
|
40
|
+
export const guardParentQid = (when) => (awaitsReadingTurn(when) ? null : when?.runs_if_enumerated ?? null);
|
|
@@ -97,8 +97,7 @@ export const CAPABILITIES = Object.freeze({
|
|
|
97
97
|
// fails loud with tooManyResults past 30000. There is no partial mode and no cursor.
|
|
98
98
|
pagination: "single-shot",
|
|
99
99
|
// …which is exactly why the enumerate ceiling must be tested BEFORE the search, via the cheap
|
|
100
|
-
// POST /count — it works at ANY magnitude
|
|
101
|
-
// counts in one call.
|
|
100
|
+
// POST /count — it works at ANY magnitude and returns per-office counts in one call.
|
|
102
101
|
countProbe: "endpoint",
|
|
103
102
|
// A count CAN be narrowed to live filings here — queryOptions.activeOnly (buildSearchRequest's
|
|
104
103
|
// `active_only`). Declared because it diverges from corsearch, which has no status clause at all;
|
|
@@ -107,7 +106,8 @@ export const CAPABILITIES = Object.freeze({
|
|
|
107
106
|
countStatusFilter: "live",
|
|
108
107
|
// JSON body, not a URI: the bound is the parser's own document-nesting cap, which the vendor names in
|
|
109
108
|
// the refusal it answers a stack wider than this with. Not a URI length, so widening is not the fix.
|
|
110
|
-
|
|
109
|
+
// A 498-term stack is refused at that cap and a 496-term stack is not, so 496 is the declared width.
|
|
110
|
+
maxOrWidth: 496,
|
|
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
113
|
// POST /text, EXACTLY 100 ids per call — a longer list is refused — and the call is BILLED: screening an
|
|
@@ -239,6 +239,16 @@ export const CAPABILITIES = Object.freeze({
|
|
|
239
239
|
goodsTextMultiWord: "ordered-phrase",
|
|
240
240
|
// Several goods terms ride ONE clause joined by OR — this register expresses a list natively.
|
|
241
241
|
goodsTextListOr: true,
|
|
242
|
+
// ── THE SHORTEST TERM THE CONTAINS FORM ACCEPTS: none known, so none declared ───────────────────
|
|
243
|
+
//
|
|
244
|
+
// No vendor document available to this repository states one: the vendor publishes no public
|
|
245
|
+
// reference for this search API, and none was found on 2026-09-22. What is measured is narrower and is
|
|
246
|
+
// handled in core.js: a ONE-character first or last token inside a phrase chain is refused (HTTP
|
|
247
|
+
// 500), and two characters are answered. A two-letter `*TERM*` on its own is not recorded either way.
|
|
248
|
+
//
|
|
249
|
+
// `null` means the compiler keeps asking the contains form at every length, which is what this
|
|
250
|
+
// register has always been sent. A declared number must come from the vendor, not from a guess.
|
|
251
|
+
containsMinLength: null,
|
|
242
252
|
// The operator the goods clause rides. `EQUALS` on WHOLE WORDS: `CONTAINS` is a hard 400 here
|
|
243
253
|
// exactly as it is on APPLICANT_NAME, and so is a mid-word wildcard. Several words are asked for
|
|
244
254
|
// with `OR` inside the value.
|
|
@@ -285,7 +295,7 @@ export const CAPABILITIES = Object.freeze({
|
|
|
285
295
|
// WIRED: providers/clarivate/src/core.js builds its enumerate from
|
|
286
296
|
// makeEnumerate({ capabilities: {...CAPABILITIES.kernel} }) — these values are the LIVE seam settings,
|
|
287
297
|
// no longer a design note. pageGuard is 1 because /search is single-shot: there is no page 2 to
|
|
288
|
-
// fetch, so the guard can only ever be a backstop. namesChunkDefault = maxOrWidth (
|
|
298
|
+
// fetch, so the guard can only ever be a backstop. namesChunkDefault = maxOrWidth (496): the kernel
|
|
289
299
|
// chunks a wide OR-stack to the parser's nesting bound before it reaches the wire.
|
|
290
300
|
kernel: Object.freeze({
|
|
291
301
|
countProbe: "endpoint",
|
|
@@ -293,7 +303,7 @@ export const CAPABILITIES = Object.freeze({
|
|
|
293
303
|
pageSize: 100,
|
|
294
304
|
pageGuard: 1, // single-shot: there is no page 2
|
|
295
305
|
ceilingDefault: 600,
|
|
296
|
-
namesChunkDefault:
|
|
306
|
+
namesChunkDefault: 496,
|
|
297
307
|
providerWindow: "30000-result hard ceiling (tooManyResults, fails loud)",
|
|
298
308
|
// POST /search returns BARE GUIDS — the search row carries no mark text, classes, status or owner.
|
|
299
309
|
// POST /text (the screen call) is therefore the SOLE content source for an enumerated band, which
|
|
@@ -90,6 +90,9 @@ export async function clarivateFetch(apiKey, base, path, { body = null, method =
|
|
|
90
90
|
attempts = i + 1;
|
|
91
91
|
resp = await fetch(url, init);
|
|
92
92
|
if (resp.ok || resp.status < 500 || i === retries) break;
|
|
93
|
+
// A 500 wrapping the search service's 400 is a refusal of the query as written, not weather: the
|
|
94
|
+
// same request is refused the same way, so it is not sent again (countFailureReason below).
|
|
95
|
+
if (WRAPPED_REFUSAL.test(await resp.clone().text().catch(() => ""))) break;
|
|
93
96
|
await new Promise((r) => setTimeout(r, 1500 * (i + 1)));
|
|
94
97
|
}
|
|
95
98
|
} catch (err) {
|
|
@@ -1113,13 +1116,45 @@ export async function doSearch(apiKey, base, params, tctx) {
|
|
|
1113
1116
|
}
|
|
1114
1117
|
|
|
1115
1118
|
// ── Count ─────────────────────────────────────────────────────────────────────────────────────────
|
|
1116
|
-
// POST /count takes the SAME SearchRequest body as /search, is cheap, works at ANY magnitude
|
|
1117
|
-
//
|
|
1119
|
+
// POST /count takes the SAME SearchRequest body as /search, is cheap, works at ANY magnitude and
|
|
1120
|
+
// returns PER-OFFICE counts in one call. This is the enumerate
|
|
1118
1121
|
// kernel's countProbe:"endpoint" dependency, so it returns the kernel's plain probe shape
|
|
1119
1122
|
// { ok, total, reason } — plus `per_office`, which is richer than corsearch can offer and rides into
|
|
1120
1123
|
// the crowd descriptor for judgment.
|
|
1121
1124
|
|
|
1122
1125
|
/** ONE /count round trip over already-resolved params. Returns the kernel's plain probe shape. */
|
|
1126
|
+
// ── A REFUSED QUERY IS NAMED AS ONE, with the size of what was sent ─────────────────────────────────
|
|
1127
|
+
//
|
|
1128
|
+
// The vendor answers a query its search service cannot take with that service's 400, delivered inside
|
|
1129
|
+
// its own 500: `INTERNAL_SERVER_ERROR - 400 on POST request for "…/search"`. Passed on as a bare HTTP 500
|
|
1130
|
+
// it reads as an outage and names nothing an operator can act on. A query that is too wide is the common
|
|
1131
|
+
// case: the service's bound is the number of OR terms in one field, which is about 496, not the query's
|
|
1132
|
+
// length.
|
|
1133
|
+
// The reason says so: the widest OR stack's term count and characters, and this provider's declared
|
|
1134
|
+
// width. It avoids the words `HTTP 400`, which providerRejectedTheQuery reads as a cue to retry an owner
|
|
1135
|
+
// stack on the caller's own term; that behaviour is unchanged.
|
|
1136
|
+
//
|
|
1137
|
+
// AND IT IS A CAPABILITY GAP. The service refused the query as written and will refuse the identical query
|
|
1138
|
+
// every time, so the reason carries CAPABILITY_GAP_MARKER: the plan executor defers the slice as a
|
|
1139
|
+
// disclosed gap at once, as it does a query buildSearchRequest refuses before sending, instead of sending
|
|
1140
|
+
// it round the recovery ladder. On production that ladder re-sent a refused 500-term query and spent a
|
|
1141
|
+
// recovery park each time. Every other HTTP failure keeps the transient reading.
|
|
1142
|
+
const WRAPPED_REFUSAL = /\b400\s+on\s+POST\s+request\b/;
|
|
1143
|
+
const widestOrStack = (body) => (body?.searchFields ?? []).reduce((best, f) => {
|
|
1144
|
+
const v = String(f?.value ?? "");
|
|
1145
|
+
const terms = v ? v.split(" OR ").length : 0;
|
|
1146
|
+
return terms > best.terms ? { terms, chars: v.length } : best;
|
|
1147
|
+
}, { terms: 0, chars: 0 });
|
|
1148
|
+
export function countFailureReason(status, words, body) {
|
|
1149
|
+
if (status !== 500 || !WRAPPED_REFUSAL.test(String(words ?? ""))) return `HTTP ${status}${words ? `: ${words}` : ""}`;
|
|
1150
|
+
const w = widestOrStack(body);
|
|
1151
|
+
// THE NUMBERS COME LAST. The executor clips a failed slice's reason to its head and tail, and its own
|
|
1152
|
+
// prefixes fill the head, so a count placed early is elided; the tail is what a run's record keeps.
|
|
1153
|
+
return `${CAPABILITY_GAP_MARKER} register_refused_query: the register refused this query as malformed (a 400 inside its `
|
|
1154
|
+
+ `HTTP 500). Provider's words: ${words}. Sent ${w.terms} OR terms (${w.chars} characters); this provider's declared `
|
|
1155
|
+
+ `width is ${CAPABILITIES.maxOrWidth} terms.`;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1123
1158
|
async function countOnce(apiKey, base, p, tctx) {
|
|
1124
1159
|
let body;
|
|
1125
1160
|
try { body = buildSearchRequest(p); }
|
|
@@ -1129,11 +1164,12 @@ async function countOnce(apiKey, base, p, tctx) {
|
|
|
1129
1164
|
// Marked as a capability gap so the plan executor DEFERS the slice — a disclosed coverage row the
|
|
1130
1165
|
// lawyer reads — instead of stamping a transient error and grinding the whole run to a fan-in
|
|
1131
1166
|
// StageFailure over an answer that will never differ. A real HTTP failure below keeps the transient
|
|
1132
|
-
// reading and rides the repair ladder, exactly as before
|
|
1167
|
+
// reading and rides the repair ladder, exactly as before, except a query the service refused as written
|
|
1168
|
+
// (countFailureReason above).
|
|
1133
1169
|
catch (e) { return { ok: false, total: null, per_office: null, reason: `${CAPABILITY_GAP_MARKER} ${e.message}` }; }
|
|
1134
1170
|
|
|
1135
1171
|
const r = await clarivateFetch(apiKey, base, "/count", { body, tctx: { ...tctx, target: String(echoOf(p)).slice(0, 200) } });
|
|
1136
|
-
if (!r.ok) return { ok: false, total: null, per_office: null, reason:
|
|
1172
|
+
if (!r.ok) return { ok: false, total: null, per_office: null, reason: countFailureReason(r.status, errText(r), body) };
|
|
1137
1173
|
// Named rather than folded into the "no counts{}" branch below: both refuse (total null, never 0),
|
|
1138
1174
|
// but someone repairing a run needs to know the response was CUT, not that the provider answered in
|
|
1139
1175
|
// a shape we did not expect.
|
|
@@ -1734,7 +1770,7 @@ export async function doEnumerate(apiKey, base, params, tctx) {
|
|
|
1734
1770
|
// dictated a count instead of an enumerate. POST /search FAILS LOUD past 30000 (tooManyResults),
|
|
1735
1771
|
// so routing the count probe through it would return an ERROR string, the executor would stamp the
|
|
1736
1772
|
// block error:true, and a SANCTIONED CROWD would be counted MISSING — doctrine 5 inverted. POST
|
|
1737
|
-
// /count never fails on magnitude
|
|
1773
|
+
// /count never fails on magnitude and carries per-office truth.
|
|
1738
1774
|
// `countParams` is therefore {}: buildSearchRequest has no `limit` knob to honour, and passing a
|
|
1739
1775
|
// dead parameter would only imply one exists.
|
|
1740
1776
|
//
|
|
@@ -133,6 +133,10 @@ export const CAPABILITIES = Object.freeze({
|
|
|
133
133
|
// Several goods terms become several `product:` clauses, and within one field this query language
|
|
134
134
|
// ORs them implicitly — the same "any of these words" the explicit OR writes elsewhere.
|
|
135
135
|
goodsTextListOr: true,
|
|
136
|
+
// The shortest term the contains form accepts: none known, so none declared. The vendor publishes no
|
|
137
|
+
// public reference for this API (none was found on 2026-09-22), and no short-term refusal has been
|
|
138
|
+
// measured here. `null` keeps the contains form at every length; see clarivate/capabilities.js.
|
|
139
|
+
containsMinLength: null,
|
|
136
140
|
// ── WHICH FORM OF A NON-LATIN MARK DOES THE INDEX HOLD? ──────────────────────────────────────────
|
|
137
141
|
// `true` = the CHARACTERS. A native-script term is a legitimate, productive query here and MUST be
|
|
138
142
|
// sent — the shared executor's script-form refusal (providers/_shared/script-form.mjs) is switched
|
|
@@ -154,6 +154,23 @@ export const CAPABILITIES = Object.freeze({
|
|
|
154
154
|
// So the compiler does not build a multi-term goods entry for this provider, and it must never fall
|
|
155
155
|
// back to a space-joined string. No coverage is lost: the broad class-wide sweep still runs.
|
|
156
156
|
goodsTextListOr: false,
|
|
157
|
+
// ── THE SHORTEST TERM THE CONTAINS FORM ACCEPTS: 3 folded characters, documented ────────────────
|
|
158
|
+
//
|
|
159
|
+
// Signa's API reference, "Search trademarks", section "Match modes" (docs.signa.so/api-reference/
|
|
160
|
+
// trademarks/search-trademarks, read 2026-09-22): "Deterministic modes accept a single-character `q`
|
|
161
|
+
// (one non-empty folded char), whereas `similar` requires at least 2 folded chars and `contains`
|
|
162
|
+
// requires at least 3." The same page's validation rules repeat it. A shorter term on `contains` is a
|
|
163
|
+
// hard 400 ("match=contains requires q to be at least 3 characters (after case/accent folding) to
|
|
164
|
+
// avoid an over-broad substring scan").
|
|
165
|
+
//
|
|
166
|
+
// The compiler reads this: a goods-narrowed question or a saturation probe whose term is shorter is
|
|
167
|
+
// asked on the exact form instead, with the same class and goods filters, and the plan entry says so.
|
|
168
|
+
// Exact goes out as the ranked `strategies: ["exact"]`, which `similar`'s 2-character floor governs,
|
|
169
|
+
// so a two-letter mark is answered; a one-letter mark is below both floors and stays refused.
|
|
170
|
+
containsMinLength: 3,
|
|
171
|
+
// The ranked shape's floor: "`similar` requires at least 2 folded chars" (same page). An exact question
|
|
172
|
+
// shorter than this goes out on the deterministic `match`, which takes one; from here up it stays ranked.
|
|
173
|
+
rankedMinLength: 2,
|
|
157
174
|
// Search rows already carry status / nice_classes / owner_name → screening is inline, zero extra calls.
|
|
158
175
|
screenSource: "search-row",
|
|
159
176
|
// No hard result ceiling, and no total to compare one against.
|
|
@@ -168,8 +185,6 @@ export const CAPABILITIES = Object.freeze({
|
|
|
168
185
|
//
|
|
169
186
|
// strategies[] exact | phonetic | fuzzy | prefix — ranked, several per call
|
|
170
187
|
// match similar | exact | starts_with | ends_with | contains — deterministic, one only
|
|
171
|
-
//
|
|
172
|
-
// Every value below was run against the live API with the resulting total recorded.
|
|
173
188
|
predicates: Object.freeze({
|
|
174
189
|
exact: "exact", // strategies[] — the deterministic shape, the ranked one for audit continuity
|
|
175
190
|
// `contains` IS the unanchored mode this contract said did not exist. The old header
|
|
@@ -177,12 +192,11 @@ export const CAPABILITIES = Object.freeze({
|
|
|
177
192
|
// contains slice is a substring sweep, and they return different sets. It never needed to be
|
|
178
193
|
// fuzzy; it needed the deterministic shape, which nothing here had.
|
|
179
194
|
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
// file where the document and the wire disagree, and the wire is the one that answers queries.
|
|
195
|
+
// A LENGTH FLOOR THE VENDOR DOCUMENTS: deterministic modes take a query of 1 character or more,
|
|
196
|
+
// and `contains` demands THREE — "match=contains requires q to be at least 3 characters (after
|
|
197
|
+
// case/accent folding) to avoid an over-broad substring scan." A one- or two-character element
|
|
198
|
+
// therefore cannot ride this predicate, and it fails loud (400) rather than quietly returning a
|
|
199
|
+
// narrower set. `containsMinLength` above declares it, and the compiler asks such a term exactly.
|
|
186
200
|
default: "contains",
|
|
187
201
|
// `starts_with`, NOT the `prefix` strategy. Both exist and the executor uses this one:
|
|
188
202
|
// planPredicateParams emits `match_mode: "starts_with"` for a trailing-`*` entry and never emits
|
|
@@ -30,6 +30,9 @@ import { nonAnswerBodyError, parseJsonBody, unparsedBodyError } from "../../_sha
|
|
|
30
30
|
import { makeEnumerate, isOwnerScoped } from "../../_shared/enumerate.mjs";
|
|
31
31
|
import { makeExecutePlan } from "../../_shared/execute-plan.mjs";
|
|
32
32
|
import CAPABILITIES, { SIGNA_OFFICE_KEYS, OWNER_SCOPED_WINDOW } from "./capabilities.js";
|
|
33
|
+
// The register's own measure of a term's length: case and accents folded, spaces not counted — the same
|
|
34
|
+
// measure its documented floors are stated in (the compiler's foldedTermLength counts the same way).
|
|
35
|
+
const foldedLength = (q) => [...String(q ?? "").normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase().replace(/\s+/g, "")].length;
|
|
33
36
|
import { SIGNA_OFFICE_SNAPSHOT } from "./offices.generated.js";
|
|
34
37
|
|
|
35
38
|
export const DEFAULT_BASE = "https://api.signa.so";
|
|
@@ -660,8 +663,16 @@ export function toSignaParams(p = {}) {
|
|
|
660
663
|
// `match`. exact/phonetic stay on strategies, which is what this provider has always sent and what
|
|
661
664
|
// its fixtures were captured with; the anchored and unanchored modes can only be expressed by
|
|
662
665
|
// `match`, so they select that shape and drop strategies.
|
|
666
|
+
//
|
|
667
|
+
// EXACT BELOW THE RANKED FLOOR ONLY. The ranked shape takes `similar`'s floor of two folded
|
|
668
|
+
// characters, so a one-letter exact question is refused there. The deterministic `match: "exact"`
|
|
669
|
+
// takes one, but it asks a narrower question: "`exact` requires the whole mark to equal `q`", where
|
|
670
|
+
// the ranked `exact` strategy also returns longer marks that contain the term, which is the recall an
|
|
671
|
+
// identical question relies on. So exact stays ranked from the floor up, and only a term below it
|
|
672
|
+
// goes deterministic, where it is answered rather than refused.
|
|
663
673
|
const mode = String(p.match_mode ?? "").trim();
|
|
664
|
-
if (mode === "exact"
|
|
674
|
+
if (mode === "exact" && foldedLength(out.query) < CAPABILITIES.rankedMinLength) out.match = "exact";
|
|
675
|
+
else if (mode === "exact" || mode === "phonetic" || mode === "prefix") out.strategies = [mode];
|
|
665
676
|
else if (mode === "starts_with" || mode === "ends_with" || mode === "contains") out.match = mode;
|
|
666
677
|
// ── `default` IS THE COUNT LANE'S WORD FOR THE SAME UNANCHORED QUERY THE PLAN LANE CALLS `{}` ────
|
|
667
678
|
//
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
// Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
|
|
4
|
+
// demo-evidence.mjs — THE HAND-WRITTEN DIFF MUST BE REVIEWABLE ON ITS OWN.
|
|
5
|
+
//
|
|
6
|
+
// One integration branch carried 37 commits, 2,490 files, 221,105 additions and 410,984 deletions, and
|
|
7
|
+
// most of it was regenerated demo evidence. A reviewer cannot find the source changes inside that, so
|
|
8
|
+
// in practice nobody reads them — the review happens, and it reviews nothing. The evidence is not the
|
|
9
|
+
// problem: it is generated output, it is meant to change wholesale, and it is right that it ships.
|
|
10
|
+
// Mixing it into the same commits as hand-written code is the problem.
|
|
11
|
+
//
|
|
12
|
+
// TWO HALVES, because a convention nobody checks is a convention that lasts one beta:
|
|
13
|
+
//
|
|
14
|
+
// --check a commit that touches `demo/` touches nothing else, across a range
|
|
15
|
+
// --apply re-record `demo/MANIFEST.json` — generator, tree, and the run each product replays
|
|
16
|
+
//
|
|
17
|
+
// The manifest is what makes the regeneration commit self-explaining: without it a reviewer skipping
|
|
18
|
+
// the demo diff is taking on trust that it IS regenerated output rather than a hand edit hidden in
|
|
19
|
+
// 200,000 lines. With it, the thing they are skipping says what produced it.
|
|
20
|
+
|
|
21
|
+
import { execFileSync } from "node:child_process";
|
|
22
|
+
import { readFileSync, writeFileSync, readdirSync, existsSync, statSync } from "node:fs";
|
|
23
|
+
import { dirname, join } from "node:path";
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
25
|
+
|
|
26
|
+
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
27
|
+
export const MANIFEST = join(ROOT, "demo", "MANIFEST.json");
|
|
28
|
+
export const GENERATOR = "scripts/freeze-example-run.mjs";
|
|
29
|
+
|
|
30
|
+
const git = (...args) => execFileSync("git", args, { cwd: ROOT, encoding: "utf8" }).trim();
|
|
31
|
+
|
|
32
|
+
/** Every product directory under `demo/`, with the run it replays. */
|
|
33
|
+
export function products({ root = ROOT } = {}) {
|
|
34
|
+
const dir = join(root, "demo");
|
|
35
|
+
if (!existsSync(dir)) return [];
|
|
36
|
+
return readdirSync(dir)
|
|
37
|
+
.filter((name) => statSync(join(dir, name)).isDirectory())
|
|
38
|
+
.sort()
|
|
39
|
+
.map((name) => {
|
|
40
|
+
let meta = {};
|
|
41
|
+
try { meta = JSON.parse(readFileSync(join(dir, name, "meta.json"), "utf8")); } catch { /* absent is an answer */ }
|
|
42
|
+
return { product: name, runId: meta.runId ?? null, template: meta.template ?? null };
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Is this a commit that mixes generated evidence with hand-written source?
|
|
48
|
+
* Returns the offending paths, or an empty array. PURE given the file list.
|
|
49
|
+
*/
|
|
50
|
+
export function mixedPaths(files) {
|
|
51
|
+
const demo = files.filter((f) => f.startsWith("demo/"));
|
|
52
|
+
if (!demo.length) return []; // touches no evidence — nothing to say
|
|
53
|
+
return files.filter((f) => !f.startsWith("demo/"));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
57
|
+
const apply = process.argv.includes("--apply");
|
|
58
|
+
|
|
59
|
+
if (apply) {
|
|
60
|
+
writeFileSync(MANIFEST, `${JSON.stringify({
|
|
61
|
+
note: "What produced the evidence under demo/. Re-recorded by scripts/demo-evidence.mjs --apply in the same commit that regenerates it.",
|
|
62
|
+
generator: GENERATOR,
|
|
63
|
+
recordedFromTree: git("rev-parse", "HEAD"),
|
|
64
|
+
products: products(),
|
|
65
|
+
}, null, 2)}\n`);
|
|
66
|
+
console.log(`recorded ${products().length} demo product(s) against ${git("rev-parse", "--short", "HEAD")}`);
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// --check: every commit in the range keeps its evidence to itself.
|
|
71
|
+
const base = process.argv[process.argv.indexOf("--base") + 1] || "origin/main";
|
|
72
|
+
|
|
73
|
+
// ── A BASE THIS CHECKOUT CANNOT RESOLVE IS A FAILURE TO LOOK, AND IT SAYS SO ────────────────────
|
|
74
|
+
//
|
|
75
|
+
// A shallow clone fetches one branch, so `origin/main` is not a ref in it and `rev-list base..HEAD`
|
|
76
|
+
// dies on an ambiguous argument — a stack trace naming a line of this file, which reads as a broken
|
|
77
|
+
// script rather than as a checkout that cannot answer the question. It is neither a clean range nor
|
|
78
|
+
// a mixed one: nothing was compared. Falling back to another base would be worse, because a range
|
|
79
|
+
// measured against something the caller did not ask for reports a clean result for a population it
|
|
80
|
+
// never examined.
|
|
81
|
+
try { git("rev-parse", "--verify", "--quiet", `${base}^{commit}`); }
|
|
82
|
+
catch {
|
|
83
|
+
console.error(`demo-evidence: this checkout has no "${base}", so no commit was compared.`);
|
|
84
|
+
console.error("");
|
|
85
|
+
console.error("Nothing is known about the range either way — this is not a clean result. A shallow");
|
|
86
|
+
console.error("checkout carries only the branch it fetched; fetch the base ref, or name one this");
|
|
87
|
+
console.error("clone holds with --base.");
|
|
88
|
+
process.exit(2);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const shas = git("rev-list", `${base}..HEAD`).split("\n").filter(Boolean);
|
|
92
|
+
const bad = [];
|
|
93
|
+
for (const sha of shas) {
|
|
94
|
+
const files = git("show", "--name-only", "--pretty=format:", sha).split("\n").filter(Boolean);
|
|
95
|
+
const mixed = mixedPaths(files);
|
|
96
|
+
if (mixed.length) bad.push({ sha, mixed });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!bad.length) {
|
|
100
|
+
console.log(`demo-evidence: ${shas.length} commit(s) against ${base}; none mixes generated evidence with source.`);
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.error("demo-evidence: generated evidence is mixed with hand-written source.\n");
|
|
105
|
+
console.error("A reviewer cannot find the source diff inside a regeneration, so in practice nobody reads it.\n");
|
|
106
|
+
for (const { sha, mixed } of bad) {
|
|
107
|
+
console.error(` ${sha.slice(0, 9)} also touches ${mixed.length} non-demo path(s):`);
|
|
108
|
+
for (const f of mixed.slice(0, 5)) console.error(` ${f}`);
|
|
109
|
+
if (mixed.length > 5) console.error(` …and ${mixed.length - 5} more`);
|
|
110
|
+
}
|
|
111
|
+
console.error("\nSplit the regeneration into its own commit:");
|
|
112
|
+
console.error(" git reset HEAD~ && git add demo && git commit && git add -A && git commit");
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
package/scripts/e2e.mjs
CHANGED
|
@@ -3207,7 +3207,7 @@ async function cmdReport(id, { round: requestedToken = null } = {}) {
|
|
|
3207
3207
|
// Five test runs did exactly this in the seven days to 2026-08-25. The answer to "was this
|
|
3208
3208
|
// delivered" is therefore its own line, above, in the words used for a failed order.
|
|
3209
3209
|
console.log(` ${deliveryLine(st)}`);
|
|
3210
|
-
console.log(` state=${st.state ?? "?"}
|
|
3210
|
+
console.log(` state=${st.state ?? "?"} tier=${st.tier ?? "-"} signoff=${st.review?.signoff ?? st.verdict ?? "-"} sendPending=${st.sendPending}`);
|
|
3211
3211
|
|
|
3212
3212
|
// Which skills changed since this run started — the resume question, stated as fact not as a rule.
|
|
3213
3213
|
// — THE THREE ANSWERS THIS CHECK CAN GIVE, and it used to give one of them silently.
|
package/scripts/engine-probe.mjs
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
// ── WHY THIS EXISTS ─────────────────────────────────────────────────────────────────────────────────
|
|
11
11
|
//
|
|
12
12
|
// `probeEngineTurn` is the smallest thing that genuinely exercises an engine: one Haiku-tier turn at low
|
|
13
|
-
// effort
|
|
14
|
-
// skills dir and no run dir. It answers the one question an operator asks before spending anything —
|
|
15
|
-
// CAN this box run a turn, and under the billing mode it thinks it is using.
|
|
13
|
+
// effort, through the adapter's own spawn path, asked to call the one tool on the probe's own server, with
|
|
14
|
+
// no skills dir and no run dir. It answers the one question an operator asks before spending anything —
|
|
15
|
+
// CAN this box run a turn that uses a tool, and under the billing mode it thinks it is using.
|
|
16
16
|
//
|
|
17
17
|
// It had no way to be asked. Its only callers were `bin/onboard.mjs` (the setup wizard, which asks it
|
|
18
18
|
// once, inside a flow) and `preflightEngineTurn` in `pipeline.mjs` (which asks it at the start of a run,
|
|
@@ -28,8 +28,9 @@
|
|
|
28
28
|
// ── WHAT A GREEN PROBE DOES NOT PROVE, from the module's own header ─────────────────────────────────
|
|
29
29
|
//
|
|
30
30
|
// The cheap tier only. Both tiers ride one credential, so AUTH is proven for all of them — a per-tier
|
|
31
|
-
// model entitlement or per-tier quota is not.
|
|
32
|
-
//
|
|
31
|
+
// model entitlement or per-tier quota is not. It calls one tool on its own server, so the tool path is
|
|
32
|
+
// proven and no stage's tool server is; it sets up no skills and no run dir. A probe that says `ok` says
|
|
33
|
+
// the door opens.
|
|
33
34
|
|
|
34
35
|
// FIRST IMPORT, AND IT HAS TO BE. This entry statically reaches driver.config.mjs, which captures
|
|
35
36
|
// env at module top — and every static import evaluates before this file's own body runs, so applying the
|
package/scripts/env-audit.mjs
CHANGED
|
@@ -557,7 +557,7 @@ export function auditEnv(root = ROOT) {
|
|
|
557
557
|
// 4 read RIGHT NOW through an idiom no `env`-prefixed regex can reach —
|
|
558
558
|
// envValue("CLEAROTRON_MAX_RETRIES") and
|
|
559
559
|
// envValue("CLEAROTRON_RATE_LIMIT_DEFAULT_BACKOFF_MS") (driver.config.mjs:486,
|
|
560
|
-
// :494), envOn("CLEAROTRON_DUMP_JSON") (gateway.mjs
|
|
560
|
+
// :494), envOn("CLEAROTRON_DUMP_JSON") (runStageLadder() in gateway.mjs), and
|
|
561
561
|
// `export const API_KEY_ENV = "USPTO_API_KEY"` (providers/uspto-local/src/sync.js:77).
|
|
562
562
|
// The name is a STRING LITERAL in every one; it just never sits beside an
|
|
563
563
|
// `env` token. USPTO_API_KEY is a credential.
|
|
@@ -69,8 +69,8 @@ const FROZEN_FILES = [
|
|
|
69
69
|
{ path: "_driver/senior-rights.json", why: "publish/index.mjs:787 seniorRights" },
|
|
70
70
|
{ path: "_driver/verdict.json", why: "publish/index.mjs:792 verdictInfo" },
|
|
71
71
|
{ path: "_driver/framework.json", why: "publish/index.mjs, the frozen band vocabulary the run was rated under" },
|
|
72
|
-
{ path: "_driver/register-plan.json", why: "publish/index.mjs:
|
|
73
|
-
{ path: "_driver/instructed-scope.json", why: "publish/index.mjs:
|
|
72
|
+
{ path: "_driver/register-plan.json", why: "publish/index.mjs:910 scopeBasis" },
|
|
73
|
+
{ path: "_driver/instructed-scope.json", why: "publish/index.mjs:911 searchedJurisdictions, the fallback for register-plan" },
|
|
74
74
|
{ path: "_driver/enforcer-signals.json", why: "`esPath` declared in index.mjs" },
|
|
75
75
|
{ path: "_driver/predelivery-lint.json", why: "publish/index.mjs:172 lintSink" },
|
|
76
76
|
{ path: "_driver/escalation-state.json", why: "publish/index.mjs:173 escSink" },
|
|
@@ -118,7 +118,7 @@ const KNOCKOUT_FILES = [
|
|
|
118
118
|
// knockout report render empty (publish/knockout.mjs:140-155). Named by stages-knockout.mjs:32,41.
|
|
119
119
|
{ path: "_driver/register-counts.json", why: "publish/knockout.mjs:140-155 counted figures + the Register column" },
|
|
120
120
|
{ path: "_driver/register-records.json", why: "stages-knockout.mjs:41 the terms behind the close-variation axis" },
|
|
121
|
-
{ path: "_driver/instructed-scope.json", why: "publish/index.mjs:
|
|
121
|
+
{ path: "_driver/instructed-scope.json", why: "publish/index.mjs:911 searchedJurisdictions, the fallback for register-plan" },
|
|
122
122
|
];
|
|
123
123
|
|
|
124
124
|
/** The allowlist for a template. One place, so a new template cannot half-exist. */
|