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
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
|
|
37
37
|
import { existsSync, readFileSync, writeFileSync, renameSync } from "node:fs";
|
|
38
38
|
import { isCapabilityGapReason } from "./coverage-ledger.mjs";
|
|
39
|
+
import { fullyDeferredAxes } from "./register-plan.mjs";
|
|
39
40
|
|
|
40
41
|
export const SETTLE_SCHEMA_VERSION = 1;
|
|
41
42
|
|
|
@@ -52,22 +53,28 @@ export const SETTLE_SCHEMA_VERSION = 1;
|
|
|
52
53
|
* receipt is final. So `suspect` catches a MIS-STAMPED deferral — an executor bug flagging a transient as
|
|
53
54
|
* deterministic — and exists so that shape gets one attempt instead of silently becoming permanent.
|
|
54
55
|
*
|
|
55
|
-
*
|
|
56
|
+
* STICKY — a qid this run has already accepted as a capability gap, under any plan version, is accepted
|
|
57
|
+
* again whatever its current reason text says (stickyGaps below). PURE.
|
|
56
58
|
*/
|
|
57
|
-
export function partitionReceiptDeferrals(plan, receipt) {
|
|
59
|
+
export function partitionReceiptDeferrals(plan, receipt, { sticky = new Set() } = {}) {
|
|
58
60
|
const axisOf = new Map((plan?.entries ?? []).map((e) => [String(e?.qid ?? ""), String(e?.axis ?? "").toLowerCase()]));
|
|
59
61
|
// An axis the frozen plan says is deferred end to end is accepted whatever its per-qid reason text says:
|
|
60
62
|
// there is no slice of it left for a dispatch to reach.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
//
|
|
64
|
+
// THE PLAN SAYS SO, NOT THE COVERAGE SKELETON. This read the skeleton's `deferred` state, which
|
|
65
|
+
// deriveCoverageSkeleton sets for an axis carrying ANY deferred qid and nothing missing, and at the
|
|
66
|
+
// fan-in nothing is missing. So every deferral on every axis was accepted: a provider hard error was
|
|
67
|
+
// logged "ACCEPTED as provider capability gaps … never retried", skipped the one bounded attempt the
|
|
68
|
+
// hard-error path is designed to get, and was then re-opened by the envelope, which reads the reason.
|
|
69
|
+
// fullyDeferredAxes asks the plan: every entry on the axis `unsupported`.
|
|
70
|
+
const fullyDeferred = new Set(fullyDeferredAxes(plan).map((a) => String(a.axis ?? "").toLowerCase()));
|
|
64
71
|
const accepted = [], suspect = [];
|
|
65
72
|
for (const d of receipt?.deferred ?? []) {
|
|
66
73
|
const qid = String(d?.qid ?? "");
|
|
67
74
|
const axis = axisOf.get(qid) ?? "";
|
|
68
75
|
const reason = String(d?.reason ?? "");
|
|
69
76
|
const row = { qid, axis, reason };
|
|
70
|
-
(isCapabilityGapReason(reason) || fullyDeferred.has(axis) ? accepted : suspect).push(row);
|
|
77
|
+
(isCapabilityGapReason(reason) || fullyDeferred.has(axis) || sticky.has(qid) ? accepted : suspect).push(row);
|
|
71
78
|
}
|
|
72
79
|
return { accepted, suspect };
|
|
73
80
|
}
|
|
@@ -129,7 +136,7 @@ const shallowRow = (d) => ({ qid: String(d?.qid ?? ""), reason: String(d?.reason
|
|
|
129
136
|
* the one case the file exists to make durable. `supersede()` builds the entry; it is deliberately a
|
|
130
137
|
* summary rather than a full copy, because the qid-level truth is always re-derivable from the receipt.
|
|
131
138
|
*/
|
|
132
|
-
export function buildDecisionDoc({ planVersion, deferredTotal, accepted, closed, closeFailed, decidedAt, history = [] }) {
|
|
139
|
+
export function buildDecisionDoc({ planVersion, deferredTotal, accepted, closed, closeFailed, decidedAt, history = [], stickyGaps = [], settleTried = [] }) {
|
|
133
140
|
return {
|
|
134
141
|
schema_version: SETTLE_SCHEMA_VERSION,
|
|
135
142
|
plan_version: planVersion ?? null,
|
|
@@ -138,10 +145,60 @@ export function buildDecisionDoc({ planVersion, deferredTotal, accepted, closed,
|
|
|
138
145
|
accepted: accepted ?? [],
|
|
139
146
|
closed: closed ?? [],
|
|
140
147
|
close_failed: closeFailed ?? [],
|
|
148
|
+
sticky_gaps: stickyGaps ?? [],
|
|
149
|
+
settle_tried: settleTried ?? [],
|
|
141
150
|
history: history ?? [],
|
|
142
151
|
};
|
|
143
152
|
}
|
|
144
153
|
|
|
154
|
+
// ── A CAPABILITY GAP IS DECIDED ONCE PER RUN, NOT ONCE PER PLAN VERSION ────────────────────────────────
|
|
155
|
+
//
|
|
156
|
+
// The live `accepted[]` is rebuilt from the current receipt on every settle, and a settle happens again
|
|
157
|
+
// whenever the plan version moves — every supplemental fold bumps it. So an acceptance lasted exactly as
|
|
158
|
+
// long as the version it was made under. On a production run on 2026-09-22 one slice the provider refused
|
|
159
|
+
// was accepted ("never retried"), re-opened, re-proposed, re-executed and accepted again, several times
|
|
160
|
+
// in one run, each pass paying the provider for an answer that could not change.
|
|
161
|
+
//
|
|
162
|
+
// `sticky_gaps` is the run's memory of them: every qid accepted with a reason that states a capability gap
|
|
163
|
+
// (isCapabilityGapReason), with the reason and the plan version it was first accepted under. It is carried
|
|
164
|
+
// forward on every settle and never dropped: not when the plan version moves, not when the receipt stops
|
|
165
|
+
// listing the qid. Only a REASON-MATCHED gap enters it. An axis-level acceptance (the skeleton branch in
|
|
166
|
+
// partitionReceiptDeferrals) and every suspect or close_failed row stay out, so a deferral a later attempt
|
|
167
|
+
// could close keeps today's behaviour.
|
|
168
|
+
export function stickyGapsAfter(prior, accepted, planVersion) {
|
|
169
|
+
const out = new Map((prior?.sticky_gaps ?? []).map((g) => [String(g?.qid ?? ""), g]));
|
|
170
|
+
for (const a of accepted ?? []) {
|
|
171
|
+
const qid = String(a?.qid ?? "");
|
|
172
|
+
if (!qid || out.has(qid) || !isCapabilityGapReason(a?.reason)) continue;
|
|
173
|
+
out.set(qid, { qid, axis: String(a?.axis ?? ""), reason: String(a?.reason ?? "").slice(0, 400), since_plan_version: planVersion ?? null });
|
|
174
|
+
}
|
|
175
|
+
out.delete("");
|
|
176
|
+
return [...out.values()];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── A SUSPECT DEFERRAL GETS ITS ONE ATTEMPT ONCE PER RUN ─────────────────────────────────────────────
|
|
180
|
+
//
|
|
181
|
+
// The settle step spends one executor attempt on a suspect deferral: a hard error the run could not
|
|
182
|
+
// classify as permanent, on the chance the provider has recovered. Every supplemental fold bumps the plan
|
|
183
|
+
// version and re-settles, and the repair ledger's budget is keyed on the plan version, so "one attempt"
|
|
184
|
+
// was one per plan version. `settle_tried` records each qid the settle step has sent, carried forward like
|
|
185
|
+
// `sticky_gaps`; a suspect already in it is recorded close_failed without another call.
|
|
186
|
+
export function settleTriedAfter(prior, dispatched, planVersion) {
|
|
187
|
+
const out = new Map((prior?.settle_tried ?? []).map((t) => [String(t?.qid ?? ""), t]));
|
|
188
|
+
for (const d of dispatched ?? []) {
|
|
189
|
+
const qid = String(d?.qid ?? "");
|
|
190
|
+
if (!qid || out.has(qid)) continue;
|
|
191
|
+
out.set(qid, { qid, axis: String(d?.axis ?? ""), plan_version: planVersion ?? null, outcome: String(d?.outcome ?? "").slice(0, 160) });
|
|
192
|
+
}
|
|
193
|
+
out.delete("");
|
|
194
|
+
return [...out.values()];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** The run's sticky capability gaps, by qid → row. Absent or unreadable decision ⇒ none. */
|
|
198
|
+
export function readStickyGaps(P) {
|
|
199
|
+
return new Map((readEnvelopeDecision(P)?.sticky_gaps ?? []).map((g) => [String(g?.qid ?? ""), g]).filter(([q]) => q));
|
|
200
|
+
}
|
|
201
|
+
|
|
145
202
|
/** One history entry per superseded decision. Returns the prior history with the old decision appended. */
|
|
146
203
|
export function supersede(prior, source) {
|
|
147
204
|
if (!prior) return [];
|
|
@@ -167,30 +224,42 @@ export function supersede(prior, source) {
|
|
|
167
224
|
*/
|
|
168
225
|
export async function settleReceipt({ P, plan, receipt, dispatch = null, rejoin = null, now = null }) {
|
|
169
226
|
const deferred = receipt?.deferred ?? [];
|
|
170
|
-
|
|
227
|
+
const prior = readEnvelopeDecision(P);
|
|
228
|
+
const sticky = new Set((prior?.sticky_gaps ?? []).map((g) => String(g?.qid ?? "")));
|
|
229
|
+
let { accepted, suspect } = partitionReceiptDeferrals(plan, receipt, { sticky });
|
|
171
230
|
const closed = [], closeFailed = [];
|
|
231
|
+
const tried = new Map((prior?.settle_tried ?? []).map((t) => [String(t?.qid ?? ""), t]));
|
|
232
|
+
for (const s of suspect.filter((x) => tried.has(x.qid)))
|
|
233
|
+
closeFailed.push({ qid: s.qid, axis: s.axis, outcome: `already tried once this run (${String(tried.get(s.qid)?.outcome ?? "").slice(0, 100)}) — not sent again` });
|
|
234
|
+
suspect = suspect.filter((x) => !tried.has(x.qid));
|
|
235
|
+
const dispatched = [];
|
|
172
236
|
if (suspect.length && dispatch && rejoin) {
|
|
173
237
|
const byAxis = new Map();
|
|
174
238
|
for (const s of suspect) { if (!byAxis.has(s.axis)) byAxis.set(s.axis, []); byAxis.get(s.axis).push(s.qid); }
|
|
175
239
|
for (const [axis, qids] of byAxis) { if (axis) await dispatch(axis, qids); }
|
|
176
240
|
const after = await rejoin();
|
|
177
241
|
const stillDeferred = new Map((after?.deferred ?? []).map((d) => [String(d.qid), String(d.reason ?? "")]));
|
|
178
|
-
const re = partitionReceiptDeferrals(plan, after);
|
|
242
|
+
const re = partitionReceiptDeferrals(plan, after, { sticky });
|
|
179
243
|
accepted = re.accepted;
|
|
180
244
|
for (const s of suspect) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
245
|
+
// Only a qid that was actually sent (dispatch skips one with no axis) counts as tried.
|
|
246
|
+
if (!stillDeferred.has(s.qid)) { closed.push({ qid: s.qid, axis: s.axis, outcome: "ok" }); if (s.axis) dispatched.push({ ...s, outcome: "ok" }); }
|
|
247
|
+
else if (!re.accepted.some((a) => a.qid === s.qid)) {
|
|
248
|
+
const outcome = `still deferred: ${stillDeferred.get(s.qid).slice(0, 140)}`;
|
|
249
|
+
closeFailed.push({ qid: s.qid, axis: s.axis, outcome });
|
|
250
|
+
if (s.axis) dispatched.push({ ...s, outcome });
|
|
251
|
+
}
|
|
184
252
|
}
|
|
185
253
|
} else if (suspect.length) {
|
|
186
254
|
for (const s of suspect) closeFailed.push({ qid: s.qid, axis: s.axis, outcome: "no executor lane at this seam" });
|
|
187
255
|
}
|
|
188
|
-
const prior = readEnvelopeDecision(P);
|
|
189
256
|
return writeEnvelopeDecision(P, buildDecisionDoc({
|
|
190
257
|
planVersion: receipt?.plan_version, deferredTotal: deferred.length,
|
|
191
258
|
accepted: accepted.map((a) => ({ ...a, decision: "accepted-capability-gap" })),
|
|
192
259
|
closed, closeFailed, decidedAt: now ?? new Date().toISOString(),
|
|
193
260
|
history: supersede(prior, prior ? "re-settled" : null),
|
|
261
|
+
stickyGaps: stickyGapsAfter(prior, accepted, receipt?.plan_version),
|
|
262
|
+
settleTried: settleTriedAfter(prior, dispatched, receipt?.plan_version),
|
|
194
263
|
}));
|
|
195
264
|
}
|
|
196
265
|
|
|
@@ -335,14 +335,14 @@ export function isUnconditionalProceed(rec) {
|
|
|
335
335
|
|
|
336
336
|
/**
|
|
337
337
|
* Deterministically BIND a recommendation line to the verdict: on a CONDITIONAL verdict an
|
|
338
|
-
* unconditional "proceed" is rewritten to carry the conditions
|
|
339
|
-
*
|
|
338
|
+
* unconditional "proceed" is rewritten to carry the conditions. BLOCKING is the reviewer's sign-off on
|
|
339
|
+
* the draft, not a hold: a BLOCKING run delivers, so its recommendation stands as the report wrote it.
|
|
340
340
|
* Code derives the bound from the model's OWN verdict + reasons; it never invents a verdict. PURE.
|
|
341
341
|
*/
|
|
342
342
|
export function bindRecommendation(rec, verdict, reasons = [], { maxReasons, maxLen } = {}) {
|
|
343
343
|
const r = String(rec ?? "").trim();
|
|
344
344
|
const v = String(verdict || "").toUpperCase();
|
|
345
|
-
|
|
345
|
+
// BLOCKING returns the recommendation as written: nothing holds delivery on it, so "on hold" was untrue (ruled 2026-09-22).
|
|
346
346
|
if (v !== "CONDITIONAL" || !r || !isUnconditionalProceed(r)) return r;
|
|
347
347
|
const conds = (reasons ?? []).filter(Boolean);
|
|
348
348
|
if (!conds.length) return r;
|
|
@@ -600,7 +600,7 @@ export function riskStatement({ tier, verdict, reasons, basis, clauses } = {}) {
|
|
|
600
600
|
// CLEAR, so long as it is clear on register findings ALONE).
|
|
601
601
|
const registerOnly = basis === "register-only";
|
|
602
602
|
const basisNote = registerOnly ? " Register findings only — no common-law or marketplace search was run." : "";
|
|
603
|
-
if (v === "BLOCKING") return `
|
|
603
|
+
if (v === "BLOCKING") return `${t}${basisNote ? `.${basisNote}` : ""}`; // the reviewer's sign-off is not the clearance's answer and nothing is held on it: the band stands alone, as below
|
|
604
604
|
if (v === "CONDITIONAL") {
|
|
605
605
|
// A clause stored as explicit null is a condition ruled to the run record alone: it is not the lede,
|
|
606
606
|
// it is not counted, and its reason is never the fallback text. `undefined` (legacy/short) is not null.
|
package/driver/gateway.mjs
CHANGED
|
@@ -225,6 +225,8 @@ export function toolWrittenArtifact(p) {
|
|
|
225
225
|
?? null;
|
|
226
226
|
}
|
|
227
227
|
import { buildGatherMcpConfig, allowedToolsFor, toolGroupsForStage, recordAxisFor, seatWritesForGroups } from "./engine/mcp/gather-config.mjs";
|
|
228
|
+
import { everyToolCallRefused } from "./engine/tool-refusal.mjs"; // one definition, shared with the engine probe
|
|
229
|
+
export { everyToolCallRefused };
|
|
228
230
|
// The profiles STORE root, for the write boundary only — read from the module that owns it, never
|
|
229
231
|
// re-derived from CLEAROTRON_CUSTOMERS_DIR here. Acyclic: profiles.mjs imports node builtins + config.
|
|
230
232
|
import { unitRefusalsFor } from "./register-unit-record.mjs";
|
|
@@ -1528,6 +1530,11 @@ async function runStageLadder(name, opts, stageCodexHome = null) {
|
|
|
1528
1530
|
// appear: a postponed run resumes on its own, and a signed-out one cannot. The sentence rides after
|
|
1529
1531
|
// the colon, as `model_mismatch:` carries its detail, so no run-level classifier needs a new token.
|
|
1530
1532
|
else if (turn.signals?.signedOut && fail) fail = `engine_signed_out: ${turn.signals.signedOut}`;
|
|
1533
|
+
// AND ONE WHOSE TOOLS WERE ALL REFUSED IS NAMED, not left as the missing file the refusal caused. The
|
|
1534
|
+
// turn reported success and the stage failed for want of what its tools would have produced; a
|
|
1535
|
+
// `missing_file` reads as a model that did not write, and sends the reader to the wrong place. The
|
|
1536
|
+
// sentence says what fixes this host. After the sign-in, which is the more basic fault when both appear.
|
|
1537
|
+
else if (turn.signals?.toolsRefused && fail) fail = `engine_tools_refused: ${turn.signals.toolsRefused}`;
|
|
1531
1538
|
// A6 (addendum 2026-07-30): stop_reason max_tokens with ZERO usable output is a DETECTED FAULT with a
|
|
1532
1539
|
// name — never a silent paid retry. The turn ran to its output-token ceiling and the artifact never
|
|
1533
1540
|
// landed (a content fail on a "successful" turn), or the turn itself died at the ceiling (transport
|
|
@@ -2027,6 +2034,15 @@ async function runStageLadder(name, opts, stageCodexHome = null) {
|
|
|
2027
2034
|
note(`[${name}] the engine's sign-in could not be refreshed — breaking the ladder (a retry re-sends the same credential)`);
|
|
2028
2035
|
break;
|
|
2029
2036
|
}
|
|
2037
|
+
// EVERY TOOL CALL THE TURN MADE WAS REFUSED. On some hosts codex's own sandbox refuses the stage's
|
|
2038
|
+
// tool servers while the turn reports success, and the stage then fails for want of what the tools
|
|
2039
|
+
// would have produced. The next attempt spawns the same sandbox and is refused the same way, so a
|
|
2040
|
+
// retry re-buys the refusal: measured at three paid attempts per stage on a production run before the
|
|
2041
|
+
// identical-failure break stopped it. Break on the first. A turn where any call completed is not this.
|
|
2042
|
+
if (everyToolCallRefused(turn)) {
|
|
2043
|
+
note(`[${name}] every tool call this turn made was refused (${turn.mcpToolCallsRefused}) — breaking the ladder (a retry is refused the same way)`);
|
|
2044
|
+
break;
|
|
2045
|
+
}
|
|
2030
2046
|
// D3: overload (529 / status_overloaded) — stop the ladder here: an in-ladder re-attempt hammers an
|
|
2031
2047
|
// API that just said it is overloaded, seconds apart, on the SAME model. Breaking hands the failure
|
|
2032
2048
|
// to the existing machinery: the chain cascades models (FALLBACK_ELIGIBLE) and an exhausted chain
|
|
@@ -2816,7 +2832,7 @@ export function correctionHint(lastFail, { gridLedgerName = "common-law-grid.jso
|
|
|
2816
2832
|
`The driver computed every obligation and every identifier in it — the coverage unit, the query id, the hit ` +
|
|
2817
2833
|
`count, the unaccounted classes and terms, each deferred slice's own receipt reason. Record the named row(s) ` +
|
|
2818
2834
|
`through the \`record_coverage\` tool — {"row_id","status","reason"} per row, never by writing or editing any ` +
|
|
2819
|
-
`file: "status" EXACTLY one bare token of confirmed-clean / coverage-limited / deferred, "reason" the sentence ` +
|
|
2835
|
+
`file: "status" EXACTLY one bare token of confirmed-clean / coverage-limited / deferred / withheld-by-judgment, "reason" the sentence ` +
|
|
2820
2836
|
`the lawyer reads (qualifiers go in the reason, never in the status). ` +
|
|
2821
2837
|
`A row marked "open" cannot be confirmed-clean, and its own "open_because" says which of the two kinds ` +
|
|
2822
2838
|
`it is. A NEVER-SEARCHED slice — the active register provider cannot express it, so nothing can make it run — is ` +
|
|
@@ -2983,7 +2999,7 @@ export function correctionHint(lastFail, { gridLedgerName = "common-law-grid.jso
|
|
|
2983
2999
|
// run the driver stamped as form-required, and the stamp is conditional. On an unstamped run
|
|
2984
3000
|
// validators.registerFindings demands the table exactly as it did before and emits this label,
|
|
2985
3001
|
// so dropping the arm left the one lane that can still fire it with a generic hint.
|
|
2986
|
-
hint = "the file has a findings heading plus a Coverage ledger with a status row (confirmed-clean / coverage-limited / deferred)";
|
|
3002
|
+
hint = "the file has a findings heading plus a Coverage ledger with a status row (confirmed-clean / coverage-limited / deferred)" + (/common-law-findings/.test(lastFail) ? ", or each ledger row's status is recorded by calling `record_coverage_status` with `grid_spec_path`, the same spec path the grid tool was given" : "");
|
|
2987
3003
|
} else if (/negative-results|coverage-ledger|audit-trail|findings-heading/.test(lastFail)) {
|
|
2988
3004
|
hint = "the findings file carries ALL required sections: a findings heading, the Negative results matrix " +
|
|
2989
3005
|
"(every variant × platform row), the Coverage ledger with a status row, and the Audit trail call log";
|
|
@@ -47,7 +47,7 @@ export function managerGroupsVerdict({ idGroups, managerGroups, user, uid, why =
|
|
|
47
47
|
// was not established", and saying so is the whole point — the deployment that HAD this fault also
|
|
48
48
|
// had a check that reported nothing wrong.
|
|
49
49
|
if (!Array.isArray(idGroups) || !idGroups.length) {
|
|
50
|
-
return { state: "skip", message: `could not read the groups of ${user}${why ? ` — ${why}` : ""}. Not checked, not passed.` };
|
|
50
|
+
return { state: "skip", blocked: true, message: `could not read the groups of ${user}${why ? ` — ${why}` : ""}. Not checked, not passed.` };
|
|
51
51
|
}
|
|
52
52
|
if (!Array.isArray(managerGroups)) {
|
|
53
53
|
return { state: "skip", message: `no readable systemd --user manager for ${user}${why ? ` — ${why}` : ""}. `
|
|
@@ -195,22 +195,39 @@ export const refuseUndeclared = (params) => refuseUndeclaredShared(params, DECLA
|
|
|
195
195
|
* The Nice classes the frame judged necessary beyond the instructed ones, as strings. IMPURE (reads
|
|
196
196
|
* the run's own accepted call).
|
|
197
197
|
*
|
|
198
|
-
* THE PLAN COMPILE
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* the run this came from the delivered report said two such classes were "covered for the name and open
|
|
203
|
-
* for its variants" while the reviewing lawyer's scope included one of them throughout.
|
|
198
|
+
* THE PLAN COMPILE DOES NOT READ THIS. It takes `frameIdentifiedClassRows` below, where each added
|
|
199
|
+
* class keeps its reason and costs one identical-mark question rather than riding every entry (decision
|
|
200
|
+
* 18). The one caller left is the house-element check, which unions this list with the instructed
|
|
201
|
+
* classes because the client's own filings are best looked for across the widest class set.
|
|
204
202
|
*
|
|
205
203
|
* EMPTY IS THE ORDINARY ANSWER and must stay cheap: no frame yet, a legacy or replayed run whose
|
|
206
204
|
* accepted call predates the field, a frame that identified nothing — all of them return `[]`, the
|
|
207
|
-
* union is a no-op, and the
|
|
205
|
+
* union is a no-op, and the check looks in the instructed classes alone.
|
|
208
206
|
*/
|
|
209
207
|
export function frameIdentifiedClasses(runDir) {
|
|
210
208
|
const rows = lastAcceptedMatterFrame(runDir)?.identified_classes;
|
|
211
209
|
return (Array.isArray(rows) ? rows : []).map((r) => String(r?.class ?? "").trim()).filter(Boolean);
|
|
212
210
|
}
|
|
213
211
|
|
|
212
|
+
/**
|
|
213
|
+
* The same rows WITH THEIR REASONS, for the register plan (decision 18). IMPURE, like its sibling.
|
|
214
|
+
*
|
|
215
|
+
* The reason is why the class is here and it is half the row: the bound on this widening is that a
|
|
216
|
+
* class is added only for goods the CLIENT'S OWN business plainly reaches, and a class carrying no
|
|
217
|
+
* stated reason cannot be checked against that by anyone. The plan compiler drops such a row rather
|
|
218
|
+
* than searching it, so the reason is load-bearing and not documentation.
|
|
219
|
+
*
|
|
220
|
+
* Kept beside `frameIdentifiedClasses` rather than replacing it: the other caller verifies a proposed
|
|
221
|
+
* house element against an owner-scoped lookup, where the widest class set is the right one to look in
|
|
222
|
+
* and a reason would mean nothing.
|
|
223
|
+
*/
|
|
224
|
+
export function frameIdentifiedClassRows(runDir) {
|
|
225
|
+
const rows = lastAcceptedMatterFrame(runDir)?.identified_classes;
|
|
226
|
+
return (Array.isArray(rows) ? rows : [])
|
|
227
|
+
.map((r) => ({ class: String(r?.class ?? "").trim(), reason: String(r?.reason ?? "").trim() }))
|
|
228
|
+
.filter((r) => r.class);
|
|
229
|
+
}
|
|
230
|
+
|
|
214
231
|
/**
|
|
215
232
|
* The forms of the name this run's client ratified, as strings. IMPURE (reads the run's own accepted
|
|
216
233
|
* call). Empty or one form is the ordinary answer.
|
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 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.
|
|
5
|
+
"version": "0.3.3-beta.1",
|
|
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": {
|
|
@@ -27,6 +27,14 @@
|
|
|
27
27
|
"tool": "record_dispositions",
|
|
28
28
|
"why": "no fixture yet; accumulating semantics claimed in the tool description ('Everything already recorded is kept') and not yet planted"
|
|
29
29
|
},
|
|
30
|
+
{
|
|
31
|
+
"tool": "record_coverage_status",
|
|
32
|
+
"why": "both declared fields are required, so a call has no top-level key to omit; its accumulation by unit (a later call keeps the units it omits) is pinned in a-common-law-coverage-status-is-read-as-data.test.mjs"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"tool": "record_withheld_families",
|
|
36
|
+
"why": "its one declared field is required, so a call has no top-level key to omit; its accumulation by qid (a later call keeps the families it omits) is pinned in every-waiting-family-is-asked-or-withheld.test.mjs"
|
|
37
|
+
},
|
|
30
38
|
{
|
|
31
39
|
"tool": "record_unit_note",
|
|
32
40
|
"why": "no fixture yet; every field optional, which is the exposed shape — this row is the highest-priority one to close"
|
|
@@ -61,11 +69,12 @@
|
|
|
61
69
|
{
|
|
62
70
|
"tool": "record_clearance_variants",
|
|
63
71
|
"fields": [
|
|
72
|
+
"goods_words",
|
|
64
73
|
"incumbent_classes",
|
|
65
|
-
"
|
|
66
|
-
"
|
|
74
|
+
"search_floor",
|
|
75
|
+
"watchlist_owners"
|
|
67
76
|
],
|
|
68
|
-
"why": "a partial silently empties the register-axis search floor, narrowing the run's own definition of coverage"
|
|
77
|
+
"why": "a partial silently empties the register-axis search floor, narrowing the run's own definition of coverage. `goods_words` joins them for the same reason and one worse: an emptied goods list reads as \"this matter named no goods\" rather than as a partial call, so the crowded sweep it exists to narrow goes back to returning more filings than anyone can read, and nothing says why. The MERGE preserves all four (mergeClearanceVariantsCall, keep-if-absent) — these rows record that the ACCEPTOR alone does not."
|
|
69
78
|
},
|
|
70
79
|
{
|
|
71
80
|
"tool": "record_blind_frame",
|
|
@@ -639,7 +639,7 @@ export async function knockoutInner(ctx, job, opts = {}) {
|
|
|
639
639
|
// for reconcile-runs' exact liveness test. The stepper and the identity are separate calls now.
|
|
640
640
|
...identitySeed(),
|
|
641
641
|
stepIndex: 0, stepLabel: STEPS[0], stepN: 1, stepTotal: STEPS.length,
|
|
642
|
-
|
|
642
|
+
review: null, url: null, failedStage: null, reason: null, deliveredAt: null,
|
|
643
643
|
// A5/A3: a re-run of a previously-terminal knockout may reopen the state ONLY because the resume
|
|
644
644
|
// guard cleared the sentinel (ctx.stateReset threads that authority); startedAt is no longer
|
|
645
645
|
// seeded anywhere — writeRunStatus backfills it first-write-wins.
|
|
@@ -1092,11 +1092,11 @@ export async function knockoutInner(ctx, job, opts = {}) {
|
|
|
1092
1092
|
const deliveredAt = new Date().toISOString();
|
|
1093
1093
|
// `tier` beside `verdict` — the same band word under the name the clearance lane records it by, so a
|
|
1094
1094
|
// reader of either lane's status finds the rating in one place. `verdict` stays as it was.
|
|
1095
|
-
writeRunStatus(ctx, { state: "delivered",
|
|
1095
|
+
writeRunStatus(ctx, { state: "delivered", tier: overall, statement: published.statement, url: published.url, reports: published.reports.map((r) => ({ mark: r.mark, url: r.url })), deliveredAt, sendPending: true, stepIndex: STEPS.length - 1, stepLabel: STEPS[STEPS.length - 1], stepN: STEPS.length, stepTotal: STEPS.length });
|
|
1096
1096
|
// — the knockout lane's pool copy learns its terminal state the same way,
|
|
1097
1097
|
// for the same reason: publish returns the pool dir, and `state: "delivered"` is decided after it
|
|
1098
1098
|
// returns. Same seam, same best-effort contract, no lane-specific exception to write down.
|
|
1099
|
-
const stamp = writeSettleStamp(published.poolRunDir, { state: "delivered",
|
|
1099
|
+
const stamp = writeSettleStamp(published.poolRunDir, { state: "delivered", tier: overall, deliveredAt, runId: published.runId ?? run.runId, lane: "knockout" });
|
|
1100
1100
|
if (!stamp.written) note(`delivery: settle stamp not written (${stamp.reason})`);
|
|
1101
1101
|
const archived = archive(run);
|
|
1102
1102
|
rollupStatus(run.studioRoot);
|