clearotron 0.4.0-beta.2 → 0.4.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.
Files changed (49) hide show
  1. package/THIRD-PARTY-NOTICES.md +5 -5
  2. package/build-info.json +2 -2
  3. package/driver/CHANGELOG.md +9 -0
  4. package/driver/coverage-form.mjs +6 -0
  5. package/driver/coverage-ledger.mjs +14 -2
  6. package/driver/driver.config.mjs +24 -7
  7. package/driver/engine/mcp/stdio-server.mjs +12 -1
  8. package/driver/package.json +1 -1
  9. package/driver/pipeline-knockout.mjs +29 -8
  10. package/driver/pipeline.mjs +58 -8
  11. package/driver/progress.mjs +6 -1
  12. package/driver/publish/knockout.mjs +16 -3
  13. package/driver/publish/render-knockout.mjs +38 -11
  14. package/driver/record-carry.mjs +16 -0
  15. package/driver/reference-strip-signatures.mjs +11 -1
  16. package/driver/register-plan.mjs +7 -3
  17. package/driver/register-served.mjs +91 -0
  18. package/driver/remedy-accounting.mjs +38 -9
  19. package/driver/score-redaction.mjs +389 -20
  20. package/driver/skills/clearance-register/SKILL.md +0 -8
  21. package/driver/skills/clearance-register/digest.md +1 -1
  22. package/driver/skills/clearance-register/providers/corsearch.md +1 -1
  23. package/driver/skills/clearance-register/register-recipes.md +3 -9
  24. package/driver/skills/clearance-search/SKILL.md +2 -2
  25. package/driver/skills/clearance-search/phase2-execution.md +1 -1
  26. package/driver/skills/knockout-assess/SKILL.md +2 -0
  27. package/driver/stages-knockout.mjs +22 -0
  28. package/driver/suite-census.json +120 -18
  29. package/driver/unit-inventory.mjs +38 -43
  30. package/mcp-server/CHANGELOG.md +4 -0
  31. package/mcp-server/package.json +1 -1
  32. package/node_modules/brace-expansion/index.js +78 -22
  33. package/node_modules/brace-expansion/package.json +1 -1
  34. package/node_modules/readdir-glob/node_modules/brace-expansion/index.js +78 -22
  35. package/node_modules/readdir-glob/node_modules/brace-expansion/package.json +1 -1
  36. package/package.json +1 -1
  37. package/portal-ui/package.json +2 -2
  38. package/providers/jx/src/core.js +0 -1
  39. package/providers/jx/src/judge.js +0 -1
  40. package/providers/jx/src/nativeread.js +0 -1
  41. package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
  42. package/providers/oauth-mcp-bridge/package.json +1 -1
  43. package/providers/signa/src/core.js +37 -14
  44. package/scripts/e2e-first-time.mjs +12 -4
  45. package/scripts/e2e-scenario-ops.mjs +25 -2
  46. package/scripts/e2e.mjs +113 -15
  47. package/scripts/mint-reference-strip-backlog.mjs +36 -2
  48. package/scripts/score.mjs +102 -29
  49. package/shared/identifier-scan.mjs +31 -1
