clearotron 0.5.0-beta.2 → 0.5.0-beta.3
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 +7 -0
- package/build-info.json +2 -2
- package/docs/architecture/04-configuration-reference.md +1 -0
- package/docs/architecture/05-config-governance.md +1 -1
- package/driver/CHANGELOG.md +11 -0
- package/driver/band-shape.mjs +4 -4
- package/driver/contract-vocabulary.mjs +3 -3
- package/driver/coverage-form-io.mjs +3 -1
- package/driver/coverage-form.mjs +21 -2
- package/driver/coverage-ledger.mjs +7 -1
- package/driver/declination-call.mjs +33 -16
- package/driver/degraded-parts.mjs +6 -0
- package/driver/engine/mcp/clarivate-server.mjs +2 -0
- package/driver/engine/mcp/corsearch-server.mjs +2 -0
- package/driver/engine/mcp/declination-server.mjs +4 -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 +15 -0
- package/driver/engine/mcp/held-answers.mjs +11 -3
- package/driver/engine/mcp/signa-server.mjs +2 -0
- package/driver/engine/mcp/supplemental.mjs +5 -2
- package/driver/engine/mcp/uspto-local-server.mjs +2 -0
- package/driver/envelope-settle.mjs +13 -3
- package/driver/gateway.mjs +31 -10
- package/driver/package.json +1 -1
- package/driver/parts-not-completed.mjs +52 -11
- package/driver/pipeline-knockout.mjs +5 -4
- package/driver/pipeline.mjs +436 -72
- package/driver/placement-decisions.mjs +4 -4
- package/driver/placement-form-io.mjs +21 -1
- package/driver/placement-form.mjs +25 -15
- package/driver/placement-union.mjs +53 -8
- package/driver/publish/index.mjs +7 -3
- package/driver/runner.mjs +4 -4
- package/driver/skills/clearance-register/digest.md +1 -1
- package/driver/skills/clearance-search/phase2-execution.md +3 -3
- package/driver/skills/clearance-search/synthesis-rules.md +3 -3
- package/driver/skills/placement-inquiry/SKILL.md +3 -3
- package/driver/stages.mjs +5 -5
- package/driver/suite-census.json +84 -12
- package/driver/verify.mjs +6 -6
- package/mcp-server/CHANGELOG.md +4 -0
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/_shared/README.md +1 -0
- package/providers/_shared/enumerate.mjs +4 -3
- package/providers/_shared/execute-plan.mjs +24 -7
- package/providers/_shared/send-until.mjs +25 -0
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/scripts/band-shape-probe.mjs +1 -1
- package/scripts/e2e-first-time.mjs +2 -1
- package/shared/names-in-force.mjs +1 -0
- package/shared/slices-taken-out.mjs +65 -0
- package/skills/clearotron-account/SKILL.md +1 -1
- package/skills/clearotron-ops/SKILL.md +1 -1
package/driver/suite-census.json
CHANGED
|
@@ -81,6 +81,12 @@
|
|
|
81
81
|
"skips": 0,
|
|
82
82
|
"todos": 0
|
|
83
83
|
},
|
|
84
|
+
"a-blocking-email-keeps-the-reports-client-actions.test.mjs": {
|
|
85
|
+
"tests": 3,
|
|
86
|
+
"asserts": 7,
|
|
87
|
+
"skips": 0,
|
|
88
|
+
"todos": 0
|
|
89
|
+
},
|
|
84
90
|
"a-borrowed-node-modules-is-not-a-licence-defect.test.mjs": {
|
|
85
91
|
"tests": 8,
|
|
86
92
|
"asserts": 28,
|
|
@@ -669,6 +675,12 @@
|
|
|
669
675
|
"skips": 0,
|
|
670
676
|
"todos": 0
|
|
671
677
|
},
|
|
678
|
+
"a-failure-notice-goes-to-whoever-asked.test.mjs": {
|
|
679
|
+
"tests": 3,
|
|
680
|
+
"asserts": 10,
|
|
681
|
+
"skips": 0,
|
|
682
|
+
"todos": 0
|
|
683
|
+
},
|
|
672
684
|
"a-family-withheld-by-judgment-is-not-read-as-awaiting.test.mjs": {
|
|
673
685
|
"tests": 6,
|
|
674
686
|
"asserts": 15,
|
|
@@ -705,6 +717,24 @@
|
|
|
705
717
|
"skips": 0,
|
|
706
718
|
"todos": 0
|
|
707
719
|
},
|
|
720
|
+
"a-findings-record-broken-after-synthesis-is-restored-at-delivery.pipeline.test.mjs": {
|
|
721
|
+
"tests": 6,
|
|
722
|
+
"asserts": 38,
|
|
723
|
+
"skips": 0,
|
|
724
|
+
"todos": 0
|
|
725
|
+
},
|
|
726
|
+
"a-finished-turn-is-not-a-timeout-for-a-request-inside-it.test.mjs": {
|
|
727
|
+
"tests": 5,
|
|
728
|
+
"asserts": 23,
|
|
729
|
+
"skips": 0,
|
|
730
|
+
"todos": 0
|
|
731
|
+
},
|
|
732
|
+
"a-floor-is-set-aside-on-its-goods-or-mark-on-the-evidence.test.mjs": {
|
|
733
|
+
"tests": 6,
|
|
734
|
+
"asserts": 19,
|
|
735
|
+
"skips": 0,
|
|
736
|
+
"todos": 0
|
|
737
|
+
},
|
|
708
738
|
"a-floor-row-whose-owner-was-set-aside-is-answered.test.mjs": {
|
|
709
739
|
"tests": 2,
|
|
710
740
|
"asserts": 11,
|
|
@@ -803,7 +833,7 @@
|
|
|
803
833
|
},
|
|
804
834
|
"a-goods-test-miss-is-not-called-other-goods.test.mjs": {
|
|
805
835
|
"tests": 2,
|
|
806
|
-
"asserts":
|
|
836
|
+
"asserts": 7,
|
|
807
837
|
"skips": 0,
|
|
808
838
|
"todos": 0
|
|
809
839
|
},
|
|
@@ -827,7 +857,7 @@
|
|
|
827
857
|
},
|
|
828
858
|
"a-group-row-gives-every-owner-in-it-a-fate.test.mjs": {
|
|
829
859
|
"tests": 7,
|
|
830
|
-
"asserts":
|
|
860
|
+
"asserts": 37,
|
|
831
861
|
"skips": 0,
|
|
832
862
|
"todos": 0
|
|
833
863
|
},
|
|
@@ -1557,6 +1587,12 @@
|
|
|
1557
1587
|
"skips": 0,
|
|
1558
1588
|
"todos": 0
|
|
1559
1589
|
},
|
|
1590
|
+
"a-register-search-that-cannot-finish-is-delivered-as-not-searched.pipeline.test.mjs": {
|
|
1591
|
+
"tests": 3,
|
|
1592
|
+
"asserts": 22,
|
|
1593
|
+
"skips": 0,
|
|
1594
|
+
"todos": 0
|
|
1595
|
+
},
|
|
1560
1596
|
"a-register-unit-follow-up-carries-the-one-market-list-and-the-classes.test.mjs": {
|
|
1561
1597
|
"tests": 4,
|
|
1562
1598
|
"asserts": 10,
|
|
@@ -1815,12 +1851,24 @@
|
|
|
1815
1851
|
"skips": 0,
|
|
1816
1852
|
"todos": 0
|
|
1817
1853
|
},
|
|
1854
|
+
"a-search-a-check-cannot-prove-is-delivered-as-not-searched.pipeline.test.mjs": {
|
|
1855
|
+
"tests": 11,
|
|
1856
|
+
"asserts": 66,
|
|
1857
|
+
"skips": 0,
|
|
1858
|
+
"todos": 0
|
|
1859
|
+
},
|
|
1818
1860
|
"a-search-asks-the-web-on-the-settings-its-report-type-pins.test.mjs": {
|
|
1819
1861
|
"tests": 6,
|
|
1820
1862
|
"asserts": 22,
|
|
1821
1863
|
"skips": 0,
|
|
1822
1864
|
"todos": 0
|
|
1823
1865
|
},
|
|
1866
|
+
"a-search-taken-out-of-the-band-is-never-read-as-answered.test.mjs": {
|
|
1867
|
+
"tests": 5,
|
|
1868
|
+
"asserts": 23,
|
|
1869
|
+
"skips": 0,
|
|
1870
|
+
"todos": 0
|
|
1871
|
+
},
|
|
1824
1872
|
"a-search-the-engine-chose-not-to-run-is-not-reported-as-unfinished.test.mjs": {
|
|
1825
1873
|
"tests": 5,
|
|
1826
1874
|
"asserts": 13,
|
|
@@ -1905,6 +1953,12 @@
|
|
|
1905
1953
|
"skips": 0,
|
|
1906
1954
|
"todos": 0
|
|
1907
1955
|
},
|
|
1956
|
+
"a-slice-taken-out-of-the-band-is-open-for-its-own-reason.test.mjs": {
|
|
1957
|
+
"tests": 4,
|
|
1958
|
+
"asserts": 12,
|
|
1959
|
+
"skips": 0,
|
|
1960
|
+
"todos": 0
|
|
1961
|
+
},
|
|
1908
1962
|
"a-slice-the-fold-refused-as-already-asked-is-not-a-dispatch-failure.test.mjs": {
|
|
1909
1963
|
"tests": 10,
|
|
1910
1964
|
"asserts": 32,
|
|
@@ -2067,6 +2121,12 @@
|
|
|
2067
2121
|
"skips": 0,
|
|
2068
2122
|
"todos": 0
|
|
2069
2123
|
},
|
|
2124
|
+
"a-turns-tool-servers-are-told-when-to-stop-sending.test.mjs": {
|
|
2125
|
+
"tests": 3,
|
|
2126
|
+
"asserts": 12,
|
|
2127
|
+
"skips": 0,
|
|
2128
|
+
"todos": 0
|
|
2129
|
+
},
|
|
2070
2130
|
"a-two-form-matter-owes-a-read-per-form.test.mjs": {
|
|
2071
2131
|
"tests": 8,
|
|
2072
2132
|
"asserts": 25,
|
|
@@ -2403,6 +2463,12 @@
|
|
|
2403
2463
|
"skips": 0,
|
|
2404
2464
|
"todos": 0
|
|
2405
2465
|
},
|
|
2466
|
+
"an-identical-mark-is-declined-on-its-goods-only-on-goods-held-complete.test.mjs": {
|
|
2467
|
+
"tests": 6,
|
|
2468
|
+
"asserts": 19,
|
|
2469
|
+
"skips": 0,
|
|
2470
|
+
"todos": 0
|
|
2471
|
+
},
|
|
2406
2472
|
"an-incumbent-alert-adds-no-class.test.mjs": {
|
|
2407
2473
|
"tests": 2,
|
|
2408
2474
|
"asserts": 3,
|
|
@@ -2597,7 +2663,7 @@
|
|
|
2597
2663
|
},
|
|
2598
2664
|
"band-truth-gate.pipeline.test.mjs": {
|
|
2599
2665
|
"tests": 3,
|
|
2600
|
-
"asserts":
|
|
2666
|
+
"asserts": 16,
|
|
2601
2667
|
"skips": 0,
|
|
2602
2668
|
"todos": 0
|
|
2603
2669
|
},
|
|
@@ -3653,7 +3719,7 @@
|
|
|
3653
3719
|
},
|
|
3654
3720
|
"engine.anthropic.test.mjs": {
|
|
3655
3721
|
"tests": 70,
|
|
3656
|
-
"asserts":
|
|
3722
|
+
"asserts": 263,
|
|
3657
3723
|
"skips": 2,
|
|
3658
3724
|
"todos": 0
|
|
3659
3725
|
},
|
|
@@ -4385,7 +4451,7 @@
|
|
|
4385
4451
|
},
|
|
4386
4452
|
"named-band-missing.test.mjs": {
|
|
4387
4453
|
"tests": 7,
|
|
4388
|
-
"asserts":
|
|
4454
|
+
"asserts": 24,
|
|
4389
4455
|
"skips": 0,
|
|
4390
4456
|
"todos": 0
|
|
4391
4457
|
},
|
|
@@ -7103,7 +7169,7 @@
|
|
|
7103
7169
|
},
|
|
7104
7170
|
"the-review-after-a-repair-reads-only-what-the-repair-changed.pipeline.test.mjs": {
|
|
7105
7171
|
"tests": 24,
|
|
7106
|
-
"asserts":
|
|
7172
|
+
"asserts": 193,
|
|
7107
7173
|
"skips": 0,
|
|
7108
7174
|
"todos": 0
|
|
7109
7175
|
},
|
|
@@ -7120,8 +7186,8 @@
|
|
|
7120
7186
|
"todos": 0
|
|
7121
7187
|
},
|
|
7122
7188
|
"the-review-is-told-the-floors-placement-set-aside.test.mjs": {
|
|
7123
|
-
"tests":
|
|
7124
|
-
"asserts":
|
|
7189
|
+
"tests": 11,
|
|
7190
|
+
"asserts": 27,
|
|
7125
7191
|
"skips": 0,
|
|
7126
7192
|
"todos": 0
|
|
7127
7193
|
},
|
|
@@ -7391,7 +7457,7 @@
|
|
|
7391
7457
|
},
|
|
7392
7458
|
"the-words-the-step-reads-match-what-the-driver-checks.test.mjs": {
|
|
7393
7459
|
"tests": 9,
|
|
7394
|
-
"asserts":
|
|
7460
|
+
"asserts": 45,
|
|
7395
7461
|
"skips": 0,
|
|
7396
7462
|
"todos": 0
|
|
7397
7463
|
},
|
|
@@ -7427,7 +7493,7 @@
|
|
|
7427
7493
|
},
|
|
7428
7494
|
"the-writer-owes-positions-not-records.test.mjs": {
|
|
7429
7495
|
"tests": 6,
|
|
7430
|
-
"asserts":
|
|
7496
|
+
"asserts": 21,
|
|
7431
7497
|
"skips": 0,
|
|
7432
7498
|
"todos": 0
|
|
7433
7499
|
},
|
|
@@ -7953,8 +8019,8 @@
|
|
|
7953
8019
|
"todos": 0
|
|
7954
8020
|
},
|
|
7955
8021
|
"connector-skills-are-one-copy.test.mjs": {
|
|
7956
|
-
"tests":
|
|
7957
|
-
"asserts":
|
|
8022
|
+
"tests": 6,
|
|
8023
|
+
"asserts": 31,
|
|
7958
8024
|
"skips": 0,
|
|
7959
8025
|
"todos": 0
|
|
7960
8026
|
},
|
|
@@ -8681,6 +8747,12 @@
|
|
|
8681
8747
|
"skips": 0,
|
|
8682
8748
|
"todos": 0
|
|
8683
8749
|
},
|
|
8750
|
+
"providers/_shared/test/a-plan-call-past-its-time-to-stop-sending-returns-what-it-holds.test.mjs": {
|
|
8751
|
+
"tests": 5,
|
|
8752
|
+
"asserts": 19,
|
|
8753
|
+
"skips": 0,
|
|
8754
|
+
"todos": 0
|
|
8755
|
+
},
|
|
8684
8756
|
"providers/_shared/test/a-question-with-no-regions-is-asked-to-the-orders-scope.test.mjs": {
|
|
8685
8757
|
"tests": 3,
|
|
8686
8758
|
"asserts": 6,
|
package/driver/verify.mjs
CHANGED
|
@@ -2689,12 +2689,12 @@ function placementAccountVerdict(dir) {
|
|
|
2689
2689
|
const rid = rowOf.get(id), cause = rid ? causeOf.get(rid) : null;
|
|
2690
2690
|
return `${id}${group ? ` in (${group})` : ""}${cause ? `, its row ${rid} (${cause})` : ""}`;
|
|
2691
2691
|
});
|
|
2692
|
-
const finishRow = floors.some((f) => f.includes(", its row ")) ? " Where a floor's row is named, finish that row:
|
|
2693
|
-
+ "sheet-2
|
|
2692
|
+
const finishRow = floors.some((f) => f.includes(", its row ")) ? " Where a floor's row is named, finish that row: give it "
|
|
2693
|
+
+ "headline-candidate or sheet-2, and a reason stating the ground." : "";
|
|
2694
2694
|
if (unjudged.length) {
|
|
2695
2695
|
const named = unjudged.slice(0, 8).map((u) => `${u.row_id ?? u.select ?? "?"} (${u.cause})`).join(", ");
|
|
2696
|
-
const floorsToo = floors.length ? ` The form gives these floors no fate either: ${capped(floors)}.
|
|
2697
|
-
+ `
|
|
2696
|
+
const floorsToo = floors.length ? ` The form gives these floors no fate either: ${capped(floors)}. Select each at `
|
|
2697
|
+
+ `headline-candidate or sheet-2, or set its line aside by name.${finishRow}` : "";
|
|
2698
2698
|
return fail(`placement_unjudged=${unjudged.length}: ${named}${unjudged.length > 8 ? ", …" : ""} — each of these rows is `
|
|
2699
2699
|
+ "in placement-form.json and its own judgement will not render: a selected row owes a `tier` from the closed set and a `reason` "
|
|
2700
2700
|
+ "stating the ground; a row you wrote in full owes mark, owner and jurisdiction too. Finish these rows in the form; "
|
|
@@ -2755,8 +2755,8 @@ function placementSaidBackVerdict({ refused, floors, also, finishRow = "" }) {
|
|
|
2755
2755
|
if (floors.length)
|
|
2756
2756
|
return fail(`placement_floors_without_fate=${floors.length}: ${capped(floors)} — each is a floor, a live identical or `
|
|
2757
2757
|
+ "near-identical mark in an ordered class, and the form gives it no fate. "
|
|
2758
|
-
+ "
|
|
2759
|
-
+
|
|
2758
|
+
+ "Select it at headline-candidate or sheet-2, or set its line aside by name."
|
|
2759
|
+
+ `${finishRow} Only these are owed; every other row stands.${also}`);
|
|
2760
2760
|
if (refused.length)
|
|
2761
2761
|
return fail(`placement_refused=${refused.length}: ${capped(refused)} — the form refused each of these for the reason given `
|
|
2762
2762
|
+ "with it, and nothing else on the form moved. Send each again corrected, or leave it out; every other row stands.");
|
package/mcp-server/CHANGELOG.md
CHANGED
package/mcp-server/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trademark-artifacts-mcp",
|
|
3
|
-
"version": "0.5.0-beta.
|
|
3
|
+
"version": "0.5.0-beta.3",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"private": true,
|
|
6
6
|
"description": "MCP server to interrogate clearotron trademark-clearance runs — list/read artifacts, trace the full decision flow, telemetry/cost, coverage, single-run search, and a gated single-step what-if. Imports the clearotron-driver read-only; touches no driver/template/deploy files.",
|
package/package.json
CHANGED
package/portal-ui/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "portal-ui",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.5.0-beta.
|
|
5
|
+
"version": "0.5.0-beta.3",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
7
7
|
"description": "The unified trademark portal UI. One address, one login: who you are decides what you see. Built as a static bundle, served by driver/portal-service.mjs — the browser never reaches profile-service or recipe-service.",
|
|
8
8
|
"engines": {
|
|
@@ -24,6 +24,7 @@ documents both: `countProbe` (`"endpoint"` / `"cheap"` / `"none"`) says where a
|
|
|
24
24
|
| `term-shape.mjs` | Whether a string is a searchable mark term and agrees with its predicate (`{ predicate: "exact", term: "WAVO*" }` is a silent clean). |
|
|
25
25
|
| `territory-codes.mjs` | Display name → ISO 3166 / WIPO ST.3 code. `""` means worldwide, `null` means unknown — and never goes to the wire. |
|
|
26
26
|
| `provider-text.mjs` | Clips a vendor message from the MIDDLE, because the word that classifies a refusal sits at the end of the sentence. |
|
|
27
|
+
| `send-until.mjs` | The time after which a plan call sends no further question, handed to each turn's tool servers ahead of the turn's deadline, so a call cut short returns what it holds. |
|
|
27
28
|
| `test/` | Twelve files, provider-agnostic by design — several drive every adapter's real code rather than fixtures of what we believe it returns. |
|
|
28
29
|
|
|
29
30
|
## What reads it
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
import { makeCountProbe, parseToolText, isToolError } from "./count.mjs"; import { floorOf, moreThan } from "../../shared/register-floor.mjs";
|
|
58
58
|
import { guardCountCall, guardToolCall } from "./transport-guard.mjs";
|
|
59
59
|
import { clipProviderText } from "./provider-text.mjs"; // — keep the discriminator
|
|
60
|
+
import { pastSendUntil } from "./send-until.mjs"; // past a call's time to stop sending, a timed-out question is not split again
|
|
60
61
|
|
|
61
62
|
// ── — HOW MUCH OF A PROVIDER ERROR SURVIVES INTO THE BAND BLOCK: ALL OF IT ───────────────────────────
|
|
62
63
|
//
|
|
@@ -448,7 +449,7 @@ export function makeEnumerate(deps) {
|
|
|
448
449
|
}
|
|
449
450
|
// A half that timed out while its sibling answered is the one place fewer regions may help: it is
|
|
450
451
|
// halved in turn, under the same rule.
|
|
451
|
-
const r = timedOut({ r: first })
|
|
452
|
+
const r = timedOut({ r: first }) && !pastSendUntil(tctx)
|
|
452
453
|
? await regionHalves(auth, { ...params, regions: half }, tctx, { ceiling, incomplete, cause: parseToolText(first)?.reason ?? cause, hear })
|
|
453
454
|
: first;
|
|
454
455
|
const parsed = isToolError(r) ? null : parseToolText(r);
|
|
@@ -637,7 +638,7 @@ export function makeEnumerate(deps) {
|
|
|
637
638
|
+ `Provider's own words: ${clipProviderText(c?.reason ?? "", COUNT_PROBE_BUDGET)}`,
|
|
638
639
|
{ crowd_basis: "provider-refused-count", count_unavailable: true });
|
|
639
640
|
}
|
|
640
|
-
if (splittable && isGatewayTimeout(c?.reason)) return regionHalves(auth, params, tctx, { ceiling, incomplete, cause: String(c.reason), hear });
|
|
641
|
+
if (splittable && isGatewayTimeout(c?.reason) && !pastSendUntil(tctx)) return regionHalves(auth, params, tctx, { ceiling, incomplete, cause: String(c.reason), hear });
|
|
641
642
|
return incomplete(0, 0, [],
|
|
642
643
|
`provider error on the count probe before enumeration: ${String(c?.reason ?? "count unavailable")}`, timeoutMark(c?.reason));
|
|
643
644
|
}
|
|
@@ -670,7 +671,7 @@ export function makeEnumerate(deps) {
|
|
|
670
671
|
if (page === 0 && allNames && allNames.length > 1 && listRefusal?.test(String(r.text ?? ""))) {
|
|
671
672
|
return enumerateQuestion(auth, params, tctx, { split, width: Math.ceil(allNames.length / 2) }, said);
|
|
672
673
|
}
|
|
673
|
-
if (splittable && isGatewayTimeout(r.text)) return regionHalves(auth, params, tctx, { ceiling, incomplete, cause: String(r.text), hear });
|
|
674
|
+
if (splittable && isGatewayTimeout(r.text) && !pastSendUntil(tctx)) return regionHalves(auth, params, tctx, { ceiling, incomplete, cause: String(r.text), hear });
|
|
674
675
|
return incomplete(total, results.length, results, `provider error during enumeration (page ${page}): ${String(r.text ?? "")}`, timeoutMark(r.text));
|
|
675
676
|
}
|
|
676
677
|
const parsed = parseToolText(r);
|
|
@@ -20,6 +20,8 @@ import { nativeScriptIndexGap } from "./script-form.mjs"; import { floorOf, more
|
|
|
20
20
|
import { entryTermIssues, goodsTermsList } from "./term-shape.mjs";
|
|
21
21
|
import { awaitsReadingTurn, releasedFamiliesFile } from "./plan-guards.mjs";
|
|
22
22
|
import { FOLLOW_UP_SEARCH_LIMIT, runDriverDirOf, followUpRoom, followUpClass, identicalMarkTerms, recordFollowUpSearch, recordOutstanding } from "./follow-up-searches.mjs";
|
|
23
|
+
import { bandWithSlicesOut, slicesTakenOutByAxis } from "../../shared/slices-taken-out.mjs";
|
|
24
|
+
import { pastSendUntil } from "./send-until.mjs"; // a call past its time to stop sending sends no new question // a band read as answers, with the slices a check could not prove ran taken out
|
|
23
25
|
|
|
24
26
|
/** How soon after an answer lands it is written to the band, while the call goes on (see `keepAnswers` below). */
|
|
25
27
|
export const KEEP_ANSWERS_AFTER_MS = 2000;
|
|
@@ -375,10 +377,20 @@ export function makeExecutePlan(deps) {
|
|
|
375
377
|
try { identicalTerms = identicalMarkTerms(JSON.parse(readFileSync(join(driverDir, "register-plan.json"), "utf8"))); } catch { /* no frozen plan: no identical terms known */ }
|
|
376
378
|
}
|
|
377
379
|
const refused = [];
|
|
380
|
+
// THE BAND AS ANSWERS, for the checks below that read it so: a slice the run's searches record took out of the band,
|
|
381
|
+
// because a check could not prove it ran, is read as the open slice it was delivered as, whatever wrote the band
|
|
382
|
+
// since (shared/slices-taken-out.mjs). The run is the band's own, as the propose tool reads it. The writes below
|
|
383
|
+
// still merge the file as it stands.
|
|
384
|
+
const asAnswers = (file) => {
|
|
385
|
+
const prior = JSON.parse(readFileSync(file, "utf8"));
|
|
386
|
+
return Array.isArray(prior) ? bandWithSlicesOut(prior, slicesTakenOutByAxis(dirname(dirname(outPath))).get(axis)).blocks : prior;
|
|
387
|
+
};
|
|
388
|
+
const bandAsAnswers = () => asAnswers(outPath);
|
|
378
389
|
// The follow-ups this axis's band already answers, read once and only when the net meets a question sent before:
|
|
379
|
-
// an answered one adds nothing when it is sent again, and a failed one counts again
|
|
380
|
-
// its time limit is parked: its band is renamed (`<band>.tainted-<n>`) so the resume runs the
|
|
381
|
-
// re-run sends the same follow-ups. Their answers are in the renamed band, so they count nothing
|
|
390
|
+
// an answered one adds nothing when it is sent again, and a failed one counts again, as does one taken out. A unit
|
|
391
|
+
// whose pass was killed at its time limit is parked: its band is renamed (`<band>.tainted-<n>`) so the resume runs the
|
|
392
|
+
// unit again, and the re-run sends the same follow-ups. Their answers are in the renamed band, so they count nothing
|
|
393
|
+
// either. Every band on record is read as answers, so a slice taken out counts again whichever band holds it.
|
|
382
394
|
let answeredQids = null;
|
|
383
395
|
// This axis's band, and the bands a parked pass left (`<band>.tainted-<n>`), oldest first and the band's own last.
|
|
384
396
|
const bandsOnRecord = () => {
|
|
@@ -393,7 +405,7 @@ export function makeExecutePlan(deps) {
|
|
|
393
405
|
answeredQids = new Set();
|
|
394
406
|
for (const band of bandsOnRecord()) {
|
|
395
407
|
try {
|
|
396
|
-
const prior =
|
|
408
|
+
const prior = asAnswers(band);
|
|
397
409
|
for (const b of Array.isArray(prior) ? prior : []) if (b?.qid && b.error !== true) answeredQids.add(String(b.qid));
|
|
398
410
|
} catch { /* no band yet: nothing answered */ }
|
|
399
411
|
}
|
|
@@ -426,7 +438,7 @@ export function makeExecutePlan(deps) {
|
|
|
426
438
|
const seeded = new Set();
|
|
427
439
|
if (qidsFilter) {
|
|
428
440
|
try {
|
|
429
|
-
const existing =
|
|
441
|
+
const existing = bandAsAnswers(); // a parent taken out of the band releases no child
|
|
430
442
|
if (Array.isArray(existing)) {
|
|
431
443
|
for (const b of existing) {
|
|
432
444
|
if (b && typeof b === "object" && b.qid && typeof b.state === "string" && !qidsFilter.has(b.qid)) {
|
|
@@ -695,7 +707,11 @@ export function makeExecutePlan(deps) {
|
|
|
695
707
|
// change the answer. An unhandled fault here is an unknown of unknown durability, so it keeps the
|
|
696
708
|
// transient reading. The reason names it an EXECUTOR fault so it is never read as something the
|
|
697
709
|
// register said — a block that fabricated a provider response would be the worse defect.
|
|
710
|
+
// PAST ITS TIME TO STOP SENDING, A CALL SENDS NO NEW QUESTION (send-until.mjs). The question is not reached by
|
|
711
|
+
// this call and keeps whatever block the band holds for it; the call returns what it has, naming it.
|
|
712
|
+
const notReached = [];
|
|
698
713
|
const runEntry = async (e) => {
|
|
714
|
+
if (pastSendUntil(tctx)) { notReached.push(e.qid); return; }
|
|
699
715
|
if (driverDir && followUpClass(e, { identicalTerms }) === "counted") {
|
|
700
716
|
const room = followUpRoom(driverDir, FOLLOW_UP_SEARCH_LIMIT, e, { answered: answeredBefore });
|
|
701
717
|
if (!room.open) {
|
|
@@ -754,8 +770,8 @@ export function makeExecutePlan(deps) {
|
|
|
754
770
|
// fringe) gets no block and any stale one is dropped (a fringe slice must never outlive its
|
|
755
771
|
// parent's crowd verdict). This makes the initial call and every warm followup the SAME call.
|
|
756
772
|
// A follow-up the net did not send is not owned by this call: whatever answer it already had stands.
|
|
757
|
-
const refusedSet = new Set(refused);
|
|
758
|
-
const owned = new Set(targeted.map((e) => e.qid).filter((q) => !refusedSet.has(q)));
|
|
773
|
+
const refusedSet = new Set(refused), notReachedSet = new Set(notReached);
|
|
774
|
+
const owned = new Set(targeted.map((e) => e.qid).filter((q) => !refusedSet.has(q) && !notReachedSet.has(q)));
|
|
759
775
|
let preserved = [];
|
|
760
776
|
try {
|
|
761
777
|
const existing = JSON.parse(readFileSync(outPath, "utf8"));
|
|
@@ -793,6 +809,7 @@ export function makeExecutePlan(deps) {
|
|
|
793
809
|
states: Object.fromEntries([...stateByQid].filter(([q]) => !seeded.has(q))),
|
|
794
810
|
...(Object.keys(spellingCounts).length ? { spelling_counts: spellingCounts } : {}),
|
|
795
811
|
...(refused.length ? { outstanding: refused } : {}),
|
|
812
|
+
...(notReached.length ? { not_reached: notReached } : {}),
|
|
796
813
|
}, null, 2) };
|
|
797
814
|
};
|
|
798
815
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
// send-until.mjs — the time after which a plan call sends no further question to the register.
|
|
5
|
+
//
|
|
6
|
+
// A plan call runs inside a model's turn, and the turn has a deadline. When the call outlasted it, the call and the
|
|
7
|
+
// turn ended together, so the unit could not write what the call had answered: on a production run on 2026-10-21 a
|
|
8
|
+
// unit's plan search ran into the register's gateway timeouts on every attempt and never wrote its result. The
|
|
9
|
+
// driver now hands each turn's tool servers a time to stop sending (CLEAROTRON_GATHER_SEND_UNTIL_MS, set per turn by
|
|
10
|
+
// the gateway, ahead of the turn's own deadline by a reserve). Past it, the executor sends no new question and a
|
|
11
|
+
// timed-out question is not split again; the call returns what it holds and the turn has the time to write it.
|
|
12
|
+
//
|
|
13
|
+
// A pure leaf. A call with no time to stop sending, the driver's own dispatches among them, runs as it always did.
|
|
14
|
+
|
|
15
|
+
/** The gateway's reserve for a turn of `timeoutSec`: a quarter of the turn, and never more than five minutes. */
|
|
16
|
+
export const sendReserveMs = (timeoutSec) => Math.min(300_000, Math.max(0, Number(timeoutSec) || 0) * 250);
|
|
17
|
+
|
|
18
|
+
/** Whether a call given `tctx` is past its time to stop sending. False when it has none. */
|
|
19
|
+
export function pastSendUntil(tctx, now = Date.now()) {
|
|
20
|
+
const until = Number(tctx?.sendUntilMs);
|
|
21
|
+
return Number.isFinite(until) && until > 0 && now >= until;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The call context's time to stop sending, read from the environment a turn's tool server was started with. */
|
|
25
|
+
export const sendUntilFromEnv = (env = process.env) => Number(env.CLEAROTRON_GATHER_SEND_UNTIL_MS) || null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trademark-oauth-mcp-bridge",
|
|
3
|
-
"version": "0.5.0-beta.
|
|
3
|
+
"version": "0.5.0-beta.3",
|
|
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).",
|
|
@@ -65,7 +65,7 @@ if (!/^\d+$/.test(String(opts.seed)) || Number(opts.seed) <= 0)
|
|
|
65
65
|
|
|
66
66
|
// ── the inputs deriveBandShape uses, read the same way it reads them ─────────────────────────────────
|
|
67
67
|
//
|
|
68
|
-
// Deliberately mirrors driver/pipeline.mjs:
|
|
68
|
+
// Deliberately mirrors driver/pipeline.mjs:501-563 mergeRegisterBands rather than inventing a gathering step. If the two
|
|
69
69
|
// diverge, this probe measures a shape the run never had.
|
|
70
70
|
|
|
71
71
|
const runDir = opts.run;
|
|
@@ -76,7 +76,7 @@ export const COUNTED_EVENTS = {
|
|
|
76
76
|
"post-repair-correction-unrecoverable": FAILED, "post-repair-state-not-kept": FAILED,
|
|
77
77
|
"digest-flush-timeout": FAILED, "document-coverage-render-failed": FAILED, "doubts-failed": FAILED,
|
|
78
78
|
"evidence-claim-invariant": { kind: FAILED, when: (e) => e.ok === false || Number(e.violations) > 0 },
|
|
79
|
-
"floor-duty-failed": FAILED, "frame-reopen-redigest-failed": FAILED, "goods-evidence-failed": FAILED, "placement-decisions-failed": FAILED, "placement-band-ids-failed": FAILED, "late-evidence-failed": FAILED, "band-refused-records-failed": FAILED, "verdict-write-deferred": FAILED, "ranking-read-by-writer-failed": FAILED, "audit-rebuild-failed": FAILED, "audit-build-failed": FAILED, "write-up-goods-from-failed": FAILED, "house-element-ownership-failed": FAILED,
|
|
79
|
+
"floor-duty-failed": FAILED, "frame-reopen-redigest-failed": FAILED, "goods-evidence-failed": FAILED, "placement-decisions-failed": FAILED, "placement-band-ids-failed": FAILED, "late-evidence-failed": FAILED, "band-refused-records-failed": FAILED, "verdict-write-deferred": FAILED, "ranking-read-by-writer-failed": FAILED, "audit-rebuild-failed": FAILED, "audit-build-failed": FAILED, "write-up-goods-from-failed": FAILED, "declination-goods-states-failed": FAILED, "house-element-ownership-failed": FAILED,
|
|
80
80
|
"intake-asks-missing": FAILED, "invalid-artifact-strikes": FAILED, "knockout-band-checks-inert": FAILED,
|
|
81
81
|
"knockout-in-use-as-unanswered": FAILED, "knockout-ladder-unreadable": FAILED, "knockout-method-checks-inert": FAILED,
|
|
82
82
|
"knockout-owner-checks-failed": FAILED, "knockout-register-records-failed": FAILED, "knockout-sweep-outage": FAILED,
|
|
@@ -231,6 +231,7 @@ export const NOT_COUNTED_EVENTS = {
|
|
|
231
231
|
],
|
|
232
232
|
"the twin of an incident counted under another record — its dispatch, its repair, its clamp or its failure": [
|
|
233
233
|
"assessment-not-completed-delivery", "part-completed", "overview-not-completed-delivery", "review-not-completed-delivery",
|
|
234
|
+
"core-artifact-restored", "searches-not-completed-delivery", "unit-not-completed-delivery",
|
|
234
235
|
"action-reemit", "ask-answer-reemit", "authority-write-denied", "connotation-remedy", "corrections-applied",
|
|
235
236
|
"corrective-cycle-receipt", "corrective-cycle-settled", "corrective-findings-stale", "coverage-absence-rendered",
|
|
236
237
|
"coverage-closure", "coverage-ledger-recovered", "delivery-stale-repair", "delivery-stale-requeued", "envelope-close-rows", "escalation-noop", "floor-duty-after-repair", "writer-duty-after-repair", "writer-duty-after-correction", "guard-clause-replaced", "guard-gap-closed", "verdict-recomputed-gap-closed", "guard-clause-named-elsewhere", "audit-rebuilt", "audit-withdrawn", "record-carry-doubts-again",
|
|
@@ -71,6 +71,7 @@ export const NAMES_IN_FORCE = Object.freeze([
|
|
|
71
71
|
"CLEAROTRON_FRAME_REOPEN_MAX",
|
|
72
72
|
"CLEAROTRON_GATHER_AGENT",
|
|
73
73
|
"CLEAROTRON_GATHER_CONCURRENCY",
|
|
74
|
+
"CLEAROTRON_GATHER_SEND_UNTIL_MS",
|
|
74
75
|
"CLEAROTRON_GATHER_SESSION_ID",
|
|
75
76
|
"CLEAROTRON_GATHER_SESSION_KEY",
|
|
76
77
|
"CLEAROTRON_HARD_MS",
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
// slices-taken-out.mjs — a band read with the slices a check could not prove ran taken out, by every reader of it.
|
|
5
|
+
//
|
|
6
|
+
// The run's searches record (driver/parts-not-completed.mjs, its `searches` part) names each slice taken out of a band
|
|
7
|
+
// and delivered open: its axis, and its qid or, for a slice with none, its query. A unit re-run after the fan-in writes
|
|
8
|
+
// its axis's band whole, so such a slice can come back with the records nobody could prove were searched. Every reader
|
|
9
|
+
// that takes a band's blocks as answers reads the recorded slices out: the pipeline's merge and plan join, the answers
|
|
10
|
+
// the workers share, the propose tool's check on a re-proposed question, and the executor's checks on a follow-up asked
|
|
11
|
+
// before and on a parent before it releases a child. Each reads them out in memory and never by writing the file: a
|
|
12
|
+
// unit or the executor may be writing that band at the same moment.
|
|
13
|
+
//
|
|
14
|
+
// HERE AND NOT IN THE DRIVER, because the executor reads it too and nothing under providers/ imports the driver. Where
|
|
15
|
+
// the record sits is stated here for the same reason, once, for its writer and its readers alike.
|
|
16
|
+
//
|
|
17
|
+
// A pure leaf: node:fs and shared/driver-dir.mjs only.
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import { driverDir } from "./driver-dir.mjs";
|
|
20
|
+
|
|
21
|
+
/** The folder under `_driver/` holding the record of each part a run could not complete. */
|
|
22
|
+
export const NOT_COMPLETED_DIR = "not-completed";
|
|
23
|
+
|
|
24
|
+
/** The record's path for `part`. */
|
|
25
|
+
export function notCompletedPath(runDir, part) {
|
|
26
|
+
return driverDir(runDir, NOT_COMPLETED_DIR, `${part}.json`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The slices the run's searches record took out of the band, by axis, each named by its qid or, with none, its query.
|
|
31
|
+
* Empty when the run has no record, and when its record cannot be read: a damaged record names no slice. IMPURE (reads
|
|
32
|
+
* the record).
|
|
33
|
+
*/
|
|
34
|
+
export function slicesTakenOutByAxis(runDir) {
|
|
35
|
+
const byAxis = new Map();
|
|
36
|
+
let slices = [];
|
|
37
|
+
try { slices = runDir ? JSON.parse(readFileSync(notCompletedPath(runDir, "searches"), "utf8"))?.slices : []; } catch { slices = []; }
|
|
38
|
+
for (const s of Array.isArray(slices) ? slices : []) {
|
|
39
|
+
if (!s?.axis || (!s?.qid && !s?.query)) continue;
|
|
40
|
+
if (!byAxis.has(s.axis)) byAxis.set(s.axis, []);
|
|
41
|
+
byAxis.get(s.axis).push(s);
|
|
42
|
+
}
|
|
43
|
+
return byAxis;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A band's blocks with `slices` taken out: each becomes the executor's deferred block, or is added as one. A slice with
|
|
48
|
+
* no qid is named by its query: the block with no qid on that query becomes a deferred block on it, and nothing is
|
|
49
|
+
* added where the band holds none. PURE.
|
|
50
|
+
*/
|
|
51
|
+
export function bandWithSlicesOut(blocks, slices) {
|
|
52
|
+
const out = Array.isArray(blocks) ? [...blocks] : [];
|
|
53
|
+
const taken = [];
|
|
54
|
+
for (const s of Array.isArray(slices) ? slices : []) {
|
|
55
|
+
const i = s.qid ? out.findIndex((b) => b?.qid === s.qid) : out.findIndex((b) => b && !b.qid && b.query === s.query);
|
|
56
|
+
if (!s.qid && i < 0) continue;
|
|
57
|
+
const was = i >= 0 ? out[i] : null;
|
|
58
|
+
if (was?.error === true && was.deferred === true && was.reason === s.reason) continue;
|
|
59
|
+
const block = { state: "incomplete", ...(s.qid ? { qid: s.qid } : {}), query: String(was?.query ?? s.query ?? s.qid), total_hits: 0, fetched: 0,
|
|
60
|
+
sample: [], error: true, deferred: true, reason: String(s.reason) };
|
|
61
|
+
if (i >= 0) out[i] = block; else out.push(block);
|
|
62
|
+
taken.push(s);
|
|
63
|
+
}
|
|
64
|
+
return { blocks: out, taken };
|
|
65
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: clearotron-account
|
|
3
|
-
description: Work with a trademark clearance account: review the searches already run for it, and commission new ones. Use when the user asks about their clearance searches in general, or wants a new search set up.
|
|
3
|
+
description: "Work with a trademark clearance account: review the searches already run for it, and commission new ones. Use when the user asks about their clearance searches in general, or wants a new search set up."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Working with your trademark clearance searches (account connector)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: clearotron-ops
|
|
3
|
-
description: Drive the clearance engine as an integrator: take an intake brief, start a run, monitor it, and courier the delivery. Use when operating the engine rather than reading its output.
|
|
3
|
+
description: "Drive the clearance engine as an integrator: take an intake brief, start a run, monitor it, and courier the delivery. Use when operating the engine rather than reading its output."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Operating the clearance engine (ops connector) — intake + monitoring
|