clearotron 0.3.1-beta.0 → 0.3.1-beta.2
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 +17 -0
- package/INSTALL.md +1 -1
- package/README.md +6 -1
- package/THIRD-PARTY-NOTICES.md +4 -4
- package/bin/connect.mjs +2 -2
- package/bin/onboard.mjs +96 -11
- package/bin/start.mjs +129 -23
- package/build-info.json +2 -2
- package/docs/architecture/05-config-governance.md +14 -0
- package/driver/CHANGELOG.md +52 -0
- package/driver/citation-census.json +2 -1
- package/driver/common-law-receipts.mjs +78 -1
- package/driver/contract-vocabulary.mjs +6 -6
- package/driver/engine/anthropic-agent.mjs +75 -0
- package/driver/form-neighbourhood.mjs +44 -2
- package/driver/matter-frame-record.mjs +68 -1
- package/driver/named-band.mjs +1 -1
- package/driver/package.json +1 -1
- package/driver/pipeline-knockout.mjs +13 -6
- package/driver/pipeline.mjs +48 -34
- package/driver/portal-config-view.mjs +6 -0
- package/driver/portal-mcp-client.mjs +54 -9
- package/driver/portal-service.mjs +69 -6
- package/driver/profile-page.html +34 -4
- package/driver/profiles.mjs +5 -1
- package/driver/register-availability.mjs +2 -2
- package/driver/register-plan.mjs +22 -0
- package/driver/scope-ledger.mjs +33 -0
- package/driver/skills/prelim-common-law/SKILL.md +3 -1
- package/driver/skills/prelim-search/synthesis-rules.md +3 -1
- package/driver/stages.mjs +10 -1
- package/driver/suite-census.json +82 -58
- package/mcp-server/CHANGELOG.md +16 -0
- package/mcp-server/http-server.mjs +27 -4
- package/mcp-server/lib/cf-access.mjs +16 -2
- package/mcp-server/lib/http-handler.mjs +37 -1
- package/mcp-server/lib/ops.mjs +22 -9
- package/mcp-server/lib/options.mjs +1 -1
- package/mcp-server/lib/plan.mjs +47 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +29 -4
- package/package.json +1 -1
- package/portal-ui/dist/assets/{index-Cv-E_agg.css → index-D5WAoLZI.css} +20 -6
- package/portal-ui/dist/assets/{index-CwPAS0we.js → index-D8ITW-aD.js} +2151 -744
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +6 -6
- package/providers/_shared/term-shape.mjs +6 -0
- package/providers/_shared/territory-codes.mjs +43 -0
- package/providers/clarivate/src/capabilities.js +13 -1
- package/providers/clarivate/src/core.js +21 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/scripts/citation-line-check.mjs +67 -21
- package/scripts/e2e.mjs +4 -2
- package/scripts/import-cycle-check.mjs +34 -3
- package/scripts/live-surface-check.mjs +2 -2
- package/scripts/mint-names-in-force.mjs +21 -2
- package/scripts/purge-runs.mjs +114 -3
- package/scripts/revisit-render-check.mjs +77 -44
- package/scripts/test-run.mjs +7 -0
- package/scripts/travelling-predicates.mjs +1 -1
- package/shared/connect-clients.mjs +95 -16
- package/shared/invocation.mjs +25 -0
- package/shared/mcp-challenge.mjs +32 -0
- package/shared/names-in-force.mjs +2 -0
- package/shared/notice-owed.mjs +62 -0
- package/shared/scope.mjs +66 -5
- package/shared/stdio-connect.mjs +92 -24
- package/shared/trigger-lane.mjs +19 -0
- package/shared/wsl.mjs +15 -0
- package/skills/clearotron-account/SKILL.md +27 -0
- package/skills/clearotron-ops/COURIER.md +8 -1
- package/skills/clearotron-ops/SKILL.md +18 -2
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
|
|
16
16
|
// 6 mandatory store platforms + the general-web cell. Field-scoped cells are additive (a matter can
|
|
17
17
|
// have MORE rows per variant, never fewer).
|
|
18
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { driverDir } from "../shared/driver-dir.mjs"; // — one definition of where a run's _driver/ is
|
|
21
|
+
|
|
18
22
|
export const MIN_CELLS_PER_VARIANT = 7;
|
|
19
23
|
|
|
20
24
|
const norm = (s) => (s || "").trim().replace(/^["'`]+|["'`]+$/g, "").toLowerCase();
|
|
@@ -281,12 +285,39 @@ export function findGridLedgerViolations(manifestOrTerms, ledgerRaw, { minCellsP
|
|
|
281
285
|
// (same contract as findGridLedgerViolations).
|
|
282
286
|
//
|
|
283
287
|
// @returns {Array<{variant:string, missing:string[]}>}
|
|
284
|
-
export function findPlatformIdentityViolations(manifestOrTerms, ledgerRaw, dictatedPlatforms = []) {
|
|
288
|
+
export function findPlatformIdentityViolations(manifestOrTerms, ledgerRaw, dictatedPlatforms = [], { wholeGrid = false } = {}) {
|
|
285
289
|
if (!dictatedPlatforms.length) return [];
|
|
286
290
|
const map = parseGridLedger(ledgerRaw);
|
|
287
291
|
const want = dictatedPlatforms.map(norm);
|
|
288
292
|
const out = [];
|
|
289
293
|
const variants = Array.isArray(manifestOrTerms) ? manifestOrTerms : parseManifestVariants(manifestOrTerms);
|
|
294
|
+
|
|
295
|
+
// ── A CHANNEL NO HALF RAN IS NOBODY'S, AND THAT IS THE ONE THIS COULD NOT SEE ────────────────────
|
|
296
|
+
//
|
|
297
|
+
// The per-variant join below judges a variant that plausibly attempted the whole dictated grid, and
|
|
298
|
+
// skips one whose coverage is partial — deliberately, so the count ladder's carve-outs do not re-fail
|
|
299
|
+
// here. A channel that NO variant reached is partial for every one of them, so every variant was
|
|
300
|
+
// skipped and the channel went unreported.
|
|
301
|
+
//
|
|
302
|
+
// That is what happened on a delivered run. The grid is split in two by term, and both halves carried
|
|
303
|
+
// the same platforms; half A wrote that the repositories were "not in grid mandate, assigned to the
|
|
304
|
+
// parallel half", half B wrote that they were covered "indirectly, via general web search". Neither
|
|
305
|
+
// ran them, each said the other owned them, and the delivered coverage carried it as a NOTE. A pass's
|
|
306
|
+
// statement that another pass owns a channel is not a receipt, and prose is not a state.
|
|
307
|
+
//
|
|
308
|
+
// Reported once, against the whole grid rather than per variant, because the fact is about the
|
|
309
|
+
// channel: nothing in this ledger touched it. The caller fails the fold with it, and the coverage
|
|
310
|
+
// ledger then carries the channel as deferred — open, not run — instead of a sentence.
|
|
311
|
+
const everywhere = new Set();
|
|
312
|
+
for (const cells of map.values()) for (const pl of cells) everywhere.add(pl);
|
|
313
|
+
//
|
|
314
|
+
// ASKED FOR BY THE CALLER, and only where the ledger is the MERGED fold artifact. On one half's own
|
|
315
|
+
// ledger a channel the other half ran is legitimately absent, and reporting it there would re-fail
|
|
316
|
+
// exactly the split this gate was built to allow. The fold is where both halves are in one file, and
|
|
317
|
+
// it is the only place the question "did anybody run this" has an answer.
|
|
318
|
+
const untouched = want.filter((w) => !everywhere.has(w));
|
|
319
|
+
if (wholeGrid && untouched.length && everywhere.size) out.push({ variant: "*", missing: untouched, whole: true });
|
|
320
|
+
|
|
290
321
|
for (const variant of variants) {
|
|
291
322
|
// UNION the " / " family's accounted platforms before judging: workers legitimately re-key
|
|
292
323
|
// between the packed and split forms (copper-conduit, 2026-06-12) and a supplementary closure
|
|
@@ -903,3 +934,49 @@ export function findSimilarListingSignals(findingsContent) {
|
|
|
903
934
|
flush();
|
|
904
935
|
return signals;
|
|
905
936
|
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* THE CHANNELS THE RUN DICTATED AND NOBODY RAN, as coverage-ledger rows.
|
|
940
|
+
*
|
|
941
|
+
* A channel one half says the other owns is nobody's: on a delivered run half A wrote that the
|
|
942
|
+
* repositories were "not in grid mandate, assigned to the parallel half" and half B wrote that they were
|
|
943
|
+
* covered indirectly through general web search. Neither ran them, and the delivered coverage carried it
|
|
944
|
+
* as a NOTE. Prose is not a state, and a reader cannot act on it.
|
|
945
|
+
*
|
|
946
|
+
* A ROW IN THE LEDGER'S OWN SHAPE AND ITS OWN OPEN STATE, never a new one and never a refusal. `deferred`
|
|
947
|
+
* is what this ledger already calls a slice that could not run at all, every consumer already reads it —
|
|
948
|
+
* the deadline envelope re-runs it, the verdict floor clamps over it, the report shows it where open rows
|
|
949
|
+
* are shown — and a disclosed gap is the point. Failing the fold instead would turn a gap the client can
|
|
950
|
+
* see into a clearance that delivers nothing, which is the opposite of what disclosure is for.
|
|
951
|
+
*
|
|
952
|
+
* Reads the run's own two artifacts and answers `[]` for anything it cannot read: no spec, no merged
|
|
953
|
+
* ledger, or an unparseable one. An absence here is not a finding — the receipts gate above owns that
|
|
954
|
+
* question — and inventing a row from a file this could not read would be a gap nobody can close.
|
|
955
|
+
*/
|
|
956
|
+
export function openChannelRows(runDir, io = {}) {
|
|
957
|
+
const read = io.read ?? ((p) => readFileSync(p, "utf8"));
|
|
958
|
+
const exists = io.exists ?? ((p) => existsSync(p));
|
|
959
|
+
// THE SHARED ACCESSOR, not a hand-built path. `shared/driver-dir.mjs` exists to end exactly this:
|
|
960
|
+
// its own header records 1123 hand-built sites across 221 files, and the state that made it
|
|
961
|
+
// indefensible — the hook whose job is policing writes into this subtree computed the subtree's
|
|
962
|
+
// location by hand, like everyone else, so the location was not a decision anybody owned. An arm
|
|
963
|
+
// enforces it; a hand-join here is caught rather than merely untidy.
|
|
964
|
+
const at = (name) => driverDir(runDir, name);
|
|
965
|
+
try {
|
|
966
|
+
if (!exists(at("grid-spec.json")) || !exists(join(runDir, "common-law-grid.json"))) return [];
|
|
967
|
+
const spec = JSON.parse(read(at("grid-spec.json")));
|
|
968
|
+
const platforms = (spec?.platforms ?? []).filter(Boolean);
|
|
969
|
+
if (!platforms.length) return [];
|
|
970
|
+
const ledgerRaw = read(join(runDir, "common-law-grid.json"));
|
|
971
|
+
const terms = (spec?.terms ?? spec?.variants ?? []).filter(Boolean);
|
|
972
|
+
const violations = findPlatformIdentityViolations(terms.length ? terms : [], ledgerRaw, platforms, { wholeGrid: true });
|
|
973
|
+
const whole = violations.find((v) => v.whole);
|
|
974
|
+
return (whole?.missing ?? []).map((platform) => ({
|
|
975
|
+
axis: "common-law",
|
|
976
|
+
status: "deferred",
|
|
977
|
+
unit: `common-law / ${platform}`,
|
|
978
|
+
reason: `open — not run: ${platform} was dictated in the grid and no pass produced a receipt for it. `
|
|
979
|
+
+ "A pass stating that another pass owns a channel is not a receipt.",
|
|
980
|
+
}));
|
|
981
|
+
} catch { return []; }
|
|
982
|
+
}
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
// D3 verify.mjs:1123 checkJson: fail(String(e.message)) — FIVE parsers reach this one site
|
|
21
21
|
// D4 verify.mjs:1603 parseCoverageLedgerJson, same shape
|
|
22
22
|
// D5 verify.mjs:1504 fail(`${unaccounted[0].token}:…`) — token minted in a DATA ROW
|
|
23
|
-
// D6 verify.mjs:1567 fail(`${violations[0].token}…`) — register-plan.mjs
|
|
24
|
-
// D7 verify.mjs:1558 fail(`${v2[0].token}${detail}…`) — register-plan.mjs:
|
|
23
|
+
// D6 verify.mjs:1567 fail(`${violations[0].token}…`) — validatePlanFeasibility in register-plan.mjs
|
|
24
|
+
// D7 verify.mjs:1558 fail(`${v2[0].token}${detail}…`) — register-plan.mjs:2018 disclosureTextByAxis
|
|
25
25
|
// D8 verify.mjs:1692 fail(caseLawLedgerFail(…)) — token built in case-law-ledger.mjs:204
|
|
26
26
|
//
|
|
27
27
|
// A partition built on the 60 tokens a regex CAN see would run green while blind to the rest, which is
|
|
@@ -108,10 +108,10 @@ export const VOCABULARY = [
|
|
|
108
108
|
{ token: "coverage_form_missing", stages: ["register-digest"], site: "driver/verify.mjs:1445" },
|
|
109
109
|
{ token: "coverage_form_empty", stages: ["register-digest"], site: "driver/verify.mjs:1449" },
|
|
110
110
|
{ token: "coverage_status_offenum", stages: ["register-digest"], site: "driver/verify.mjs:2050" },
|
|
111
|
-
{ token: "coverage_deferred_unaccounted", stages: ["register-digest"], site: "driver/verify.mjs:1504", family: "driver/register-plan.mjs:
|
|
112
|
-
{ token: "coverage_clean_unexecuted", stages: ["register-digest"], site: "driver/verify.mjs
|
|
113
|
-
{ token: "coverage_clean_skipped", stages: ["register-digest"], site: "driver/verify.mjs
|
|
114
|
-
{ token: "coverage_clean_unverified_incomplete", stages: ["register-digest"], site: "driver/verify.mjs
|
|
111
|
+
{ token: "coverage_deferred_unaccounted", stages: ["register-digest"], site: "driver/verify.mjs:1504 coverageFormFail", family: "driver/register-plan.mjs:1642 PROVIDER_HARD_ERROR_PREFIX — token on a data row", dynamic: "D5" },
|
|
112
|
+
{ token: "coverage_clean_unexecuted", stages: ["register-digest"], site: "driver/verify.mjs, the matterContext validator", family: "driver/register-plan.mjs:1417 validatePlanFeasibility", dynamic: "D6" },
|
|
113
|
+
{ token: "coverage_clean_skipped", stages: ["register-digest"], site: "driver/verify.mjs, the matterContext validator", family: "driver/register-plan.mjs:1766 searchedJurisdictionsFromPlan", dynamic: "D6" },
|
|
114
|
+
{ token: "coverage_clean_unverified_incomplete", stages: ["register-digest"], site: "driver/verify.mjs, the matterContext validator", family: "driver/register-plan.mjs:2018 disclosureTextByAxis", dynamic: "D7" },
|
|
115
115
|
{ token: "coverage_clean_tainted", stages: ["register-digest"], site: "driver/verify.mjs:1578" },
|
|
116
116
|
{ token: "coverage_ledger_", stages: ["register-digest"], site: "driver/verify.mjs:1603", family: "driver/coverage-ledger.mjs (parseCoverageLedgerJson token-first throws)", dynamic: "D4" },
|
|
117
117
|
{ token: "coverage_key_unknown", stages: ["register-digest"], site: "driver/verify.mjs:1603", family: "driver/coverage-ledger.mjs", dynamic: "D4" },
|
|
@@ -67,6 +67,34 @@ export function activeElapsedMs({ wall, toolWaitMs = 0, toolAskedAt = null, now
|
|
|
67
67
|
// so it can never fire before the byte-stall and never clips a legitimately quiet-but-working stretch
|
|
68
68
|
// shorter than 5 minutes. A no-progress kill is RECORDED as a stall (signals.stalled + signals.noProgress),
|
|
69
69
|
// never as "the stage needed more time" — the retry policy must not extend the budget for it.
|
|
70
|
+
// TRICKLE FLOOR — a turn that streams a token every twelve seconds is not a slow turn, and no clock
|
|
71
|
+
// above can see it. The byte-stall resets on any streamed byte; the no-progress ceiling resets on token
|
|
72
|
+
// movement, deliberately, because a healthy streaming turn must never be clipped for being slow. So a
|
|
73
|
+
// trickle held both off and was stopped only by the last-resort wall, at budget + 60s, with the whole
|
|
74
|
+
// attempt discarded and re-run from the start.
|
|
75
|
+
//
|
|
76
|
+
// MEASURED, on one round of one scenario, and that is stated because it is not yet a distribution: two
|
|
77
|
+
// stages killed at the wall streamed 0.08 and 0.12 output tokens per second of ACTIVE time; their own
|
|
78
|
+
// retries, same matter and same engine, ran at 74 and 82.83. Three orders of magnitude apart, which is
|
|
79
|
+
// why a floor needs no fine calibration to separate them — and exactly why the floor below sits far
|
|
80
|
+
// under the slowest healthy sample rather than near the gap. The number that finally ships wants the
|
|
81
|
+
// same window read across the other preserved rounds; until then this is a floor against a pathology,
|
|
82
|
+
// not a budget for a stage.
|
|
83
|
+
//
|
|
84
|
+
// ON ACTIVE TIME, never elapsed, for the reason the hard ceiling states at length: a turn waiting on a
|
|
85
|
+
// slow tool is not producing tokens and must not be killed for it. Zero disables the instrument.
|
|
86
|
+
const minTokensPerSec = () => {
|
|
87
|
+
const v = Number(process.env.CLEAROTRON_MIN_TOKENS_PER_SEC);
|
|
88
|
+
return Number.isFinite(v) && v >= 0 ? v : 1;
|
|
89
|
+
};
|
|
90
|
+
// THE WARM-UP IS NOT A COURTESY, it is what makes the measurement meaningful: a rate over three seconds
|
|
91
|
+
// of active time is noise, and a turn that thinks before it writes would fail a floor applied at once.
|
|
92
|
+
// Five minutes of ACTIVE time is well past the point where a working turn has produced something, and
|
|
93
|
+
// far short of the 46 and 41 minutes the killed attempts burned.
|
|
94
|
+
const rateWarmupMs = () => {
|
|
95
|
+
const v = Number(process.env.CLEAROTRON_MIN_TOKENS_WARMUP_MS);
|
|
96
|
+
return Number.isFinite(v) && v > 0 ? v : 300000;
|
|
97
|
+
};
|
|
70
98
|
const noProgressMs = (stallClockMs) => {
|
|
71
99
|
const pinned = Number(process.env.CLEAROTRON_NO_PROGRESS_MS);
|
|
72
100
|
if (pinned > 0) return pinned;
|
|
@@ -418,6 +446,8 @@ export const anthropicAgentEngine = {
|
|
|
418
446
|
// (default 120s) so a real wedge still trips fast. Clamp NaN/≤0 → global so a misconfig never disables it.
|
|
419
447
|
const STALL = (Number(stallSec) > 0 ? Number(stallSec) * 1000 : stallMs());
|
|
420
448
|
const NOPROG = noProgressMs(STALL); // the honest-progress ceiling (see noProgressMs) — ≥ STALL by construction unless pinned for tests
|
|
449
|
+
const MIN_RATE = minTokensPerSec(); // output tokens per second of ACTIVE time; 0 disables (see minTokensPerSec)
|
|
450
|
+
const WARMUP = rateWarmupMs(); // active time before the floor may fire at all
|
|
421
451
|
// legacy-engine parity (+60s past the stage timeout); clamp NaN/≤0 → 660s so the hard wall never
|
|
422
452
|
// silently disables. `CLEAROTRON_HARD_MS` pins it for tests, exactly as CLEAROTRON_STALL_MS and
|
|
423
453
|
// CLEAROTRON_NO_PROGRESS_MS pin the other two clocks — and it is why this ceiling had no end-to-end arm
|
|
@@ -457,6 +487,7 @@ export const anthropicAgentEngine = {
|
|
|
457
487
|
try { child.stdin.write(input); child.stdin.end(); } catch { /* child gone — handled by close/error */ }
|
|
458
488
|
let buf = "", stderr = "", resultEvent = null, killed = false, stallKill = false, settled = false;
|
|
459
489
|
let noProgressKill = false; // the no-progress ceiling fired (a stall discriminator, see noProgressMs)
|
|
490
|
+
let trickleKill = false; // the token-rate floor fired (a stall discriminator, see minTokensPerSec)
|
|
460
491
|
let overflow = false; // A3: stdout/stderr exceeded maxBuffer — force a nonzero fail, never parse the truncated tail
|
|
461
492
|
const maxBuffer = engineMaxBufferChars();
|
|
462
493
|
let lastMove = Date.now();
|
|
@@ -790,6 +821,20 @@ export const anthropicAgentEngine = {
|
|
|
790
821
|
if (sig !== null && artifactSeen.get(f) !== sig) { artifactSeen.set(f, sig); progress(); }
|
|
791
822
|
}
|
|
792
823
|
};
|
|
824
|
+
// Is this turn producing tokens too slowly to be working? Active time, after the warm-up, over the
|
|
825
|
+
// turn's own streamed output — the same accumulator the journal reports, so the number that kills a
|
|
826
|
+
// turn is the number a reader afterwards sees.
|
|
827
|
+
// What the rate WAS when the floor fired, for the failure line. Two decimals: the readings this
|
|
828
|
+
// exists for are hundredths of a token per second.
|
|
829
|
+
const trickleRate = (now = Date.now()) => {
|
|
830
|
+
const activeMs = activeElapsedMs({ wall: now - (firstOutputAt ?? now), toolWaitMs, toolAskedAt, now });
|
|
831
|
+
return activeMs > 0 ? (((streamedUsage()?.output ?? 0) * 1000) / activeMs).toFixed(2) : "0.00";
|
|
832
|
+
};
|
|
833
|
+
const isTrickling = (now) => {
|
|
834
|
+
const activeMs = activeElapsedMs({ wall: now - firstOutputAt, toolWaitMs, toolAskedAt, now });
|
|
835
|
+
if (activeMs < WARMUP) return false;
|
|
836
|
+
return (streamedUsage()?.output ?? 0) < (MIN_RATE * activeMs) / 1000;
|
|
837
|
+
};
|
|
793
838
|
const watchdog = setInterval(() => {
|
|
794
839
|
artifactProgress();
|
|
795
840
|
const now = Date.now();
|
|
@@ -817,6 +862,25 @@ export const anthropicAgentEngine = {
|
|
|
817
862
|
// kill it NOW, well below the wall, and record it as a STALL — never let the ceiling raise turn
|
|
818
863
|
// into extended stall burn, and never let this kill read as "the stage needed more time".
|
|
819
864
|
else if (progIdle >= (started ? NOPROG : Math.max(NOPROG, GRACE))) { stallKill = true; noProgressKill = true; killed = true; killTree(); }
|
|
865
|
+
// ── A TRICKLE IS A STALL WEARING A STREAM ────────────────────────────────────────────
|
|
866
|
+
//
|
|
867
|
+
// Both clocks above are satisfied by a token every twelve seconds: the byte-stall resets on any
|
|
868
|
+
// byte, and the no-progress ceiling counts token movement as progress ON PURPOSE, because the
|
|
869
|
+
// engine contract promises a slow-but-working streaming turn is never clipped. So the only thing
|
|
870
|
+
// that stopped a trickle was the wall, at budget + 60s, with the whole attempt thrown away.
|
|
871
|
+
//
|
|
872
|
+
// This fires far below the wall and is RECORDED AS A STALL with its own discriminator, so the
|
|
873
|
+
// retry policy treats it as one and never extends the budget for the next attempt — the thing a
|
|
874
|
+
// ceiling raise would have done, and the reason this is not one.
|
|
875
|
+
//
|
|
876
|
+
// The rate is over the turn's own streamed output, on active time, after a warm-up. A healthy
|
|
877
|
+
// turn is nowhere near it: the samples that motivated this ran at 74 and 82.83 tokens per second
|
|
878
|
+
// against kills at 0.08 and 0.12, and the floor sits at 1.
|
|
879
|
+
// ONE CONDITION, NOT A NESTED BLOCK. An `else if (enabled) { if (trickling) … }` would swallow
|
|
880
|
+
// the chain: with the floor enabled and the rate healthy, the hard ceiling below would never be
|
|
881
|
+
// reached, and the only clock that bounds a turn producing tokens at a normal rate forever would
|
|
882
|
+
// have been switched off by adding this one.
|
|
883
|
+
else if (started && MIN_RATE > 0 && isTrickling(now)) { stallKill = true; trickleKill = true; killed = true; killTree(); }
|
|
820
884
|
// ── THE CEILING MEASURES ACTIVE TIME, NOT ELAPSED ────────────────────────────────────
|
|
821
885
|
//
|
|
822
886
|
// Ruling: "there isnt such thing as a hung model. it always delivers something or fails."
|
|
@@ -952,6 +1016,13 @@ export const anthropicAgentEngine = {
|
|
|
952
1016
|
? (stderr + `\nrequest timed out (anthropic-agent no-progress watchdog: no token movement / agent-loop step / artifact write for ${Math.round(NOPROG / 1000)}s — a STALL, not a slow turn)`
|
|
953
1017
|
+ `\nanthropic-agent no-progress specimen: firstByteMs=${firstOutputAt === null ? "NEVER" : Math.round(firstOutputAt - t0)}`
|
|
954
1018
|
+ ` toolCalls=${toolCalls} noProgressMs=${NOPROG} graceMs=${GRACE}`)
|
|
1019
|
+
: trickleKill
|
|
1020
|
+
// ITS OWN SENTENCE, because "0 streamed tokens" is the one thing this kill is not. A reader
|
|
1021
|
+
// meeting the stall line over a turn that streamed for forty minutes would go looking for a
|
|
1022
|
+
// silent process and find a talkative one. The rate that killed it rides the line, so the
|
|
1023
|
+
// threshold can be argued from artifacts rather than from memory.
|
|
1024
|
+
? (stderr + `\nrequest timed out (anthropic-agent trickle floor: ${trickleRate()} output tokens/sec of active time,`
|
|
1025
|
+
+ ` under the ${MIN_RATE}/sec floor after a ${Math.round(WARMUP / 1000)}s warm-up — a STALL that kept the pipe warm, not a slow turn)`)
|
|
955
1026
|
: stallKill ? (stderr + "\nrequest timed out (anthropic-agent stall-watchdog: 0 streamed tokens)")
|
|
956
1027
|
// Startup-class death (the 3× register-digest code=1 zero-token shape): the CLI exited without
|
|
957
1028
|
// emitting a single stream event — the failure happened before any turn ran (arg/auth/MCP
|
|
@@ -1020,6 +1091,10 @@ export const anthropicAgentEngine = {
|
|
|
1020
1091
|
// never journal a token-moving kill as usage:null). noStreamEvents: the CLI died before emitting
|
|
1021
1092
|
// ANY stream event — a startup-class failure, diagnosable from stderrTail alone.
|
|
1022
1093
|
signals: { stalled: stallKill || undefined, noProgress: noProgressKill || undefined,
|
|
1094
|
+
// A TRICKLE IS A STALL, and this says which kind. Retry policy keys on `stalled` — so this
|
|
1095
|
+
// never extends the next attempt's budget — while a reader keys on this to tell a turn that
|
|
1096
|
+
// said nothing from one that said almost nothing for three quarters of an hour.
|
|
1097
|
+
trickle: trickleKill || undefined,
|
|
1023
1098
|
hardWall: (killed && !stallKill) || undefined, rateLimited: rateLimited || undefined, resetsAt,
|
|
1024
1099
|
usageStreamed: (streamUsage != null) || undefined,
|
|
1025
1100
|
noStreamEvents: (!sawAnyEvent && resultEvent == null) || undefined,
|
|
@@ -114,7 +114,32 @@ export function skeletonPatterns(element) {
|
|
|
114
114
|
// also leading/trailing-anchored substrings of the skeleton, so a long mark's family is reachable in pieces.
|
|
115
115
|
const sk = consonantSkeleton(w);
|
|
116
116
|
if (sk.length >= 2) { out.add(`${sk[0]}*${sk[sk.length - 1]}`); }
|
|
117
|
-
|
|
117
|
+
// ── A PATTERN THAT DEGENERATES TO ITS OWN ELEMENT IS NOT A PATTERN ────────────────────────────────
|
|
118
|
+
//
|
|
119
|
+
// The vowel-slot loop above replaces each vowel RUN with `?`. An element carrying no vowel has no run
|
|
120
|
+
// to replace, so it falls through the loop unchanged and `pat` is the normalized element itself — a
|
|
121
|
+
// plain string with no pattern syntax in it. Every initialism is in this class: SMS, BCG, KFC, HSBC,
|
|
122
|
+
// MTV, CNN, and single-character elements like X.
|
|
123
|
+
//
|
|
124
|
+
// That string used to be returned and dispatched under the hard-coded `wildcard` predicate the
|
|
125
|
+
// register-plan fringe pushes. `termPredicateIssue` reads the pair as unexecutable, the pipeline
|
|
126
|
+
// raises StageFailure at plan compile because the entry was freshly minted rather than inherited, and
|
|
127
|
+
// THE WHOLE MATTER DIES BEFORE ONE QUERY IS SENT — nothing delivered, and the message correctly says
|
|
128
|
+
// retrying will not help. A mark whose dominant element has no vowel could not be cleared at all.
|
|
129
|
+
//
|
|
130
|
+
// SCREENED HERE RATHER THAN AT THE PUSH SITE, and that is a decision rather than convenience. The
|
|
131
|
+
// entry is worthless, not a lost slice: a starless wildcard term maps to `wildcardInfix`, which every
|
|
132
|
+
// provider serves as a contains search over the raw term — the same search the crowd-gate parent
|
|
133
|
+
// already dispatches one line earlier with `predicate: "default"` over the same element. Stamping it
|
|
134
|
+
// `unsupported` at the funnel would therefore publish a disclosed coverage gap for an axis that is
|
|
135
|
+
// NOT uncovered, and a false gap in a client's report is worse than the honest death it replaces.
|
|
136
|
+
// The funnel's own rules (markup, substance, variant values) screen terms the compiler does not
|
|
137
|
+
// author; this one it authors, here, and this is the only producer of `wildcardPatterns` in the tree.
|
|
138
|
+
//
|
|
139
|
+
// ONE SCREEN OVER EVERYTHING THIS FUNCTION RETURNS, not a guard beside each member. The anchored
|
|
140
|
+
// form below carries `*` by construction today, so filtering it is a no-op today — which is the
|
|
141
|
+
// point: a pattern added here later is covered without anybody remembering this rule.
|
|
142
|
+
return [...out].filter((p) => /[*?]/.test(p));
|
|
118
143
|
}
|
|
119
144
|
|
|
120
145
|
// ── Visual confusables (Unicode-confusable axis) — look-alikes an examiner/consumer would conflate ──────
|
|
@@ -241,7 +266,24 @@ export function formNeighbourhood(element, { markets = [], scripts = SUPPORTED_S
|
|
|
241
266
|
dropped_axes: [...drop].sort(),
|
|
242
267
|
axes: [
|
|
243
268
|
{ axis: "edit-1", count: edits.length, mechanism: "Damerau-Levenshtein edit-1, exhaustive" },
|
|
244
|
-
|
|
269
|
+
// NO PATTERN is a THIRD state, and it is disclosed in the same voice as a judgment drop. An
|
|
270
|
+
// element with too few consonants to anchor a skeleton wildcard (X, and anything normalizing to
|
|
271
|
+
// one character) yields no retrieval pattern at all — see skeletonPatterns. Silence here would
|
|
272
|
+
// let the axis read as ordinary, when what happened is that it contributed nothing.
|
|
273
|
+
//
|
|
274
|
+
// AND THE KEYS ARE A SEPARATE QUESTION FROM THE PATTERNS, so the row asks it separately. For an
|
|
275
|
+
// element like X the metaphone keys survive and really do verify which returned marks are true
|
|
276
|
+
// sound-alikes, and the row should say so rather than call the whole axis dead. For a purely
|
|
277
|
+
// numeric element — "99", "5" — there are no keys either, and a row that still claimed the keys
|
|
278
|
+
// verify would be a CLIENT-FACING CLAIM OF A VERIFICATION THAT DID NOT HAPPEN. That is the worse
|
|
279
|
+
// failure of the two, so the clause is dropped rather than printed empty. The first draft of
|
|
280
|
+
// this row hard-coded the clause and pinned only X in its test, which has keys — the arm named
|
|
281
|
+
// the disclosure property and drove one member of it, so it passed while this was live.
|
|
282
|
+
{ axis: "phonetic-family", count: wildcards.length,
|
|
283
|
+
mechanism: drop.has("phonetic-family") ? "DROPPED — judgment's variant-layer scope decision"
|
|
284
|
+
: wildcards.length ? `consonant-skeleton wildcard + Double-Metaphone key(s) [${keys.join(",")}]`
|
|
285
|
+
: keys.length ? `NO PATTERN — "${el}" has too few consonants to anchor a skeleton wildcard, so the family has no retrieval pattern; Double-Metaphone key(s) [${keys.join(",")}] still verify what the other axes return`
|
|
286
|
+
: `NO PATTERN — "${el}" has too few consonants to anchor a skeleton wildcard and yields no Double-Metaphone key, so this axis contributes nothing for this element and verifies nothing` },
|
|
245
287
|
{ axis: "visual-confusable", count: confs.length, mechanism: drop.has("visual-confusable") ? "DROPPED — judgment's variant-layer scope decision" : "Unicode-confusable homoglyph + multigraph table" },
|
|
246
288
|
{ axis: "transliteration", count: trans.length, mechanism: drop.has("transliteration") ? "DROPPED — judgment's variant-layer scope decision" : `scoped scripts: ${scripts.join(", ")}` },
|
|
247
289
|
],
|
|
@@ -125,6 +125,16 @@ export function renderMatterFrame(model) {
|
|
|
125
125
|
out.push(`- **Scope jurisdictions:** ${model.scope_jurisdictions.join(", ")}`);
|
|
126
126
|
if (model.excluded_jurisdictions.length)
|
|
127
127
|
out.push(`- **Excluded jurisdictions:** ${model.excluded_jurisdictions.join(", ")}`);
|
|
128
|
+
// CLASSES THE FRAME ADDED, WITH THE REASON IT ADDED THEM. Rendered as its own rows rather than folded
|
|
129
|
+
// into the instructed scope above, because the two are different claims: that section is what the
|
|
130
|
+
// client asked for, quoted from the driver's intake record, and these are what the frame concluded is
|
|
131
|
+
// necessary as well. A reader who cannot tell those apart cannot tell an instruction from a judgement.
|
|
132
|
+
//
|
|
133
|
+
// NOTHING RENDERS WHEN THERE ARE NONE — no heading, no "none" line. An asserted zero is right where a
|
|
134
|
+
// seat might have skipped the question (meaning angles below), and wrong here: the frame is not asked
|
|
135
|
+
// to find extra classes, so finding none is the ordinary case and not an answer worth a row.
|
|
136
|
+
for (const c of (model.identified_classes ?? []))
|
|
137
|
+
out.push(`- **Class ${c.class} — identified by the frame:** ${c.reason}`);
|
|
128
138
|
out.push("");
|
|
129
139
|
|
|
130
140
|
// `Search channels:` — domains only; the grid site-restricts to them and the general web is always
|
|
@@ -156,13 +166,34 @@ export function renderMatterFrame(model) {
|
|
|
156
166
|
*/
|
|
157
167
|
/** The shape this tool declares, at every depth — what the ACCEPTOR enforces. */
|
|
158
168
|
const DECLARED = Object.freeze({
|
|
159
|
-
"": ["prose_body", "scope_basis", "scope_jurisdictions", "excluded_jurisdictions", "search_channels", "meaning_angles", "meaning_angles_none", "intake_asks"],
|
|
169
|
+
"": ["prose_body", "scope_basis", "scope_jurisdictions", "excluded_jurisdictions", "search_channels", "meaning_angles", "meaning_angles_none", "intake_asks", "identified_classes"],
|
|
160
170
|
intake_asks: ["ask", "owner"],
|
|
171
|
+
identified_classes: ["class", "reason"],
|
|
161
172
|
});
|
|
162
173
|
|
|
163
174
|
/** Refuse an undeclared key by path, at depth. Shared walk; the table above is what is this tool's. */
|
|
164
175
|
export const refuseUndeclared = (params) => refuseUndeclaredShared(params, DECLARED, "matterframe");
|
|
165
176
|
|
|
177
|
+
/**
|
|
178
|
+
* The Nice classes the frame judged necessary beyond the instructed ones, as strings. IMPURE (reads
|
|
179
|
+
* the run's own accepted call).
|
|
180
|
+
*
|
|
181
|
+
* THE PLAN COMPILE UNIONS THIS WITH THE INSTRUCTED CLASSES so every variant axis carries both. Before
|
|
182
|
+
* it existed the axes carried the instructed classes alone and a class the frame had identified reached
|
|
183
|
+
* the sweep only if something proposed it as supplemental work — where it competed for capped slots
|
|
184
|
+
* with model-minted extras. A cap was deciding coverage the frame had already judged necessary, and on
|
|
185
|
+
* the run this came from the delivered report said two such classes were "covered for the name and open
|
|
186
|
+
* for its variants" while the reviewing lawyer's scope included one of them throughout.
|
|
187
|
+
*
|
|
188
|
+
* EMPTY IS THE ORDINARY ANSWER and must stay cheap: no frame yet, a legacy or replayed run whose
|
|
189
|
+
* accepted call predates the field, a frame that identified nothing — all of them return `[]`, the
|
|
190
|
+
* union is a no-op, and the plan is exactly what it was.
|
|
191
|
+
*/
|
|
192
|
+
export function frameIdentifiedClasses(runDir) {
|
|
193
|
+
const rows = lastAcceptedMatterFrame(runDir)?.identified_classes;
|
|
194
|
+
return (Array.isArray(rows) ? rows : []).map((r) => String(r?.class ?? "").trim()).filter(Boolean);
|
|
195
|
+
}
|
|
196
|
+
|
|
166
197
|
/** The last ACCEPTED call for this run, or null. */
|
|
167
198
|
export function lastAcceptedMatterFrame(runDir) {
|
|
168
199
|
return lastAccepted(matterFrameCallPaths(String(runDir ?? "")).accepted, readFileSync);
|
|
@@ -190,6 +221,12 @@ export function mergeMatterFrameCall(stored, received) {
|
|
|
190
221
|
scope_jurisdictions: keepIfAbsent(received?.scope_jurisdictions, base.scope_jurisdictions),
|
|
191
222
|
excluded_jurisdictions: keepIfAbsent(received?.excluded_jurisdictions, base.excluded_jurisdictions),
|
|
192
223
|
meaning_angles_none: keepIfAbsent(received?.meaning_angles_none, base.meaning_angles_none),
|
|
224
|
+
// KEEP-IF-ABSENT, for the same reason as the two above and one more. These are classes the frame
|
|
225
|
+
// judged necessary beyond the instructed ones, and the plan compile unions them into every variant
|
|
226
|
+
// axis — so a partial call that dropped them would not merely make the frame quieter, it would
|
|
227
|
+
// NARROW THE SEARCH on the next compile, silently and in the direction that misses rights. An
|
|
228
|
+
// omission here is a repair that did not mention them, never a decision to withdraw them.
|
|
229
|
+
identified_classes: keepIfAbsent(received?.identified_classes, base.identified_classes),
|
|
193
230
|
};
|
|
194
231
|
}
|
|
195
232
|
|
|
@@ -228,6 +265,35 @@ export function acceptMatterFrame(params, { instructedScope = null } = {}) {
|
|
|
228
265
|
intake_asks.push({ ask, owner });
|
|
229
266
|
}
|
|
230
267
|
|
|
268
|
+
// ── CLASSES THE FRAME JUDGED NECESSARY BEYOND THE INSTRUCTED ONES ─────────────────────────────────
|
|
269
|
+
//
|
|
270
|
+
// The frame reads the description of use and can conclude that a class nobody instructed is in scope.
|
|
271
|
+
// It has always said so in its prose. Nothing could act on that: the plan compile takes its classes
|
|
272
|
+
// from the driver's intake record, so the identified ones reached the sweep only if a model proposed
|
|
273
|
+
// them as supplemental work, where they competed for capped slots with model-minted extras — a cap
|
|
274
|
+
// deciding coverage the frame had already judged necessary.
|
|
275
|
+
//
|
|
276
|
+
// TYPED RATHER THAN PARSED OUT OF THE PROSE, and that is the whole reason this field exists. Deriving
|
|
277
|
+
// classes from judgment prose is ruled against with measurement: 19 of 21 runs carry the same class
|
|
278
|
+
// number in both an applied and a dropped row, and deriving there dropped the PRIMARY class. A field
|
|
279
|
+
// the frame fills in deliberately is a decision; a number scraped out of a sentence is a guess.
|
|
280
|
+
//
|
|
281
|
+
// A REASON PER CLASS IS REQUIRED, not decorative. This widens a client's search, and the next reader
|
|
282
|
+
// asking why class 9 was swept needs the frame's own sentence rather than an inference from a number.
|
|
283
|
+
// ABSENT OR EMPTY CHANGES NOTHING — the compile unions an empty list and the plan is what it was.
|
|
284
|
+
const identified_classes = [];
|
|
285
|
+
for (const c of (Array.isArray(params?.identified_classes) ? params.identified_classes : [])) {
|
|
286
|
+
const raw = str(c?.class), reason = str(c?.reason);
|
|
287
|
+
const n = Number(raw);
|
|
288
|
+
if (!Number.isInteger(n) || n < 1 || n > 45)
|
|
289
|
+
return { ok: false, reason: `matterframe_identified_class_invalid:${raw || "<empty>"} — a Nice class is a whole number 1-45` };
|
|
290
|
+
if (!reason)
|
|
291
|
+
return { ok: false, reason: `matterframe_identified_class_reason_missing:${n} — every class the frame adds carries a one-line reason, because it widens what the client is charged to search` };
|
|
292
|
+
if (identified_classes.some((k) => k.class === String(n)))
|
|
293
|
+
return { ok: false, reason: `matterframe_identified_class_duplicate:${n} — one row per class` };
|
|
294
|
+
identified_classes.push({ class: String(n), reason });
|
|
295
|
+
}
|
|
296
|
+
|
|
231
297
|
const model = {
|
|
232
298
|
schema_version: SCHEMA_VERSION,
|
|
233
299
|
instructed_scope: instructedScope ?? null,
|
|
@@ -238,6 +304,7 @@ export function acceptMatterFrame(params, { instructedScope = null } = {}) {
|
|
|
238
304
|
search_channels: list(params?.search_channels),
|
|
239
305
|
meaning_angles, meaning_angles_none,
|
|
240
306
|
intake_asks,
|
|
307
|
+
identified_classes,
|
|
241
308
|
};
|
|
242
309
|
return { ok: true, model, content: renderMatterFrame(model) };
|
|
243
310
|
}
|
package/driver/named-band.mjs
CHANGED
|
@@ -137,7 +137,7 @@ export function parseNamedBand(raw) {
|
|
|
137
137
|
// byte-identical to a slice the plan deliberately counted without fetching. Measured on a real
|
|
138
138
|
// run: four capability-gap blocks carried `error:true, deferred:true` into this function and
|
|
139
139
|
// reached record-carry.json with both fields gone and a sentence claiming the run "has a hit
|
|
140
|
-
// COUNT for this slice". register-plan.mjs:
|
|
140
|
+
// COUNT for this slice". register-plan.mjs:1439 validatePlanFeasibility already enforces the same rule one layer up
|
|
141
141
|
// ("a transient must not ship indistinguishable from a sanctioned descriptor") — it reads the
|
|
142
142
|
// RAW blocks, which is why it could. Every consumer that reads THIS projection could not.
|
|
143
143
|
// Conditional like the four keys above, so old bands carry neither key and nothing shifts.
|
package/driver/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "clearotron-driver",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.3.1-beta.
|
|
5
|
+
"version": "0.3.1-beta.2",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
7
7
|
"description": "Deterministic driver for the trademark clearance workflow: orchestration in code (fan-out, fan-in barrier, gating, retries); the model does judgment leaves only, through a reasoning CLI spawned per stage.",
|
|
8
8
|
"engines": {
|
|
@@ -1146,7 +1146,19 @@ export async function knockoutInner(ctx, job, opts = {}) {
|
|
|
1146
1146
|
// because mark_sent is the only clear and it settles a failure.json on the same evidence a delivery
|
|
1147
1147
|
// needs. The marker is NOT duplicated — the primary lane's own packet lands `<runId>.failed.pending`
|
|
1148
1148
|
// and the *.pending watch matches it; a second `<runId>.pending` would outlive every settle.
|
|
1149
|
+
// THE RECORD OF AN OBLIGATION MUST NOT BE CONTINGENT ON THE ACTION IT OBLIGES — clearance parity,
|
|
1150
|
+
// and the same defect this lane would have had. `writeOutboxPacket` returns null rather than throwing,
|
|
1151
|
+
// so an unwritable outbox reaches the raw marker write below; with the flag after it, that throw took
|
|
1152
|
+
// the record of the obligation with it and the run ended failed and NOT owed. Guards first, then the
|
|
1153
|
+
// flag, then the writes that can fail.
|
|
1149
1154
|
let failPingSent = false;
|
|
1155
|
+
try { rmSync(join(run.runDir, ".sent")); } catch { /* none */ }
|
|
1156
|
+
try { rmSync(driverDir(run.runDir, "send-receipts.json"), { force: true }); } catch { /* none */ }
|
|
1157
|
+
// a fresh notice supersedes an older send's skip-guards (a resumed-then-failed-again run must
|
|
1158
|
+
// still notify — the .sent/receipts invariant, clearance parity)
|
|
1159
|
+
try { writeRunStatus(ctx, { sendPending: true }); } catch (sErr) {
|
|
1160
|
+
note(`knockout failure notice could NOT be recorded as owed (${String(sErr?.message ?? sErr).slice(0, 100)}) — the run directory is unwritable`);
|
|
1161
|
+
}
|
|
1150
1162
|
try {
|
|
1151
1163
|
const rich = buildFailurePacket({
|
|
1152
1164
|
runId, agent, job, failedStage, shortReason, terminalKind,
|
|
@@ -1163,12 +1175,7 @@ export async function knockoutInner(ctx, job, opts = {}) {
|
|
|
1163
1175
|
mkdirSync(config.outboxDir, { recursive: true });
|
|
1164
1176
|
writeFileSync(join(config.outboxDir, `${runId}.pending`), `${agent}\n`);
|
|
1165
1177
|
}
|
|
1166
|
-
|
|
1167
|
-
// still notify — the .sent/receipts invariant, clearance parity)
|
|
1168
|
-
try { rmSync(join(run.runDir, ".sent")); } catch { /* none */ }
|
|
1169
|
-
try { rmSync(driverDir(run.runDir, "send-receipts.json"), { force: true }); } catch { /* none */ }
|
|
1170
|
-
writeRunStatus(ctx, { sendPending: true });
|
|
1171
|
-
} catch (nfErr) { note(`knockout failure-notice write skipped (${String(nfErr?.message ?? nfErr).slice(0, 100)})`); }
|
|
1178
|
+
} catch (nfErr) { note(`knockout failure notice is owed but its packet/marker could not be written (${String(nfErr?.message ?? nfErr).slice(0, 100)}) — status.json carries the obligation`); }
|
|
1172
1179
|
note(`=== KNOCKOUT ${terminalKind === REFUSAL_TERMINAL_KIND ? "REFUSED" : "FAILED"} ${run.codename} at ${failedStage}: ${shortReason} ===\n`);
|
|
1173
1180
|
// `codename`: this lane's terminals reach the SAME CLI exit as the clearance lane's, and the
|
|
1174
1181
|
// CLI composes its resume line from the returned identity. Without it a knockout operator is the only
|