@@ -2079,7 +2079,7 @@ export function foldSupplementalEntries(plan, entries) {
2079
2079
  const eTerm = String(e.term ?? e.terms?.[0] ?? "");
2080
2080
  if (inBatch.has(e.qid) && inBatch.get(e.qid) === eTerm) continue; // the same row twice — one refusal
2081
2081
  if (inBatch.has(e.qid)) {
2082
- refused.push({ qid: e.qid, term: String(e.term ?? e.terms?.[0] ?? ""),
2082
+ refused.push({ qid: e.qid, term: String(e.term ?? e.terms?.[0] ?? ""), kind: "identity-collision",
2083
2083
  issue: planRowRefusal({ qid: e.qid, issue:
2084
2084
  `a different term ("${inBatch.get(e.qid)}") already minted this identity in the same batch, so `
2085
2085
  + `one of the two would be dropped with nothing recorded. Two DIFFERENT terms sharing one qid is `
@@ -2098,7 +2098,7 @@ export function foldSupplementalEntries(plan, entries) {
2098
2098
  e = kept;
2099
2099
  const issues = entryTermIssues(e);
2100
2100
  if (issues.length) {
2101
- refused.push({ qid: e.qid, term: String(e.term ?? e.terms?.[0] ?? ""),
2101
+ refused.push({ qid: e.qid, term: String(e.term ?? e.terms?.[0] ?? ""), kind: "malformed-term",
2102
2102
  issue: planRowRefusal({ qid: e.qid, issue: issues[0].issue }) });
2103
2103
  continue;
2104
2104
  }
@@ -2107,7 +2107,11 @@ export function foldSupplementalEntries(plan, entries) {
2107
2107
  const key = entryQuestionKey(e, plan);
2108
2108
  const twin = key ? asked.get(key) : undefined;
2109
2109
  if (twin) {
2110
- refused.push({ qid: e.qid, term: String(e.term ?? e.terms?.[0] ?? ""),
2110
+ // THE TWIN IS A FIELD, NOT ONLY A PHRASE IN THE PROSE. This row is the only place that knows the
2111
+ // question is already asked, and by which plan row. A reader could parse it back out of the
2112
+ // sentence; a caller cannot be asked to. The remedy accounting reads it to tell a duplicate
2113
+ // refusal (answered elsewhere) from the other two kinds (genuinely unasked).
2114
+ refused.push({ qid: e.qid, term: String(e.term ?? e.terms?.[0] ?? ""), kind: "duplicate-question", twin,
2111
2115
  issue: planRowRefusal({ qid: e.qid, issue:
2112
2116
  `it asks the same question as plan row "${twin}" once regions are resolved (an entry with no `
2113
2117
  + `regions of its own inherits the plan's, so an empty list is the WIDEST scope, not a narrower `
@@ -0,0 +1,91 @@
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
+ // register-served.mjs — which register actually served this run, as a field in the run's own record.
5
+ //
6
+ // THE KNOCKOUT LANE ALREADY RECORDS THIS and the clearance lane recorded nothing equivalent, so the same
7
+ // question about a clearance run had no answer to look up. What stood in for the field was searching for a
8
+ // register's NAME as a substring inside three artifacts that happen to mention it — and measured on the
9
+ // archived runs that instrument could not attribute more than half of two scenarios' runs to any register
10
+ // at all. Those are runs whose register is unknown to the record, not runs that used none, and nothing in
11
+ // the record said which.
12
+ //
13
+ // WHAT SERVED, NOT WHAT WAS CONFIGURED. The value is taken from the same resolver the dispatch itself goes
14
+ // through, at the moment a register is used, rather than from the frozen settings the run started on. The
15
+ // per-run settings sidecar is deliberately NOT the home for it: that file exists to freeze what the run
16
+ // was sold, so a resume runs on what it started on, and a register recorded there would be the launch
17
+ // value presented with a frozen file's authority. A run whose provider changed under it would read wrong
18
+ // and confident, which is the failure this field exists to end rather than to relocate.
19
+ //
20
+ // A LIST, BECAUSE ONE IS A LIST OF ONE. A run that used two registers says two rather than naming the
21
+ // first, and a reader never has to know whether the field is a value or a set.
22
+ //
23
+ // BEST-EFFORT AND SILENT, like every other telemetry write in this lane: a field that cannot be recorded
24
+ // must never fail a register call. An absence is then the honest answer — this run's register is unknown
25
+ // to the record — which is exactly the state the archive is full of and the reason nothing here guesses.
26
+ import { readRunStatus, writeRunStatus } from "./progress.mjs";
27
+
28
+ /** The status key. One name, so the writer and every reader cannot drift. */
29
+ export const REGISTERS_SERVED = "registersServed";
30
+
31
+ // Per-process memo so the common case — every call of a run resolving the same register — costs one read
32
+ // and one write for the whole run rather than one of each per call. Keyed by run directory because a
33
+ // process can carry more than one (a resume, a batch).
34
+ const seen = new Map();
35
+
36
+ /**
37
+ * Note that `id` served this run, and return the run's full list. Writes only when the set changes.
38
+ * `id` is the resolver's own answer, never a raw environment read. PURE apart from the run's record.
39
+ */
40
+ export function noteRegisterServed(runDir, id) {
41
+ const register = String(id ?? "").trim().toLowerCase();
42
+ if (!runDir || !register) return null;
43
+ let set = seen.get(runDir);
44
+ if (!set) {
45
+ // A RESUME READS WHAT THE EARLIER PROCESS RECORDED, so a run that changed register across a resume
46
+ // says both rather than only the half this process saw.
47
+ set = new Set(registersServedFrom(readRunStatus(runDir)));
48
+ seen.set(runDir, set);
49
+ }
50
+ if (set.has(register)) return [...set];
51
+ set.add(register);
52
+ const registers = [...set];
53
+ try { writeRunStatus(null, { [REGISTERS_SERVED]: registers }, runDir); }
54
+ catch { /* telemetry must never break a register call */ }
55
+ return registers;
56
+ }
57
+
58
+ /** The registers a run's status records, or [] when it records none. PURE. */
59
+ export function registersServedFrom(status) {
60
+ const v = status?.[REGISTERS_SERVED];
61
+ return Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim()) : [];
62
+ }
63
+
64
+ /**
65
+ * One line for a reader, three-valued rather than two: the register, the registers, or the absence NAMED
66
+ * as an absence. "not recorded" is not "none served" and a reader must never have to guess which.
67
+ * PURE.
68
+ */
69
+ export function registerServedLine(status) {
70
+ const r = registersServedFrom(status);
71
+ if (!r.length) return "NOT RECORDED — this run's record carries no register, which is not the same fact as using none";
72
+ return r.length === 1 ? r[0] : `${r.length} registers served this run: ${r.join(", ")}`;
73
+ }
74
+
75
+ /**
76
+ * The caveat a whole-run provider tally needs when the run served more than one register: `{}` when the
77
+ * tally's single key cannot be wrong, and the spanning list when it can. PURE.
78
+ *
79
+ * A TALLY'S KEY IS RESOLVED AT PUBLISH and the tally spans the whole run, so on a run whose register
80
+ * changed part-way every call is filed under whichever was active at the end. That is the shape the
81
+ * served field exists to end, and until the field existed nothing could contradict it. This does not
82
+ * split the tally — attributing a whole-run count to one of two registers is a different question — it
83
+ * stops the two register fields in one record disagreeing in silence.
84
+ */
85
+ export function providerUsageCaveat(served) {
86
+ const r = Array.isArray(served) ? served.filter((x) => typeof x === "string" && x.trim()) : [];
87
+ return r.length > 1 ? { providerUsageSpans: r } : {};
88
+ }
89
+
90
+ /** Test seam: forget the per-process memo, so an arm can drive a second run in the same process. */
91
+ export function forgetRegistersServed() { seen.clear(); }
@@ -136,15 +136,40 @@ function material(block) {
136
136
  * `unaccounted` when there is no reason either — a legacy receipt states neither, and inventing
137
137
  * "not dispatched" from silence would be the same guess in a new place);
138
138
  * 2. ANY slice that did not land clean ⇒ `dispatch-failed`, naming the first such qid. Every slice
139
- * must land, exactly as verifyRegisterDirectiveClose requires of a directive — one convention;
139
+ * must land, exactly as verifyRegisterDirectiveClose requires of a directive — one convention. A
140
+ * slice the FOLD refused as already-asked is read through the twin it duplicates first, because that
141
+ * slice cannot land and the twin is where its question was answered;
140
142
  * 3. all landed, ANY slice material ⇒ `found`;
141
143
  * 4. all landed, EVERY slice a counted zero ⇒ `searched-empty`;
142
144
  * 5. otherwise ⇒ `unaccounted`, with the shape that defeated the join stated.
143
145
  * PURE.
144
146
  */
145
- export function classifyRemedyTerm(row, { blocksByQid = new Map(), executedQids = new Set() } = {}) {
147
+ export function classifyRemedyTerm(row, { blocksByQid = new Map(), executedQids = new Set(), foldRefusals = new Map() } = {}) {
146
148
  const qids = (Array.isArray(row?.qids) ? row.qids : []).filter(Boolean);
147
- const slices = qids.map((q) => sliceRow(q, blocksByQid.get(q), executedQids.has(q)));
149
+ // — A SLICE THE FOLD REFUSED AS ALREADY-ASKED IS ANSWERED BY THE ROW IT DUPLICATES.
150
+ //
151
+ // The fold refuses a minted qid when the plan already holds a row asking the same question, and hands
152
+ // back the twin's qid. Such a slice can NEVER land: it was never frozen into the plan, and the reopen's
153
+ // own re-attempt filters it out on purpose. Requiring it to land anyway made rule 2 below fire on it and
154
+ // return before rules 3-5 could look at the rest, so a term whose question HAD been asked and answered
155
+ // was recorded `dispatch-failed` — which reads as an engine fault a re-run would fix, where the truth is
156
+ // that the coverage is already on the plan's own row. Measured on a delivered breadth run: one term, its
157
+ // twin enumerated with 71 records, the dominant-element gap left open and clamped on that basis.
158
+ //
159
+ // ONLY THE DUPLICATE KIND RESOLVES. An identity collision or a malformed term is a genuine fault and
160
+ // stays `dispatch-failed`: nothing else asked those questions, so nothing else answers them.
161
+ const resolve = (q) => {
162
+ const r = foldRefusals.get?.(q);
163
+ return r && r.kind === "duplicate-question" && r.twin ? String(r.twin) : q;
164
+ };
165
+ const landsAs = new Map(qids.map((q) => [q, resolve(q)]));
166
+ const blockFor = (q) => blocksByQid.get(landsAs.get(q) ?? q);
167
+ const ranAs = (q) => executedQids.has(landsAs.get(q) ?? q);
168
+ const slices = qids.map((q) => {
169
+ const via = landsAs.get(q);
170
+ const slice = sliceRow(q, blockFor(q), ranAs(q));
171
+ return via && via !== q ? { ...slice, answered_by: via, refused_at_fold: "duplicate-question" } : slice;
172
+ });
148
173
  if (!qids.length) {
149
174
  const reason = String(row?.dispatch_reason ?? "").trim();
150
175
  return reason
@@ -152,16 +177,16 @@ export function classifyRemedyTerm(row, { blocksByQid = new Map(), executedQids
152
177
  : { class: "unaccounted", basis: "no-mapping", reason: "the receipt records no slice and no reason for this term — the join cannot say whether it was searched", slices };
153
178
  }
154
179
  for (const q of qids) {
155
- const b = blocksByQid.get(q);
156
- if (sliceLanded(b, executedQids.has(q))) continue;
157
- const why = !executedQids.has(q) ? "slice-not-landed"
180
+ const b = blockFor(q);
181
+ if (sliceLanded(b, ranAs(q))) continue;
182
+ const why = !ranAs(q) ? "slice-not-landed"
158
183
  : !b ? "no-band-block"
159
184
  : b.deferred === true ? "capability-gap-deferral"
160
185
  : b.error === true ? "provider-error"
161
186
  : "collapsed-slice";
162
187
  return { class: "dispatch-failed", basis: why, reason: `${why}:${q}${b?.reason ? ` — ${clip(b.reason, 200)}` : ""}`, slices };
163
188
  }
164
- const blocks = qids.map((q) => blocksByQid.get(q));
189
+ const blocks = qids.map((q) => blockFor(q));
165
190
  if (blocks.some(material)) return { class: "found", basis: "band-block", reason: null, slices };
166
191
  if (blocks.every(countedZero)) return { class: "searched-empty", basis: "counted-zero", reason: null, slices };
167
192
  const odd = blocks.find((b) => !countedZero(b));
@@ -186,14 +211,18 @@ const emptyTotals = () => {
186
211
  * `out_of_scope` carries the directives that have no term unit (source-layer channels), with the reason
187
212
  * — counted and named, never silently absent.
188
213
  *
214
+ * `foldRefusals` maps a minted qid the FOLD refused to `{ kind, twin }`, as `foldSupplementalEntries`
215
+ * recorded it. It is how a term whose slice was refused as already-asked reads through the plan row that
216
+ * answered it instead of counting as a slice that failed to land.
217
+ *
189
218
  * Deterministic; no timestamps (the caller stamps `ts`), no IO, no judgment. PURE.
190
219
  */
191
- export function accountRemedyTerms({ terms = [], blocksByQid = new Map(), executedQids = new Set(), outOfScope = [] } = {}) {
220
+ export function accountRemedyTerms({ terms = [], blocksByQid = new Map(), executedQids = new Set(), foldRefusals = new Map(), outOfScope = [] } = {}) {
192
221
  const totals = emptyTotals();
193
222
  const byDirective = {};
194
223
  const rows = [];
195
224
  for (const t of Array.isArray(terms) ? terms : []) {
196
- const c = classifyRemedyTerm(t, { blocksByQid, executedQids });
225
+ const c = classifyRemedyTerm(t, { blocksByQid, executedQids, foldRefusals });
197
226
  const directive = String(t?.directive ?? "");
198
227
  totals.terms++;
199
228
  totals[key(c.class)]++;