clearotron 0.4.1-beta.6 → 0.5.0-beta.0
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/README.md +1 -0
- package/THIRD-PARTY-NOTICES.md +1 -1
- package/bin/start.mjs +33 -8
- package/build-info.json +2 -2
- package/driver/CHANGELOG.md +57 -0
- package/driver/ask-ledger.mjs +86 -15
- package/driver/band-shape.mjs +61 -11
- package/driver/band-size.mjs +10 -0
- package/driver/close-verify.mjs +29 -2
- package/driver/common-law-coverage-status.mjs +1 -1
- package/driver/connotation-search.mjs +7 -9
- package/driver/contract-e3-backlog.mjs +34 -34
- package/driver/contract-e3-baseline.json +5 -5
- package/driver/contract-vocabulary.mjs +12 -12
- package/driver/corrections-delivered.mjs +113 -0
- package/driver/corrections-feedforward.mjs +16 -6
- package/driver/corrective-arm.mjs +11 -8
- package/driver/coverage-form.mjs +3 -0
- package/driver/coverage-union.mjs +11 -2
- package/driver/digest-queue.mjs +1 -1
- package/driver/disposition-call.mjs +1 -1
- package/driver/document-coverage.mjs +3 -2
- package/driver/effective-scope.mjs +2 -2
- package/driver/engine/mcp/band-server.mjs +77 -13
- package/driver/engine/mcp/dispositions-server.mjs +2 -2
- package/driver/engine/mcp/recording-server.mjs +10 -6
- package/driver/findings-model.mjs +16 -4
- package/driver/floor-duty.mjs +9 -0
- package/driver/gate-metrics.mjs +7 -1
- package/driver/gateway.mjs +28 -14
- package/driver/goods-evidence.mjs +295 -0
- package/driver/knockout-assess-record.mjs +1 -1
- package/driver/narrative-refutation-record.mjs +40 -2
- package/driver/package.json +1 -1
- package/driver/pipeline.mjs +1945 -314
- package/driver/placement-decisions.mjs +243 -0
- package/driver/placement-form.mjs +42 -8
- package/driver/placement-union.mjs +14 -6
- package/driver/provider-usage.mjs +10 -7
- package/driver/publish/audit-from-spine.mjs +5 -2
- package/driver/publish/index.mjs +19 -4
- package/driver/publish/xlsx.mjs +6 -6
- package/driver/recall-reconciliation.mjs +15 -5
- package/driver/record-carry.mjs +70 -6
- package/driver/record-discard.mjs +7 -1
- package/driver/record-fold.mjs +14 -0
- package/driver/record-goods.mjs +101 -5
- package/driver/register-plan.mjs +89 -0
- package/driver/registry-fidelity.mjs +34 -3
- package/driver/remedy-accounting.mjs +52 -14
- package/driver/repair-composers.mjs +265 -105
- package/driver/repair-scope.mjs +327 -0
- package/driver/repair-targets.mjs +92 -0
- package/driver/result-noun-fields.mjs +3 -3
- package/driver/screen-gate.mjs +96 -0
- package/driver/skills/clearance-common-law/SKILL.md +8 -0
- package/driver/skills/clearance-register/SKILL.md +5 -5
- package/driver/skills/clearance-register/digest.md +14 -14
- package/driver/skills/clearance-register/unit.md +3 -3
- package/driver/skills/clearance-search/delivery-contract.md +2 -58
- package/driver/skills/clearance-search/report-prose.md +12 -3
- package/driver/skills/clearance-search/risk-framework-triage.md +1 -1
- package/driver/skills/clearance-search/synthesis-rules.md +10 -13
- package/driver/skills/clearance-variants/SKILL.md +4 -4
- package/driver/skills/knockout-assess/SKILL.md +9 -14
- package/driver/skills/knockout-frame/SKILL.md +5 -5
- package/driver/skills/narrative-refutation/SKILL.md +1 -1
- package/driver/skills/placement-inquiry/SKILL.md +1 -1
- package/driver/skills/whatif-memo/SKILL.md +1 -1
- package/driver/stage-context.mjs +2 -1
- package/driver/stages-knockout.mjs +1 -1
- package/driver/stages.mjs +95 -60
- package/driver/suite-census.json +367 -19
- package/driver/synthesis-record.mjs +310 -8
- package/driver/terminal-clamp.mjs +53 -2
- package/driver/verify-knockout.mjs +22 -4
- package/driver/verify.mjs +23 -3
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/lib/driver.mjs +5 -0
- package/mcp-server/lib/events.mjs +25 -4
- package/mcp-server/lib/findings.mjs +4 -2
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/jx/src/core.js +3 -4
- package/providers/jx/src/judge.js +2 -2
- package/providers/jx/src/nativeread.js +1 -2
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/scripts/e2e-first-time.mjs +29 -12
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
<a href="LICENSE"><img src="https://img.shields.io/badge/licence-AGPL--3.0--only-860F09?style=flat-square" alt="Licence: AGPL-3.0-only"></a>
|
|
10
10
|
<a href=".nvmrc"><img src="https://img.shields.io/badge/node-%E2%89%A5%2022.13-250902?style=flat-square" alt="Node 22.13+"></a>
|
|
11
11
|
<a href="https://scorecard.dev/viewer/?uri=github.com/CordilleraSarl/clearotron"><img src="https://img.shields.io/ossf-scorecard/github.com/CordilleraSarl/clearotron?label=openssf%20scorecard&style=flat-square" alt="OpenSSF Scorecard"></a>
|
|
12
|
+
<a href="https://www.bestpractices.dev/projects/15666"><img src="https://img.shields.io/cii/level/15666?label=openssf%20best%20practices&style=flat-square" alt="OpenSSF Best Practices"></a>
|
|
12
13
|
</p>
|
|
13
14
|
|
|
14
15
|
Before your company commits to a name, find out what stands in its way. Give Clearotron the name, the
|
package/THIRD-PARTY-NOTICES.md
CHANGED
package/bin/start.mjs
CHANGED
|
@@ -379,6 +379,32 @@ function liveClientDoor(unitEnvFile = unitEnvPath(), run = execFileSync) {
|
|
|
379
379
|
}
|
|
380
380
|
|
|
381
381
|
/** The two ports, from the environment or the shipped defaults. Throws on a value that is not a port. */
|
|
382
|
+
// ── WHAT `start` SAYS ABOUT A CLIENT DOOR THAT IS NOT UP (the owner's words, 2026-10-18) ──────────────
|
|
383
|
+
//
|
|
384
|
+
// The client door is non-fatal: the portal and the engine door serve without it. It drains no queue, so its
|
|
385
|
+
// exit is not the queue worker's, whose line tells the reader to start one with `--watch`. And a door still
|
|
386
|
+
// up when the 30-second wait ran out has printed nothing, so it does not send the reader to look at its
|
|
387
|
+
// output for a reason. Each line says what is true of the client door.
|
|
388
|
+
|
|
389
|
+
/** The line printed when a non-fatal child exits: the client door's own, else the queue worker's. PURE. */
|
|
390
|
+
export function nonFatalExitLine({ name, script, code, signal, clientDoor = false }) {
|
|
391
|
+
const how = signal ? `on ${signal}` : `with code ${code}`;
|
|
392
|
+
return clientDoor
|
|
393
|
+
? `\nstart: ${name} (${script}) exited ${how} — the portal and the engine door are unaffected; a client assistant cannot connect until it is up.\n`
|
|
394
|
+
: `\nstart: ${name} (${script}) exited ${how} — the portal is still up, but nothing is draining the queue now. Start one with:\n node ${script} --watch\n`;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* The summary's two lines for a client door that is not running. A door that exited printed why before it
|
|
399
|
+
* went; a door still up when the wait ran out did not answer. PURE.
|
|
400
|
+
*/
|
|
401
|
+
export function clientDoorDownLines({ host, port, exited }) {
|
|
402
|
+
return [
|
|
403
|
+
` Client door NOT RUNNING on ${host}:${port} — ${exited ? "its output above says why" : "it did not answer within 30s"}. The portal and`,
|
|
404
|
+
" the engine door are unaffected; a client assistant cannot connect until it is up.",
|
|
405
|
+
];
|
|
406
|
+
}
|
|
407
|
+
|
|
382
408
|
export function resolvePorts(env = {}) {
|
|
383
409
|
const one = (name, dflt) => {
|
|
384
410
|
const raw = String(env[name] ?? "").trim();
|
|
@@ -2208,7 +2234,7 @@ if (isMain) {
|
|
|
2208
2234
|
const children = [];
|
|
2209
2235
|
let stopping = false;
|
|
2210
2236
|
|
|
2211
|
-
const start = (name, script, env, { args = [], fatal = true } = {}) => {
|
|
2237
|
+
const start = (name, script, env, { args = [], fatal = true, clientDoor = false } = {}) => {
|
|
2212
2238
|
const child = spawn(process.execPath, [join(REPO, script), ...args], {
|
|
2213
2239
|
cwd: REPO,
|
|
2214
2240
|
// Its OWN process group, so Ctrl-C reaches this supervisor alone and teardown is one ordered
|
|
@@ -2256,10 +2282,9 @@ if (isMain) {
|
|
|
2256
2282
|
if (stopping) return;
|
|
2257
2283
|
// REPORTED. Not absorbed into a panel that says a service is unreachable.
|
|
2258
2284
|
if (!fatal) {
|
|
2259
|
-
// Loudly, because
|
|
2260
|
-
//
|
|
2261
|
-
err(
|
|
2262
|
-
+ ` but nothing is draining the queue now. Start one with:\n node ${script} --watch\n`);
|
|
2285
|
+
// Loudly, because what this child did stops the instant it goes — but the portal keeps serving, which
|
|
2286
|
+
// is the whole point of a child this install is supported without. The client door says its own line.
|
|
2287
|
+
err(nonFatalExitLine({ name, script, code, signal, clientDoor }));
|
|
2263
2288
|
return;
|
|
2264
2289
|
}
|
|
2265
2290
|
err(childExitReport({ name, script, code, signal, tail }));
|
|
@@ -2444,7 +2469,7 @@ if (isMain) {
|
|
|
2444
2469
|
// assistant is correctly connected to. The unit IS the door; this path just does not add another.
|
|
2445
2470
|
const clientDoor = adoptedClientDoor
|
|
2446
2471
|
? null
|
|
2447
|
-
: start("the client door", "mcp-server/http-server-client.mjs", envs.client, { fatal: false });
|
|
2472
|
+
: start("the client door", "mcp-server/http-server-client.mjs", envs.client, { fatal: false, clientDoor: true });
|
|
2448
2473
|
// ── THE BANNER NAMES THIS DOOR ONLY ONCE IT ANSWERS ─────────────────────────────────────────────────
|
|
2449
2474
|
//
|
|
2450
2475
|
// The engine door and the portal are each awaited above before anything is printed. This door was
|
|
@@ -2492,8 +2517,8 @@ if (isMain) {
|
|
|
2492
2517
|
else
|
|
2493
2518
|
say(` It refuses every caller until a key is issued: ${DEMO ? demoConnectCommand({ prefix: invocationPrefix(), base: paths.base }) : keyIssueCommand({ prefix: invocationPrefix(), demo: DEMO, user, base: paths.base, defaultBase: join(homedir(), "trademark") })}`);
|
|
2494
2519
|
} else {
|
|
2495
|
-
|
|
2496
|
-
|
|
2520
|
+
// A door that exited printed why; one still up when the wait ran out printed nothing, and is not sent there.
|
|
2521
|
+
for (const line of clientDoorDownLines({ host: HOST, port: ports.client, exited: clientDoor?.child?.exitCode !== null })) say(line);
|
|
2497
2522
|
}
|
|
2498
2523
|
say(` Engine door http://${HOST}:${ports.mcp}/mcp — the portal's Start button calls this. Staff.`);
|
|
2499
2524
|
say("");
|
package/build-info.json
CHANGED
package/driver/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
# clearotron-driver
|
|
2
2
|
|
|
3
|
+
## 0.5.0-beta.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Fixed: Faults the final checks find in a finding's own summary sentence, a short entry or a grouped line are now corrected before delivery. Before, they shipped as found.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Fixed: A record whose register lookup failed, or came back empty, no longer counts as retrieved. The clearance now fetches it again, or says it could not.
|
|
12
|
+
- Fixed: A finding added by a late correction now appears with its own card in the delivered report.
|
|
13
|
+
- Fixed: A repeated search can change the ranking after the report was written, too late to rewrite the report. The report now says how many register records were re-ranked, and that the audit trail gives their final ranking. A clear result becomes conditional.
|
|
14
|
+
- Fixed: The report and the audit workbook now say a record was "retrieved from the register" rather than "read". The workbook's summary explains what that means.
|
|
15
|
+
- Fixed: A clearance that resumes after a pause is now held to the same standard as one that never paused. The conditions on its result are those that apply to the report it delivers. A live registration identical or near-identical to the mark that the report leaves unaddressed makes the result conditional, and the report says so. One the clearance's own final pass answers no longer does.
|
|
16
|
+
- Fixed: A result is no longer held at conditional just because a wider search that was rightly withheld was asked for again. Such a result can now read clear, and its goods category reads as searched.
|
|
17
|
+
- Fixed: Knockout ratings now weigh the earlier filings found for the mark itself. The register review follows the same ranking of results as the step before it, so some ratings change.
|
|
18
|
+
- Fixed: Knockout reports no longer carry two standing caveats. The caveat on a conditional result stays.
|
|
19
|
+
- Fixed: A resumed clearance no longer shows the false line "A machine check on the report did not pass".
|
|
20
|
+
- Fixed: A register record that a repeated search finds after the results were ranked is now ranked and reviewed before the report is written. If that cannot be done, the report is still delivered, and it counts the record among those left open. Before, such a record could be left out of the ranking, the review and the report.
|
|
21
|
+
- Fixed: A clearance no longer stops without a report when register records it retrieved are still unaddressed at the end. The report is delivered, a clear result becomes conditional, and the report states once how many records remain open.
|
|
22
|
+
- For operators: When a client door is not up, `clearotron start` now says what is true of it. Before, it gave the queue worker's advice, or pointed to output it never printed.
|
|
23
|
+
- Fixed: After a late correction, the Audit Trail delivered with the report describes the corrected result, and its open questions agree with it. If it cannot be rebuilt, the earlier Audit Trail is withheld rather than shown as current. The audit workbook is still built from the current findings.
|
|
24
|
+
- Fixed: The Audit Trail now lists each register record set aside while the results were ranked, with the reason given.
|
|
25
|
+
|
|
26
|
+
## 0.4.1
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- Fixed: On a crowded register search, the step that writes the findings no longer runs out of time and stops the search without a report.
|
|
31
|
+
- Fixed: A connection library is updated to close a reported weakness in how sign-in details could be sent.
|
|
32
|
+
- Fixed: A clearance started from a connected assistant under the Generic default now appears on Clearances when you choose Generic. Before, it showed only under All companies.
|
|
33
|
+
- Fixed: A crowded clearance on the Codex engine no longer stops before its report.
|
|
34
|
+
- Fixed: A register count or record that could not be fetched no longer shows the program's error message to a client. This covers the knockout workbook's count notes, the knockout report and a clearance finding card. Each shows the line the report already uses for something left open.
|
|
35
|
+
- Fixed: A knockout's completion message and email now give its rating, or the highest rating when several names were screened. They no longer use the term 'worst band'.
|
|
36
|
+
- Fixed: A client whose mark is written in another script, such as Greek, Cyrillic or Chinese, now receives the link to the report. Before, the email arrived without one.
|
|
37
|
+
- Fixed: In rare cases, a report repaired just before delivery listed one conflict as two findings. Its last finding also lost its full write-up. The conflict now stays one finding, and every finding keeps its write-up.
|
|
38
|
+
- Fixed: A clearance no longer stops before its report when findings link to an office's own page instead of the register record.
|
|
39
|
+
- Fixed: A report repaired just before delivery keeps its coverage notes, such as a script that was not searched, in the report and the audit workbook.
|
|
40
|
+
- Fixed: A report's conditions no longer include an internal note about the search's own checking steps.
|
|
41
|
+
- Fixed: A clearance no longer reports a search as missing, or lowers a clear result, when the same search had already run under another entry.
|
|
42
|
+
- Fixed: A search too broad for the register to count is no longer reported as one that could not be run. The report shows it as too large to read, with the register's own figure.
|
|
43
|
+
- Fixed: Reports no longer say a search "did not finish and must be re-run" when a name's non-Latin form was in fact searched. They no longer make a conclusion depend on reviewing records the search had already read in full. Knockout reports now list every territory you ordered.
|
|
44
|
+
- Fixed: The audit workbook no longer warns that the depth rules were applied to nothing on a clearance whose narrative is written in sections. Those rules govern per-finding write-ups, and such a narrative has none.
|
|
45
|
+
- For operators: A stopped run no longer leaves behind a working folder linked to the Codex sign-in; leftovers are removed the next time Codex runs.
|
|
46
|
+
- Fixed: An order for the European Union now also searches each member state's own trade mark register, at no extra search cost. A national registration — in France, for example — is now found by an EU search.
|
|
47
|
+
- Before you upgrade: if you installed Codex yourself, update it to 0.159.0 or newer first. Older copies cannot use GPT-6.1 Sol, and searches on the OpenAI engine would stop at their first step.
|
|
48
|
+
- Fixed: On Signa, a crowded search over several spellings now counts each spelling alone, so a rare spelling is no longer reported as crowded.
|
|
49
|
+
- Fixed: Every clearance uses the same web search whichever engine runs it; the engines' own web search is switched off.
|
|
50
|
+
- Fixed: Every live identical or near-identical mark a register search finds is now placed in the report or set aside with its reason.
|
|
51
|
+
- New: `npx clearotron mcp` starts Clearotron's MCP server for an assistant; with no install it answers from the demo's sample runs, and says so.
|
|
52
|
+
- New: Searches now use the latest models: Claude Haiku 5.5, Sonnet 5.5 and Opus 5.5, and OpenAI's GPT-6.1 Sol and GPT-6 Luna. Installing or updating Clearotron brings the Claude and Codex programs up to versions that can use them.
|
|
53
|
+
- Fixed: The account connector's decision timeline no longer lists the engine's internal bookkeeping events.
|
|
54
|
+
- Fixed: The address `clearotron start` and `clearotron demo` print for a client's assistant now answers as soon as it is printed.
|
|
55
|
+
- Fixed: The account connector now lists a knockout search's review step, and no longer says the search makes two research calls per name.
|
|
56
|
+
- Fixed: The audit workbook's coverage summary names each area in words, not the engine's identifiers.
|
|
57
|
+
- Fixed: Two different names written in Greek, Cyrillic or Chinese characters, or differing only in an accented letter, can now be screened in one knockout. Before, they were refused as duplicates.
|
|
58
|
+
- Fixed: Findings rated medium risk or higher are now checked for the owner's actual use in the same way on every company's risk ladder. That same search looks for the owner's record of oppositions and disputes, so "Likely to enforce" carries a rating instead of "not checked".
|
|
59
|
+
|
|
3
60
|
## 0.4.1-beta.6
|
|
4
61
|
|
|
5
62
|
### Patch Changes
|
package/driver/ask-ledger.mjs
CHANGED
|
@@ -63,10 +63,16 @@ import { squash } from "./doubt-ledger.mjs";
|
|
|
63
63
|
// qids: [ ... ], // the plan qids that would answer it (may be [])
|
|
64
64
|
// ending: { kind, by, evidence, reasons, ts } | null,
|
|
65
65
|
// handoff: "<the surface a human reads an un-ended/handed-over ask on>" | null,
|
|
66
|
+
// part, family, // only on a request kept as parts: "ran" or "family", and that family's qid
|
|
67
|
+
// short_id, // every part of a frame request: the request's short id from before its hash
|
|
68
|
+
// legacy_id, // the one part an old closure of that short id may settle: the ran part
|
|
66
69
|
// }
|
|
67
70
|
|
|
68
71
|
const clip = (s, n = 200) => { const t = String(s ?? "").replace(/\s+/g, " ").trim(); return t.length > n ? `${t.slice(0, n - 1)}…` : t; };
|
|
69
72
|
const slug = (s) => String(s ?? "").toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "x";
|
|
73
|
+
// A short, stable hash of an exact string (FNV-1a, 32 bits), beside a slug, so two qids the slug folds or cuts to
|
|
74
|
+
// one spelling still give two ids. PURE: no import, no clock.
|
|
75
|
+
const fnv32 = (s) => { let h = 0x811c9dc5; for (const c of String(s ?? "")) { h ^= c.codePointAt(0); h = Math.imul(h, 0x01000193) >>> 0; } return h.toString(16).padStart(8, "0"); };
|
|
70
76
|
|
|
71
77
|
export const ASK_ENDINGS = ["executed", "judged-immaterial", "recovery"];
|
|
72
78
|
|
|
@@ -170,6 +176,21 @@ function endingForQids(qids, join, ts) {
|
|
|
170
176
|
return null;
|
|
171
177
|
}
|
|
172
178
|
|
|
179
|
+
/** The ending for a frame directive whose every slice repeated a family the reading turn withheld: that
|
|
180
|
+
* judgment, in the turn's own recorded words, and nothing else — never `executed`, since nothing ran. A
|
|
181
|
+
* family whose record cannot be read leaves the ask OPEN: there is no judgment to show, and an ending
|
|
182
|
+
* without one would be invented. `withheld` is the record as readWithheldFamilies returns it. PURE. */
|
|
183
|
+
function withheldEnding(families, withheld, ts) {
|
|
184
|
+
const reasons = families.map((f) => String(withheld?.[f]?.reason ?? "").trim());
|
|
185
|
+
if (!families.length || reasons.some((r) => !r)) return null;
|
|
186
|
+
const axes = [...new Set(families.map((f) => String(withheld?.[f]?.axis ?? "").trim()).filter(Boolean))];
|
|
187
|
+
return mkEnding("judged-immaterial", "reading-turn", {
|
|
188
|
+
reasons: [...new Set(reasons)],
|
|
189
|
+
evidence: axes.length ? axes.map((a) => `_driver/withheld-families-${a}.json`).join(", ") : "_driver/withheld-families",
|
|
190
|
+
ts,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
173
194
|
/** The {ending, handoff} pair for one cross-check directive. Only `recall` is subject to the
|
|
174
195
|
* discharge rule; every other net keeps the ending it always had. PURE. */
|
|
175
196
|
function recallEnding(name, qid, join, ts) {
|
|
@@ -195,7 +216,8 @@ function recallEnding(name, qid, join, ts) {
|
|
|
195
216
|
* 3. escalation-skip run.jsonl `escalation-skipped` events
|
|
196
217
|
* 4. envelope run.jsonl `envelope-decision` / `envelope-closed` events (+ state.failed)
|
|
197
218
|
* 5. screen-gate run.jsonl `screen-gate-violation` events + the unresolved sidecar
|
|
198
|
-
* 6. frame-diff _driver/frame-reopen.json (requested/swept/deferrals + directive_qids)
|
|
219
|
+
* 6. frame-diff _driver/frame-reopen.json (requested/swept/deferrals + directive_qids), and the
|
|
220
|
+
* reading turn's withheld-family record for a slice its judgment settled
|
|
199
221
|
* 7. form-neighbourhood same receipt, rows the receipt's `born` map marks form-oracle-injected
|
|
200
222
|
* 8. supplemental-proposal register-units/<axis>-supplemental-plan.json entries[] + rejected[]
|
|
201
223
|
* 9. cross-check _driver/register-xcheck.json + _driver/register-recall.json (+ overflow)
|
|
@@ -207,7 +229,7 @@ export function deriveAsks({
|
|
|
207
229
|
escalationState = null, events = null,
|
|
208
230
|
frameReopen = null, screenGateUnresolved = null,
|
|
209
231
|
supplementalPlans = null, xcheck = null, recall = null,
|
|
210
|
-
planExecution = null,
|
|
232
|
+
planExecution = null, withheldFamilies = null,
|
|
211
233
|
} = {}, { ts = null } = {}) {
|
|
212
234
|
const join = planJoinFrom(planExecution);
|
|
213
235
|
const evs = Array.isArray(events) ? events.filter((e) => e && typeof e === "object") : [];
|
|
@@ -346,25 +368,56 @@ export function deriveAsks({
|
|
|
346
368
|
const twinOf = new Map((Array.isArray(fr.answered_elsewhere) ? fr.answered_elsewhere : [])
|
|
347
369
|
.filter((x) => typeof x?.directive === "string")
|
|
348
370
|
.map((x) => [x.directive, new Map((Array.isArray(x.slices) ? x.slices : []).filter((t) => t?.qid && t?.twin).map((t) => [t.qid, t.twin]))]));
|
|
371
|
+
// A slice refused as a family the reading turn withheld never ran either, and nothing answered it: the turn
|
|
372
|
+
// decided not to ask that family, and its record keeps why. The receipt names such slices
|
|
373
|
+
// (`withheld_by_judgment`). They are left out of the computed ending, which rests on the slices that ran,
|
|
374
|
+
// and a directive with no other slice ends on the turn's recorded judgment.
|
|
375
|
+
//
|
|
376
|
+
// EACH SLICE ENDS ON WHAT HAPPENED TO IT. A slice that ran ends on the execution record, through its twin
|
|
377
|
+
// where the fold refused it as already asked. A slice whose family the turn still withholds ends on the
|
|
378
|
+
// turn's recorded reason. A slice whose family the turn released after the fold is no longer withheld on
|
|
379
|
+
// record, so it ends on what the execution record says of the family itself: it ran, it was deferred, or it
|
|
380
|
+
// stays open; so does a slice whose record cannot be read, which has no judgment to end on.
|
|
381
|
+
//
|
|
382
|
+
// ONE RECORD PER PART, AND A PART KEEPS ITS ID. The slices that ran are one part, under the directive's own
|
|
383
|
+
// id. Each family the settled slices repeat is a part of its own, under an id made from that family, so its
|
|
384
|
+
// id never moves when the family's disposition changes (withheld, then released and run) or a sibling part
|
|
385
|
+
// disappears. A closure names one part, and settles only that part; an id no part carries settles nothing.
|
|
386
|
+
const withheldOf = new Map((Array.isArray(fr.withheld_by_judgment) ? fr.withheld_by_judgment : [])
|
|
387
|
+
.filter((x) => typeof x?.directive === "string")
|
|
388
|
+
.map((x) => [x.directive, new Map((Array.isArray(x.slices) ? x.slices : []).filter((t) => t?.qid && t?.family).map((t) => [t.qid, t.family]))]));
|
|
349
389
|
for (const key of requested) {
|
|
350
390
|
const qids = (Array.isArray(qidsByKey[key]) ? qidsByKey[key] : []).filter(Boolean);
|
|
351
391
|
const place = bornByKey[key] === "form-neighbourhood" ? "form-neighbourhood" : "frame-diff";
|
|
352
|
-
|
|
392
|
+
const born = { place, artifact: "_driver/frame-reopen.json", ref: key, ts: fr.ts ?? null };
|
|
393
|
+
const ask = { text: `${place === "form-neighbourhood" ? "mechanical form-oracle gap" : "blind frame-diff omission"}: ${key} — sweep it or say why not`, owner: "register", structured: null };
|
|
394
|
+
// THE ID CARRIES A HASH OF THE WHOLE KEY. The slug cuts at 48 characters and folds punctuation, so two
|
|
395
|
+
// requests that differ only after the cut, or only in punctuation, gave one id, and the second was dropped.
|
|
396
|
+
// The short id stays on the ran part as `legacy_id`, for a closure written before the hash (applyAskClosure).
|
|
397
|
+
const id = `ask:frame:${slug(key)}-${fnv32(key)}`;
|
|
398
|
+
const legacy = `ask:frame:${slug(key)}`;
|
|
399
|
+
let parts = [{ id, qids, ending: null, handoff: null, legacy }];
|
|
353
400
|
const d = deferrals.get(key);
|
|
354
401
|
if (d) {
|
|
355
|
-
|
|
356
|
-
|
|
402
|
+
parts = [{ id, qids, ending: mkEnding("recovery", "frame-reopen", { reasons: [String(d.reason ?? "deferred")], evidence: "_driver/frame-reopen.json deferrals", ts }),
|
|
403
|
+
handoff: "open coverage row (frame-gap) + the CLEAR→CONDITIONAL clamp — the report states the unswept omission", legacy }];
|
|
404
|
+
} else if (swept.has(key) && !qids.length) {
|
|
405
|
+
parts = [{ id, qids, ending: mkEnding("executed", "frame-reopen-receipt", { evidence: "swept per the frame-reopen receipt (warm-resume arm — axis-level verification)", ts }), legacy }];
|
|
357
406
|
} else if (swept.has(key)) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
407
|
+
const settled = withheldOf.get(key) ?? new Map();
|
|
408
|
+
const ran = qids.filter((q) => !settled.has(q));
|
|
409
|
+
const families = [...new Set(qids.filter((q) => settled.has(q)).map((q) => settled.get(q)))];
|
|
410
|
+
parts = [
|
|
411
|
+
ran.length && { id, qids: ran, ending: endingForQids(ran.map((q) => twinOf.get(key)?.get(q) ?? q), join, ts), // null (open) when the execution record disagrees
|
|
412
|
+
...(families.length ? { part: "ran" } : {}), legacy },
|
|
413
|
+
...families.map((fam) => ({
|
|
414
|
+
id: `${id}:family:${slug(fam)}-${fnv32(fam)}`, qids: qids.filter((q) => settled.get(q) === fam), part: "family", family: fam,
|
|
415
|
+
ending: String(withheldFamilies?.[fam]?.reason ?? "").trim() ? withheldEnding([fam], withheldFamilies, ts) : endingForQids([fam], join, ts),
|
|
416
|
+
})),
|
|
417
|
+
].filter(Boolean);
|
|
361
418
|
}
|
|
362
|
-
push({
|
|
363
|
-
|
|
364
|
-
born: { place, artifact: "_driver/frame-reopen.json", ref: key, ts: fr.ts ?? null },
|
|
365
|
-
ask: { text: `${place === "form-neighbourhood" ? "mechanical form-oracle gap" : "blind frame-diff omission"}: ${key} — sweep it or say why not`, owner: "register", structured: null },
|
|
366
|
-
qids, ending, handoff,
|
|
367
|
-
});
|
|
419
|
+
for (const p of parts) push({ ask_id: p.id, born, ask, qids: p.qids, ending: p.ending, handoff: p.handoff ?? null,
|
|
420
|
+
...(p.part ? { part: p.part } : {}), ...(p.family ? { family: p.family } : {}), short_id: legacy, ...(p.legacy ? { legacy_id: p.legacy } : {}) });
|
|
368
421
|
}
|
|
369
422
|
}
|
|
370
423
|
|
|
@@ -651,12 +704,30 @@ export function applyAskClosure(asks, closureLines, fileTexts = {}, { ts = null
|
|
|
651
704
|
const byId = new Map();
|
|
652
705
|
for (const l of closureLines ?? []) if (l?.id && !byId.has(l.id)) byId.set(l.id, l);
|
|
653
706
|
const unverified = [];
|
|
707
|
+
// A CLOSURE WRITTEN BEFORE FRAME IDS CARRIED THEIR HASH names a request by its short id. Every request that had that
|
|
708
|
+
// short id could be the one it meant, a request now kept only as family parts included. It settles the ran part of
|
|
709
|
+
// the one request that had it, and nothing where two did: settling either could end an obligation it never judged.
|
|
710
|
+
// An ambiguous citation is reported with the parts it could have meant, while any of them is still open.
|
|
711
|
+
const requestsOf = new Map(), partsOf = new Map();
|
|
712
|
+
for (const a of asks ?? []) {
|
|
713
|
+
const short = a?.short_id ?? a?.legacy_id;
|
|
714
|
+
if (!short || short === a.ask_id) continue;
|
|
715
|
+
requestsOf.set(short, new Set([...(requestsOf.get(short) ?? []), a.born?.ref ?? a.ask_id]));
|
|
716
|
+
partsOf.set(short, [...(partsOf.get(short) ?? []), a]);
|
|
717
|
+
}
|
|
718
|
+
for (const [short, requests] of requestsOf) {
|
|
719
|
+
const l = byId.get(short);
|
|
720
|
+
const parts = partsOf.get(short) ?? [];
|
|
721
|
+
if (l && requests.size > 1 && parts.some((a) => !a.ending)) unverified.push({ ask_id: short, file: l.file, quote: l.quote, ambiguous: parts.map((a) => a.ask_id) });
|
|
722
|
+
}
|
|
723
|
+
const lineFor = (a) => byId.get(a.ask_id)
|
|
724
|
+
?? (a.legacy_id && requestsOf.get(a.legacy_id)?.size === 1 ? byId.get(a.legacy_id) : undefined);
|
|
654
725
|
const carryIntoFindings = [];
|
|
655
726
|
let immaterialByStage = 0;
|
|
656
727
|
const delivered = deliveredMarks(fileTexts?.["findings.json"]);
|
|
657
728
|
const out = (asks ?? []).map((a) => {
|
|
658
729
|
if (a?.ending) return a; // the stage may never touch an ended ask
|
|
659
|
-
const l =
|
|
730
|
+
const l = lineFor(a);
|
|
660
731
|
if (!l) return a;
|
|
661
732
|
if (l.verdict === "OPEN") return { ...a, handoff: clip(l.reason, 300) || a.handoff };
|
|
662
733
|
const hay = squash(fileTexts?.[l.file]);
|
package/driver/band-shape.mjs
CHANGED
|
@@ -708,7 +708,7 @@ function bindingOffices(territories, code) {
|
|
|
708
708
|
return out;
|
|
709
709
|
}
|
|
710
710
|
|
|
711
|
-
export function bandGroups(records, { classify, inScope, goods = null, dominantTerm = "", keysOf = null, markets = [], territories = [] } = {}) {
|
|
711
|
+
export function bandGroups(records, { classify, inScope, goods = null, dominantTerm = "", keysOf = null, markets = [], territories = [], evidenceOf = null } = {}) {
|
|
712
712
|
const fold = (x) => foldDiacritics(String(x ?? "")).toLowerCase();
|
|
713
713
|
const dom = fold(dominantTerm).trim();
|
|
714
714
|
const domRe = dom ? new RegExp(`(^|[^\\p{L}\\p{N}])${dom.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}($|[^\\p{L}\\p{N}])`, "u") : null;
|
|
@@ -744,7 +744,9 @@ export function bandGroups(records, { classify, inScope, goods = null, dominantT
|
|
|
744
744
|
// A floor is never counted in the rest (ruled 2026-10-12): held only outside the order's territories, it
|
|
745
745
|
// is still listed, in (a) or (b), where the step reads it and may set it aside by name on its territory.
|
|
746
746
|
const group = outside && !floor ? "rest" : recordGroup({ tier, live, inScope: s, hits, dominant });
|
|
747
|
-
|
|
747
|
+
// "On file" is a body `band_record` can open: goods read off the band row decide the group, never that.
|
|
748
|
+
const onFile = goods?.held ? goods.held(r) : Boolean(g);
|
|
749
|
+
const rec = { r, tier, hits, words, live, inScope: s, group, outside, floor, onFile, territories: ts, inMarket: ts.some((t) => market.has(t)) };
|
|
748
750
|
const keys = typeof keysOf === "function" ? keysOf(r) : null;
|
|
749
751
|
const ownerKey = keys?.owner ?? (normKey(r?.owner_name) ? `owner:${normKey(r?.owner_name)}` : `record:${String(r?.record_id ?? "").trim().toLowerCase()}`);
|
|
750
752
|
const key = keys?.line ?? `line:${ownerKey}|${normMark(r?.mark_text) || String(r?.record_id ?? "").trim().toLowerCase()}`;
|
|
@@ -778,6 +780,13 @@ export function bandGroups(records, { classify, inScope, goods = null, dominantT
|
|
|
778
780
|
// A line the goods test could not place shows a record whose goods are on file where it has one, so
|
|
779
781
|
// `band_record` opens the record the line names; `goods_on_file` says so of that record.
|
|
780
782
|
record_id: (known ? best : (inGroup.find((x) => x.onFile) ?? best)).r?.record_id ?? null,
|
|
783
|
+
// What the goods of the record the line names rest on (goods-evidence.mjs), where the run recorded it, and, where
|
|
784
|
+
// their completeness is not established, whether any goods text is held at all (`goods_text: false` where none is,
|
|
785
|
+
// as for a held record with no goods field).
|
|
786
|
+
...(typeof evidenceOf === "function" ? (() => {
|
|
787
|
+
const ev = evidenceOf((known ? best : (inGroup.find((x) => x.onFile) ?? best)).r);
|
|
788
|
+
return { goods_evidence: ev?.state ?? null, ...(ev?.state === "unknown" && ev?.readable == null ? { goods_text: false } : {}) };
|
|
789
|
+
})() : {}),
|
|
781
790
|
floors: l.recs.filter((x) => x.floor).length,
|
|
782
791
|
...(known ? {} : { goods_on_file: inGroup.some((x) => x.onFile) }),
|
|
783
792
|
...(offices.size && l.recs.every((x) => x.outside) ? { outside: true } : {}),
|
|
@@ -821,7 +830,7 @@ export function bandGroups(records, { classify, inScope, goods = null, dominantT
|
|
|
821
830
|
* (charter P2d); the band itself stays per-registration
|
|
822
831
|
* @returns { shape, md } deterministic — same band + inputs ⇒ same bytes (no timestamps)
|
|
823
832
|
*/
|
|
824
|
-
export function buildBandShape(band, { targets = [], inScopeClasses = [], crowdContext = null, caps = {}, positions = null, goods = null, goodsTest = null, goodsWordCount = 0, dominantTerm = "", manifestDominant = "", keysOf = null, markets = [], territories = [] } = {}) {
|
|
833
|
+
export function buildBandShape(band, { targets = [], inScopeClasses = [], crowdContext = null, caps = {}, positions = null, goods = null, goodsTest = null, goodsWordCount = 0, dominantTerm = "", manifestDominant = "", keysOf = null, markets = [], territories = [], evidenceOf = null } = {}) {
|
|
825
834
|
const prepared = prepareTargets(Array.isArray(targets) && targets.every(isPrepared) ? targets.map((t) => t.text) : targets);
|
|
826
835
|
const records = Array.isArray(band?.enumerated) ? band.enumerated : [];
|
|
827
836
|
const crowds = Array.isArray(band?.crowds) ? band.crowds : [];
|
|
@@ -879,7 +888,7 @@ export function buildBandShape(band, { targets = [], inScopeClasses = [], crowdC
|
|
|
879
888
|
const floorsOrdered = probeOrder(floors, "floors");
|
|
880
889
|
|
|
881
890
|
const ownerCap = Number.isFinite(caps.owners) ? caps.owners : 40;
|
|
882
|
-
const grouped = bandGroups(records, { classify: (r) => classified.get(r), inScope, goods, dominantTerm, keysOf, markets, territories });
|
|
891
|
+
const grouped = bandGroups(records, { classify: (r) => classified.get(r), inScope, goods, dominantTerm, keysOf, markets, territories, evidenceOf });
|
|
883
892
|
const ownersIn = (ls) => new Set(ls.map((l) => l.owner_key)).size;
|
|
884
893
|
const groupOf = (g) => {
|
|
885
894
|
const ls = grouped.by_group[g].lines;
|
|
@@ -1084,12 +1093,41 @@ export const groupPageCall = (g) => `band_lookup {"group":"${g}"}`;
|
|
|
1084
1093
|
|
|
1085
1094
|
// PLACEMENT'S ORDERS IN THE SHAPE. Three sentences under the groups tell the reader to give every line a fate,
|
|
1086
1095
|
// and only placement owes the groups that. The band server serves the shape without them to every other
|
|
1087
|
-
// stage (
|
|
1096
|
+
// stage (the owner's ruling, 2026-10-14), so they are declared once here and removed there by these same strings.
|
|
1088
1097
|
export const SORT_THE_NEAR_ONES = "A few hundred? Sort by the client's goods and markets, read the near ones first, list the rest.";
|
|
1089
1098
|
export const ONE_SET_ASIDE_ROW = "Write down what you set aside and why: one row, with this count.";
|
|
1090
1099
|
export const A_FATE_BY_NAME = "Each line takes its own fate by name; no group row covers these lines.";
|
|
1091
1100
|
export const PLACEMENT_GROUP_ORDERS = Object.freeze([SORT_THE_NEAR_ONES, ONE_SET_ASIDE_ROW, A_FATE_BY_NAME]);
|
|
1092
1101
|
|
|
1102
|
+
// AND THE FATES THE GROUPS' SIZE SENTENCES ORDER (ruled 2026-10-18). Five more of placement's orders sit inside the
|
|
1103
|
+
// groups' size sentences, and the shape's commands to page a group name a lookup only placement is offered. Every
|
|
1104
|
+
// other stage is served the shape without them, removed by these same strings (`withoutPlacementOrders`); each
|
|
1105
|
+
// group's identity, members and count stay, and so does the fact that a crowded group prints only its first page.
|
|
1106
|
+
export const FLOORS_BY_NAME_HERE = "Every floor here is placed or set aside by name; this group's row covers only its other lines.";
|
|
1107
|
+
export const A_CROWDS_ONE_ROW = "Read the first page, carry forward any line a lawyer would raise, and set the rest aside in this group's one row with the count: the client is told the field is crowded.";
|
|
1108
|
+
export const AN_UNREAD_CROWDS_ONE_ROW = "Carry forward any line a lawyer would raise, and set the rest aside in this list's one row with the count, on the ground that their goods are unreadable and the field is crowded.";
|
|
1109
|
+
export const AN_UNREAD_CROWDS_FLOOR = "A floor here is placed or set aside by name on its status or its territory; this row never covers it.";
|
|
1110
|
+
export const AN_UNREAD_LISTS_FLOOR = "A floor here is placed, or set aside by name on its status or its territory; any other line may be set aside on the ground that its goods are unknown.";
|
|
1111
|
+
// What placement does with a line of that list before its fate, by its marker (approved wording, 2026-10-19). Placement's
|
|
1112
|
+
// alone, as its other orders are: every other stage keeps the markers as evidence and is not told of a fate.
|
|
1113
|
+
export const AN_UNREAD_LINE_IS_CHECKED = 'A line marked "on file, not readable", "cut by the register, no goods word found", "goods held; completeness not established" or "no goods text held; completeness not established" must be checked with band_record before its fate. Read the evidence held for it, keeping its source and goods completeness clear. A line marked "no goods on file", "fetch failed, no goods on file" or "the register\'s record lists no goods" has no goods text on file. Missing goods do not establish that the goods are unrelated.';
|
|
1114
|
+
export const PLACEMENT_LINE_ORDERS = Object.freeze([FLOORS_BY_NAME_HERE, A_CROWDS_ONE_ROW, AN_UNREAD_CROWDS_ONE_ROW, AN_UNREAD_CROWDS_FLOOR, AN_UNREAD_LISTS_FLOOR, AN_UNREAD_LINE_IS_CHECKED]);
|
|
1115
|
+
const SHAPE_LISTS = ["a", "b", "c", "d", "rest", "no_goods"];
|
|
1116
|
+
const pageEveryOtherLine = (g) => `; page every other line with ${groupPageCall(g)}`;
|
|
1117
|
+
const pageWhereALineNeedsIt = (g) => `; page with ${groupPageCall(g)} only where a line needs reading`;
|
|
1118
|
+
const pageTheFloorsLeft = (g) => `Where more floors are counted than this page shows, page the rest with ${groupPageCall(g)}; they come first.`;
|
|
1119
|
+
/**
|
|
1120
|
+
* The shape as every stage but placement is served it: placement's orders and its group-page commands removed,
|
|
1121
|
+
* each with the space before it, inside its line, so no line moves and the line each listed mark is printed on
|
|
1122
|
+
* still holds. A crowded group's "The first N lines are below" stays, as a fact about what is printed. PURE.
|
|
1123
|
+
*/
|
|
1124
|
+
export function withoutPlacementOrders(text) {
|
|
1125
|
+
let t = String(text ?? "");
|
|
1126
|
+
for (const order of [...PLACEMENT_GROUP_ORDERS, ...PLACEMENT_LINE_ORDERS, ...SHAPE_LISTS.map(pageTheFloorsLeft)]) t = t.split(` ${order}`).join("").split(order).join("");
|
|
1127
|
+
for (const g of SHAPE_LISTS) t = t.split(pageEveryOtherLine(g)).join("").split(pageWhereALineNeedsIt(g)).join("");
|
|
1128
|
+
return t;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1093
1131
|
function renderGroupsMd(md, groups, rows = []) {
|
|
1094
1132
|
md.push("", "## The band in the lawyer's order — for the client's goods first", "");
|
|
1095
1133
|
if (groups.dominant_term && groups.manifest_dominant && normKey(groups.dominant_term) !== normKey(groups.manifest_dominant))
|
|
@@ -1111,7 +1149,19 @@ function renderGroupsMd(md, groups, rows = []) {
|
|
|
1111
1149
|
const word = groups.dominant_term || groups.manifest_dominant;
|
|
1112
1150
|
const carrying = word ? `carrying «${cell(word)}»` : "carrying the searched word";
|
|
1113
1151
|
const heading = (g) => (g === "c" ? `(c) The same family as the mark, or ${carrying}, for the client's goods` : GROUP_HEADINGS[g]);
|
|
1114
|
-
|
|
1152
|
+
// A line the goods test could not place says why, from what its goods rest on: cut by the register, held but not
|
|
1153
|
+
// established whole (with goods text, or with none, as a held record with no goods field is), the register's
|
|
1154
|
+
// record listing none, a fetch that failed, or nothing held. Goods held whole that the test still could not read
|
|
1155
|
+
// are in a language it cannot use. A shape derived with no record of the goods evidence reads as it always did.
|
|
1156
|
+
const unread = (l) => {
|
|
1157
|
+
if (tested.source === "none") return "—";
|
|
1158
|
+
if (l.goods_evidence === "cut") return "cut by the register, no goods word found";
|
|
1159
|
+
if (l.goods_evidence === "unknown") return l.goods_text === false ? "no goods text held; completeness not established" : "goods held; completeness not established";
|
|
1160
|
+
if (l.goods_evidence === "absent") return "the register's record lists no goods";
|
|
1161
|
+
if (l.goods_evidence === "unavailable") return "fetch failed, no goods on file";
|
|
1162
|
+
if (l.goods_evidence === "complete") return "on file, not readable";
|
|
1163
|
+
return l.goods_on_file ? "on file, not readable" : "no goods on file";
|
|
1164
|
+
};
|
|
1115
1165
|
const words = (l) => (l.goods_words == null ? unread(l) : `${fmtN(l.goods_words.length)} of ${fmtN(total)}${l.goods_words.length ? `: ${l.goods_words.join(", ")}` : ""}`);
|
|
1116
1166
|
const count = (grp) => `${fmtN(grp.records)} records in ${fmtN(grp.lines)} lines from ${fmtN(grp.owners)} owners`;
|
|
1117
1167
|
const where = (l) => `${l.territories.join(",")}${l.outside ? " (outside the order)" : ""}`;
|
|
@@ -1129,10 +1179,10 @@ function renderGroupsMd(md, groups, rows = []) {
|
|
|
1129
1179
|
const readA = "These are the identical and near-identical marks live in the ordered classes for the client's goods: read every line.";
|
|
1130
1180
|
md.push(g === "a"
|
|
1131
1181
|
? (crowded
|
|
1132
|
-
? `${count(grp)}. ${readA} ${firstPage}
|
|
1182
|
+
? `${count(grp)}. ${readA} ${firstPage}${pageEveryOtherLine(g)}. ${FLOORS_BY_NAME_HERE}`
|
|
1133
1183
|
: grp.lines <= READ_ALL_LINES ? `${count(grp)}. Fifty? Read them all.` : `${count(grp)}. ${readA}`)
|
|
1134
1184
|
: crowded
|
|
1135
|
-
? `${count(grp)}. A thousand or more? The field is crowded. These are ${crowd[g]}.
|
|
1185
|
+
? `${count(grp)}. A thousand or more? The field is crowded. These are ${crowd[g]}. ${A_CROWDS_ONE_ROW} ${firstPage}${pageWhereALineNeedsIt(g)}.`
|
|
1136
1186
|
: grp.lines <= READ_ALL_LINES
|
|
1137
1187
|
? `${count(grp)}. Fifty? Read them all.`
|
|
1138
1188
|
: `${count(grp)}. ${SORT_THE_NEAR_ONES}`, "");
|
|
@@ -1147,7 +1197,7 @@ function renderGroupsMd(md, groups, rows = []) {
|
|
|
1147
1197
|
const restCrowded = rest.records >= groups.crowded_at;
|
|
1148
1198
|
md.push(`**The rest: ${count(rest)}${outside}** — every record in none of (a) to (d) above. ${!rest.lines ? ONE_SET_ASIDE_ROW
|
|
1149
1199
|
: restCrowded
|
|
1150
|
-
? `A thousand or more? The field is crowded.
|
|
1200
|
+
? `A thousand or more? The field is crowded. ${A_CROWDS_ONE_ROW} ${firstPage}${pageWhereALineNeedsIt("rest")}.`
|
|
1151
1201
|
: `${rest.lines <= READ_ALL_LINES ? "Fifty? Read them all." : SORT_THE_NEAR_ONES} ${ONE_SET_ASIDE_ROW}`}`);
|
|
1152
1202
|
if (rest.lines) {
|
|
1153
1203
|
md.push("", ...head);
|
|
@@ -1164,8 +1214,8 @@ function renderGroupsMd(md, groups, rows = []) {
|
|
|
1164
1214
|
? `**Not tested on goods: ${count(ng)}.** No goods words were given, so the goods test could not place them.`
|
|
1165
1215
|
: `**Goods the test could not read: ${count(ng)}.**`;
|
|
1166
1216
|
md.push("", crowded
|
|
1167
|
-
? `${lead} A thousand or more? ${firstPage}, the floors first, then the live ones in the ordered classes.
|
|
1168
|
-
: `${lead} ${ng.lines <= READ_ALL_LINES ? "Fifty? Read them all." : "A few hundred? Read the live ones in the ordered classes first."} ${A_FATE_BY_NAME}${tested.source === "none" ? "" :
|
|
1217
|
+
? `${lead} A thousand or more? ${firstPage}, the floors first, then the live ones in the ordered classes. ${AN_UNREAD_CROWDS_ONE_ROW} ${AN_UNREAD_CROWDS_FLOOR} ${pageTheFloorsLeft("no_goods")}`
|
|
1218
|
+
: `${lead} ${ng.lines <= READ_ALL_LINES ? "Fifty? Read them all." : "A few hundred? Read the live ones in the ordered classes first."} ${A_FATE_BY_NAME}${tested.source === "none" ? "" : ` ${AN_UNREAD_LINE_IS_CHECKED}`} ${AN_UNREAD_LISTS_FLOOR}`, "");
|
|
1169
1219
|
md.push(...head);
|
|
1170
1220
|
for (const l of crowded ? ng.list.slice(0, groups.first_page) : ng.list) { rows.push([md.length, l.key]); md.push(row(l)); }
|
|
1171
1221
|
}
|
package/driver/band-size.mjs
CHANGED
|
@@ -118,6 +118,16 @@ export function limitExceedsCeiling(sec) {
|
|
|
118
118
|
return Number.isFinite(sec) && sec > LIMIT_CEILING_SEC;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* The band-reading stages whose dispatch would refuse a band of `bytes`: each stage's own base, derived from the band's
|
|
123
|
+
* bytes as `stageOnce` derives it, against the same ceiling. `baseOf` gives a stage's base (the stage table's
|
|
124
|
+
* `timeoutSec`). A band that cannot be measured refuses nothing, as its dispatch returns the base. PURE.
|
|
125
|
+
*/
|
|
126
|
+
export function stagesRefusingBand(bytes, baseOf) {
|
|
127
|
+
if (!Number.isFinite(bytes)) return [];
|
|
128
|
+
return [...BAND_READING_STAGES].filter((s) => limitExceedsCeiling(derivedLimitSec(baseOf(s), { bytes }).sec));
|
|
129
|
+
}
|
|
130
|
+
|
|
121
131
|
/** The refusal a dispatch past the ceiling carries — it NAMES the size, which is the finding. PURE. */
|
|
122
132
|
export function ceilingRefusal(stage, derived) {
|
|
123
133
|
const mb = Number.isFinite(derived?.inputBytes) ? (derived.inputBytes / (1024 * 1024)).toFixed(1) : "an unmeasured";
|
package/driver/close-verify.mjs
CHANGED
|
@@ -71,6 +71,24 @@ export function answeredAs(qid, foldRefusals) {
|
|
|
71
71
|
return r && r.kind === "duplicate-question" && r.twin ? String(r.twin) : qid;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* The reading turn's judgment that settles a minted slice, or null. The frame reopen's re-sweep refuses a slice
|
|
76
|
+
* that repeats exactly a waiting family the reading turn WITHHELD (register-plan.mjs, refuseWithheldFamilies), and
|
|
77
|
+
* its twin is that family. The twin never ran, by the turn's decision, so nothing answers the slice: it is settled
|
|
78
|
+
* by the turn's recorded reason instead, and it is never read as a question that ran, was answered or came back
|
|
79
|
+
* clean. `answeredAs` above stays the duplicate rule alone, and resolves such a slice to nothing.
|
|
80
|
+
*
|
|
81
|
+
* ONLY THE WHOLE RECORD SETTLES: the `withheld-by-judgment` kind, the family it repeats, and the reason on the
|
|
82
|
+
* family's record. Anything less is a slice nobody judged, and it stays a slice that never ran. One rule for the
|
|
83
|
+
* directive below and the remedy term ledger, so the two cannot read a slice apart. PURE.
|
|
84
|
+
* @returns {{family: string, reason: string}|null}
|
|
85
|
+
*/
|
|
86
|
+
export function withheldAs(qid, foldRefusals) {
|
|
87
|
+
const r = foldRefusals?.get?.(qid);
|
|
88
|
+
const reason = String(r?.reason ?? "").trim();
|
|
89
|
+
return r && r.kind === "withheld-by-judgment" && r.twin && reason ? { family: String(r.twin), reason } : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
74
92
|
/**
|
|
75
93
|
* Verify a register frame-reopen directive was actually SEARCHED as intended (Part B, arm #1). A directive
|
|
76
94
|
* is closed ONLY if EVERY minted qid clears three checks:
|
|
@@ -85,7 +103,12 @@ export function answeredAs(qid, foldRefusals) {
|
|
|
85
103
|
*
|
|
86
104
|
* A slice the fold refused as already-asked is checked on the row that answered it (`answeredAs`), on the same
|
|
87
105
|
* three checks, and a directive closed that way names each such row in `answered_by`.
|
|
88
|
-
*
|
|
106
|
+
*
|
|
107
|
+
* A slice refused as a family the reading turn withheld (`withheldAs`) is not checked at all, because nothing ran
|
|
108
|
+
* for it: it is settled by that judgment, and a directive closed with one names it, and the family, in
|
|
109
|
+
* `withheld_by_judgment`, never in `answered_by`. It settles only itself. Every other slice must still clear the
|
|
110
|
+
* three checks, so a search that failed is never excused by a withheld sibling.
|
|
111
|
+
* @returns {{closed: boolean, reason: string|null, answered_by?: {qid: string, twin: string}[], withheld_by_judgment?: {qid: string, family: string}[]}}
|
|
89
112
|
*/
|
|
90
113
|
export function verifyRegisterDirectiveClose({ qids, intendedClasses = [], executedQids, blocksByQid, foldRefusals = new Map() } = {}) {
|
|
91
114
|
const list = qids ?? [];
|
|
@@ -94,7 +117,10 @@ export function verifyRegisterDirectiveClose({ qids, intendedClasses = [], execu
|
|
|
94
117
|
const executed = executedQids ?? new Set();
|
|
95
118
|
const byQid = blocksByQid ?? new Map();
|
|
96
119
|
const answeredBy = [];
|
|
120
|
+
const withheld = [];
|
|
97
121
|
for (const qid of list) {
|
|
122
|
+
const settled = withheldAs(qid, foldRefusals);
|
|
123
|
+
if (settled) { withheld.push({ qid, family: settled.family }); continue; }
|
|
98
124
|
const q = answeredAs(qid, foldRefusals);
|
|
99
125
|
const as = q === qid ? qid : `${qid} (answered by ${q})`;
|
|
100
126
|
if (!executed.has(q)) return { closed: false, reason: `slice-not-landed:${as}` };
|
|
@@ -106,5 +132,6 @@ export function verifyRegisterDirectiveClose({ qids, intendedClasses = [], execu
|
|
|
106
132
|
return { closed: false, reason: `wrong-scope:${as} searched[${searched.join(",")}]⊉intended[${want.join(",")}]` };
|
|
107
133
|
if (q !== qid) answeredBy.push({ qid, twin: q });
|
|
108
134
|
}
|
|
109
|
-
return { closed: true, reason: null, ...(answeredBy.length ? { answered_by: answeredBy } : {})
|
|
135
|
+
return { closed: true, reason: null, ...(answeredBy.length ? { answered_by: answeredBy } : {}),
|
|
136
|
+
...(withheld.length ? { withheld_by_judgment: withheld } : {}) };
|
|
110
137
|
}
|
|
@@ -116,7 +116,7 @@ export function recordCoverageStatus(spec, received, { now = () => new Date().to
|
|
|
116
116
|
export const NOT_STATED = "not stated";
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
|
-
* THE STATUSES A FINDINGS FILE STATES, RECORDED BY CODE (
|
|
119
|
+
* THE STATUSES A FINDINGS FILE STATES, RECORDED BY CODE (the owner, 2026-10-01). A seat whose findings carry
|
|
120
120
|
* a coverage ledger but made no `record_coverage_status` call is accepted, not re-run: a re-run for this
|
|
121
121
|
* was a full stage re-run, and nothing is added to what the model reads. Each ledger row's status is read
|
|
122
122
|
* from the row as the findings wrote it — one of the three words, else "not stated" — and recorded in the
|
|
@@ -624,14 +624,12 @@ export function quoteJoins(quote, candidates) {
|
|
|
624
624
|
const POINT_DIRECTIVE = [
|
|
625
625
|
"On every row the tool's answer marks as owing one, give `segment_index` — the NUMBER of the passage",
|
|
626
626
|
"you relied on, from the numbered passages of the receipt you ruled on.",
|
|
627
|
-
"
|
|
628
|
-
"is written in.",
|
|
629
|
-
"The number says WHICH passage; the characters show you read it. The driver copies the passage into the",
|
|
627
|
+
"The driver copies the passage into the",
|
|
630
628
|
"record for you, so never reproduce it, never translate it, and never join two passages together.",
|
|
631
|
-
"
|
|
632
|
-
"
|
|
633
|
-
"
|
|
634
|
-
"
|
|
629
|
+
"`fragment` is OPTIONAL, and never refused: the driver extracts the quote it stores from `segment_index`",
|
|
630
|
+
"ALONE, so pointing at the passage is the whole obligation. If a few characters of that passage come",
|
|
631
|
+
"easily, send them and they are recorded as a transcription check; if the script is one you cannot",
|
|
632
|
+
"reproduce, omit it and nothing is lost.",
|
|
635
633
|
];
|
|
636
634
|
|
|
637
635
|
// The anchor's own bar. SHORT ON PURPOSE — this is the piece a model must reproduce exactly, and every
|
|
@@ -1412,7 +1410,7 @@ export function renderConnotationObligations(ob, { ledgerPath = "your half ledge
|
|
|
1412
1410
|
"your `ruling` and your `note`.",
|
|
1413
1411
|
"Rows are validated as they arrive: rows that validate are KEPT even when others in the same call",
|
|
1414
1412
|
"are refused, and the tool's answer names every obligation still outstanding and which of them owe",
|
|
1415
|
-
"a `segment_index
|
|
1413
|
+
"a `segment_index`. Your rulings ACCUMULATE across calls and across attempts — nothing",
|
|
1416
1414
|
"already recorded",
|
|
1417
1415
|
"needs re-sending. Send the rest in a further call until the answer reports nothing outstanding.",
|
|
1418
1416
|
"",
|
|
@@ -1500,7 +1498,7 @@ export function meaningSweepReceiptsInstruction({
|
|
|
1500
1498
|
"A row listed with exactly ONE receipt is already resolved: it takes no",
|
|
1501
1499
|
"`receipt_index` — give that row only your `ruling` and `note`.",
|
|
1502
1500
|
"Rows that validate are KEPT even when others in the same call are refused, and the tool's answer",
|
|
1503
|
-
"names every obligation still outstanding and which of them owe a `segment_index
|
|
1501
|
+
"names every obligation still outstanding and which of them owe a `segment_index`.",
|
|
1504
1502
|
"Your rulings ACCUMULATE",
|
|
1505
1503
|
"across calls and across attempts: a row you have ruled on stays ruled, so nothing already answered",
|
|
1506
1504
|
"needs redoing — send only what the answer reports outstanding.",
|