clearotron 0.5.0-beta.2 → 0.5.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +7 -0
- package/build-info.json +2 -2
- package/docs/architecture/03-run-lifecycle.md +6 -0
- package/docs/architecture/04-configuration-reference.md +1 -0
- package/docs/architecture/05-config-governance.md +1 -1
- package/driver/CHANGELOG.md +21 -0
- package/driver/band-shape.mjs +19 -11
- package/driver/contract-vocabulary.mjs +3 -3
- package/driver/coverage-form-io.mjs +3 -1
- package/driver/coverage-form.mjs +21 -2
- package/driver/coverage-ledger.mjs +7 -1
- package/driver/declination-call.mjs +33 -16
- package/driver/degraded-parts.mjs +6 -0
- package/driver/engine/mcp/band-server.mjs +12 -12
- package/driver/engine/mcp/clarivate-server.mjs +2 -0
- package/driver/engine/mcp/codex-config.mjs +12 -7
- package/driver/engine/mcp/corsearch-server.mjs +2 -0
- package/driver/engine/mcp/declination-server.mjs +4 -5
- package/driver/engine/mcp/dispositions-server.mjs +7 -0
- package/driver/engine/mcp/euipo-server.mjs +2 -0
- package/driver/engine/mcp/free-tier-server.mjs +2 -0
- package/driver/engine/mcp/gather-config.mjs +15 -0
- package/driver/engine/mcp/held-answers.mjs +11 -3
- package/driver/engine/mcp/perplexity-server.mjs +29 -1
- package/driver/engine/mcp/recording-server.mjs +16 -1
- package/driver/engine/mcp/signa-server.mjs +2 -0
- package/driver/engine/mcp/supplemental.mjs +11 -3
- package/driver/engine/mcp/uspto-local-server.mjs +2 -0
- package/driver/engine/openai-agent.mjs +48 -9
- package/driver/envelope-settle.mjs +13 -3
- package/driver/findings-model.mjs +17 -0
- package/driver/gateway.mjs +31 -10
- package/driver/package.json +1 -1
- package/driver/parts-not-completed.mjs +52 -11
- package/driver/pipeline-knockout.mjs +5 -4
- package/driver/pipeline.mjs +474 -81
- package/driver/placement-decisions.mjs +24 -9
- package/driver/placement-form-io.mjs +21 -1
- package/driver/placement-form.mjs +25 -15
- package/driver/placement-union.mjs +53 -8
- package/driver/predelivery-lint.mjs +8 -1
- package/driver/publish/index.mjs +7 -3
- package/driver/register-digest-record.mjs +6 -3
- package/driver/research-answers.mjs +54 -7
- package/driver/runner.mjs +4 -4
- package/driver/saved-findings-block.mjs +36 -0
- package/driver/skills/clearance-register/digest.md +1 -1
- package/driver/skills/clearance-search/phase2-execution.md +7 -5
- package/driver/skills/clearance-search/synthesis-rules.md +3 -3
- package/driver/skills/placement-inquiry/SKILL.md +3 -3
- package/driver/stage-context.mjs +18 -0
- package/driver/stages.mjs +41 -13
- package/driver/suite-census.json +140 -14
- package/driver/synthesis-record.mjs +167 -1
- package/driver/verify.mjs +6 -6
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/_shared/README.md +1 -0
- package/providers/_shared/enumerate.mjs +4 -3
- package/providers/_shared/execute-plan.mjs +33 -7
- package/providers/_shared/send-until.mjs +25 -0
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/scripts/band-shape-probe.mjs +1 -1
- package/scripts/e2e-first-time.mjs +2 -1
- package/scripts/release-notes-lint.mjs +1 -1
- package/shared/inside-run.mjs +54 -0
- package/shared/names-in-force.mjs +1 -0
- package/shared/slices-taken-out.mjs +65 -0
- package/skills/clearotron-account/SKILL.md +1 -1
- package/skills/clearotron-ops/SKILL.md +1 -1
package/.env.example
CHANGED
|
@@ -184,6 +184,13 @@ CLEAROTRON_DATABASE=clarivate
|
|
|
184
184
|
# CLEAROTRON_GATHER_SESSION_KEY, CLEAROTRON_GATHER_SESSION_ID, CLEAROTRON_GATHER_AGENT,
|
|
185
185
|
# CLEAROTRON_REGISTER_CALL_LOG, CLEAROTRON_REGISTER_RECORD_LOG
|
|
186
186
|
#
|
|
187
|
+
# The time, in epoch milliseconds, at which a turn's register tools stop sending new questions, so the
|
|
188
|
+
# unit can write what it holds inside its own time. The driver writes it per turn into each register
|
|
189
|
+
# tool server's env, a quarter of the turn (at most five minutes) ahead of the turn's deadline. Leave it
|
|
190
|
+
# empty: a value set here would be read by the driver's own code-side executor as a fixed time.
|
|
191
|
+
# effect: tuning
|
|
192
|
+
CLEAROTRON_GATHER_SEND_UNTIL_MS=
|
|
193
|
+
#
|
|
187
194
|
# THERE IS NO RETENTION SETTING AND NO CLEANUP JOB. CLEAROTRON_REGISTER_LEDGER_MAX_BYTES,
|
|
188
195
|
# CLEAROTRON_REGISTER_LEDGER_KEEP and `npm run prune:ledger` are gone with the rotation tool, because the
|
|
189
196
|
# thing they bounded no longer grows: RECORD BODIES ARE WRITTEN INTO THE RUN'S OWN DIRECTORY
|
package/build-info.json
CHANGED
|
@@ -438,6 +438,7 @@ node pipeline.mjs --job <file.json> [--agent <id>] # cold run (still take
|
|
|
438
438
|
node pipeline.mjs --resume <codename> # idempotent resume: valid outputs skip
|
|
439
439
|
node pipeline.mjs --resume <codename> --from <stage> # force re-run from a stage ordinal
|
|
440
440
|
node pipeline.mjs --resume <codename> --experiment <stage> [--label <t>] # shadow dir
|
|
441
|
+
node pipeline.mjs --resume <codename> --experiment synthesis --research replay # research from the run's own answers
|
|
441
442
|
```
|
|
442
443
|
|
|
443
444
|
- **Resume** refuses a delivered run (any of the three delivered markers above — re-handing off a
|
|
@@ -448,6 +449,11 @@ node pipeline.mjs --resume <codename> --experiment <stage> [--label <t>]
|
|
|
448
449
|
- **`--experiment`** runs one stage in a shadow dir (`_experiments/<ts>-<tag>/`) on copies of its
|
|
449
450
|
inputs, under a `clearance-exp-…` session key that is excluded from the run's provider-usage
|
|
450
451
|
attribution. The canonical run is untouched.
|
|
452
|
+
- **`--research replay`** answers the arm's research from the answers the run saved
|
|
453
|
+
(`_driver/research-answers.jsonl`, copied into the shadow dir). A question the run asked gets the
|
|
454
|
+
run's latest answer to it; a question it did not ask, and any grid, is refused, and nothing is sent
|
|
455
|
+
to the research service. It is refused for a stage that holds no research tool, or a run that saved
|
|
456
|
+
no answers. The arm's `experiment-context.json` records `research: {mode, answers}`.
|
|
451
457
|
- **Orphan self-resume**: a manually resumed run that parks has no queue sidecars; the runner scans
|
|
452
458
|
run dirs for due, payload-complete `.postponed` sentinels not owned by any queue and resumes them
|
|
453
459
|
(claim = atomic rename to `.resuming`).
|
|
@@ -373,6 +373,7 @@ cannot be read as one list.
|
|
|
373
373
|
| `CLEAROTRON_DUMP_JSON` | unset | Dump each attempt's raw engine envelope to `_driver/<stage>.attempt<N>.rawjson.json`. Opt-in: any value except `0`/`off`/`false`/`no`/empty arms it. |
|
|
374
374
|
| `CLEAROTRON_DISPATCH_RECORD` | **on** | Write the verbatim message of every stage dispatch to `_driver/<stage>.attempt<N>[.repair<M>].dispatch.txt`, with `{file, sha, bytes, chars, kind}` on the attempt row. **Default ON** — `0`/`off`/`false`/`no` disarms it. Unlike `CLEAROTRON_DUMP_JSON` beside it, this is opt-OUT: the question it answers ("was the model given this?") is asked *after* the run that raised it, so a flag someone had to remember would be off on exactly the run that needed it. The files carry the company's identity verbatim and are deliberately not in the artifact table. |
|
|
375
375
|
| `CLEAROTRON_GATHER_SESSION_KEY` / `CLEAROTRON_GATHER_AGENT` / `CLEAROTRON_GATHER_SESSION_ID` | set per stage | Telemetry attribution into the provider-call ledger (set by the gather config; not operator-set). |
|
|
376
|
+
| `CLEAROTRON_GATHER_SEND_UNTIL_MS` | set per turn | When a turn's register tools stop sending new questions: a quarter of the turn, at most five minutes, ahead of its deadline (set by the gather config; not operator-set). |
|
|
376
377
|
| `CLEAROTRON_RECORD_AXIS` | set per dispatch (unset ⇒ the stage is not fanned out) | Binds one fan-out turn of a recording stage to the single member it may write. `stageOnce` suffixes a fan-out stage's label with its axis, the gather config resolves `<stage>:<axis>` back to the base stage's tool group, and this carries the axis to the recording server. A call whose payload names a different member than the turn is bound to is REFUSED, so a seat cannot write into a sibling's file — without the binding every turn of the fan-out would record over member one. Set by the driver; not operator-set. |
|
|
377
378
|
| `PORTAL_READ_MODEL` | `sonnet` | The model the portal's own compose-read turn uses. Distinct from the pipeline's tiers: this is a portal surface, not a stage. |
|
|
378
379
|
| `CLEAROTRON_ORDER_PROBE_SEED` | unset | Seed for `scripts/band-shape-probe.mjs`, so an ordering probe can be replayed. A diagnostic script's knob, not a run's. |
|
|
@@ -282,7 +282,7 @@ edited — that box runs pre-rebuild code and genuinely still sets it.
|
|
|
282
282
|
`CLEAROTRON_OUTBOX_BACKOFF_MAX_RETRIES` (5) / `CLEAROTRON_OUTBOX_GIVEUP_COOLDOWN_SEC` (3600 — note this one
|
|
283
283
|
drops BACKOFF from the prefix, which the elided spelling this row used to carry actively misled about),
|
|
284
284
|
`CLEAROTRON_OUTBOX_NOPROGRESS_MAX`, `TRADEMARK_MSGID_DOMAIN` (enqueue.local).
|
|
285
|
-
Code-set per stage (not operator-set): `CLEAROTRON_GATHER_AGENT` / `CLEAROTRON_GATHER_SESSION_KEY` / `CLEAROTRON_GATHER_SESSION_ID`.
|
|
285
|
+
Code-set per stage (not operator-set): `CLEAROTRON_GATHER_AGENT` / `CLEAROTRON_GATHER_SESSION_KEY` / `CLEAROTRON_GATHER_SESSION_ID`; per turn: `CLEAROTRON_GATHER_SEND_UNTIL_MS`.
|
|
286
286
|
|
|
287
287
|
**Who the completion notice reaches** — `CLEAROTRON_REQUESTER_WHATSAPP` (empty; effect: deployment) is a
|
|
288
288
|
JSON map of requester email or handle → number, and `CLEAROTRON_WHATSAPP_OPERATOR_COPY` (on unless `0`,
|
package/driver/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# clearotron-driver
|
|
2
2
|
|
|
3
|
+
## 0.5.0-beta.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fixed: On the Codex engine, a clearance no longer sees temporary files that another clearance left on the same machine.
|
|
8
|
+
- Fixed: A report's copy for the client is no longer held back when a live finding shares its mark and owner with a finding the review withdrew.
|
|
9
|
+
- Fixed: A register entry the engine decided to keep as background now appears in the report's context table, instead of being left out of it.
|
|
10
|
+
- Fixed: When the report's writing step is interrupted, the next attempt continues from the findings it had already finished instead of starting again.
|
|
11
|
+
- Fixed: On a crowded field, the report is written from the work the clearance has already done, without reading every register result again.
|
|
12
|
+
|
|
13
|
+
## 0.5.0-beta.3
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Fixed: The delivery email for a result the review could not sign off now lists the report's "Only you can close these" items.
|
|
18
|
+
- Fixed: When a clearance fails, its chat notice now goes to the person who asked for it, with a copy to the operator.
|
|
19
|
+
- Fixed: A clearance no longer repeats a finished step because a web lookup inside it timed out.
|
|
20
|
+
- Fixed: A clearance whose register search cannot finish, such as one that keeps timing out, now delivers its report instead of ending with none.
|
|
21
|
+
- Fixed: A search that cannot be shown to have run is now listed as not searched, and the report is still delivered.
|
|
22
|
+
- Fixed: Reports can set aside an identical mark registered for unrelated goods, recording the reason in the audit workbook instead of listing it as a finding.
|
|
23
|
+
|
|
3
24
|
## 0.5.0-beta.2
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/driver/band-shape.mjs
CHANGED
|
@@ -1129,24 +1129,32 @@ export const FLOORS_BY_NAME_HERE = "Every floor here is placed or set aside by n
|
|
|
1129
1129
|
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.";
|
|
1130
1130
|
export const A_CROWDS_ROW_COVERS_A_READ_FLOOR = "This row covers a floor only once you have read its line and its goods are not the client's goods.";
|
|
1131
1131
|
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.";
|
|
1132
|
-
export const AN_UNREAD_CROWDS_FLOOR = "
|
|
1133
|
-
export const AN_UNREAD_LISTS_FLOOR = "
|
|
1132
|
+
export const AN_UNREAD_CROWDS_FLOOR = "This row never covers a floor. Place each floor here, or set it aside by name; a goods ground needs the floor's goods read in full.";
|
|
1133
|
+
export const AN_UNREAD_LISTS_FLOOR = "Place each floor here, or set it aside by name; a goods ground needs the floor's goods read in full. Any other line here may be set aside because its goods are unknown.";
|
|
1134
1134
|
// What placement does with a line of that list before its fate, by its marker (approved wording, 2026-10-19). Placement's
|
|
1135
1135
|
// alone, as its other orders are: every other stage keeps the markers as evidence and is not told of a fate.
|
|
1136
1136
|
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.';
|
|
1137
1137
|
export const PLACEMENT_LINE_ORDERS = Object.freeze([FLOORS_BY_NAME_HERE, A_CROWDS_ONE_ROW, A_CROWDS_ROW_COVERS_A_READ_FLOOR, AN_UNREAD_CROWDS_ONE_ROW, AN_UNREAD_CROWDS_FLOOR, AN_UNREAD_LISTS_FLOOR, AN_UNREAD_LINE_IS_CHECKED]);
|
|
1138
|
+
// AND THE SIZE RULE'S ORDERS TO READ (ruled 2026-10-27). A list's size sentence tells placement how much of it to read,
|
|
1139
|
+
// and only placement owes the lines that read. Every other stage is served the shape without these orders too: the
|
|
1140
|
+
// writer is told the groups are a way to find evidence, never a list it must read to completion. Each list's count,
|
|
1141
|
+
// what it holds and the fact that a crowded one prints its first page stay.
|
|
1142
|
+
export const READ_THEM_ALL = "Fifty? Read them all.";
|
|
1143
|
+
export const READ_EVERY_LINE = ": read every line.";
|
|
1144
|
+
export const READ_THE_LIVE_ONES_FIRST = "A few hundred? Read the live ones in the ordered classes first.";
|
|
1138
1145
|
const SHAPE_LISTS = ["a", "b", "c", "d", "rest", "no_goods"];
|
|
1139
1146
|
const pageEveryOtherLine = (g) => `; page every other line with ${groupPageCall(g)}`;
|
|
1140
1147
|
const pageWhereALineNeedsIt = (g) => `; page with ${groupPageCall(g)} only where a line needs reading`;
|
|
1141
1148
|
const pageTheFloorsLeft = (g) => `Where more floors are counted than this page shows, page the rest with ${groupPageCall(g)}; they come first.`;
|
|
1142
1149
|
/**
|
|
1143
|
-
* The shape as every stage but placement is served it: placement's orders and its group-page commands removed,
|
|
1150
|
+
* The shape as every stage but placement is served it: placement's orders, its size rule's orders to read and its group-page commands removed,
|
|
1144
1151
|
* each with the space before it, inside its line, so no line moves and the line each listed mark is printed on
|
|
1145
1152
|
* still holds. A crowded group's "The first N lines are below" stays, as a fact about what is printed. PURE.
|
|
1146
1153
|
*/
|
|
1147
1154
|
export function withoutPlacementOrders(text) {
|
|
1148
1155
|
let t = String(text ?? "");
|
|
1149
|
-
for (const order of [...PLACEMENT_GROUP_ORDERS, ...PLACEMENT_LINE_ORDERS, ...SHAPE_LISTS.map(pageTheFloorsLeft)]) t = t.split(` ${order}`).join("").split(order).join("");
|
|
1156
|
+
for (const order of [...PLACEMENT_GROUP_ORDERS, ...PLACEMENT_LINE_ORDERS, ...SHAPE_LISTS.map(pageTheFloorsLeft), READ_THEM_ALL, READ_THE_LIVE_ONES_FIRST]) t = t.split(` ${order}`).join("").split(order).join("");
|
|
1157
|
+
t = t.split(READ_EVERY_LINE).join(".");
|
|
1150
1158
|
for (const g of SHAPE_LISTS) t = t.split(pageEveryOtherLine(g)).join("").split(pageWhereALineNeedsIt(g)).join("");
|
|
1151
1159
|
return t;
|
|
1152
1160
|
}
|
|
@@ -1200,16 +1208,16 @@ function renderGroupsMd(md, groups, rows = []) {
|
|
|
1200
1208
|
md.push(`### ${heading(g)}`, "");
|
|
1201
1209
|
if (!grp.lines) { md.push("(none)", ""); continue; }
|
|
1202
1210
|
const crowded = grp.records >= groups.crowded_at;
|
|
1203
|
-
const readA =
|
|
1211
|
+
const readA = `These are the identical and near-identical marks live in the ordered classes for the client's goods${READ_EVERY_LINE}`;
|
|
1204
1212
|
md.push(g === "a"
|
|
1205
1213
|
? (crowded
|
|
1206
1214
|
? `${count(grp)}. ${readA} ${firstPage}${pageEveryOtherLine(g)}. ${FLOORS_BY_NAME_HERE}`
|
|
1207
|
-
: grp.lines <= READ_ALL_LINES ? `${count(grp)}.
|
|
1215
|
+
: grp.lines <= READ_ALL_LINES ? `${count(grp)}. ${READ_THEM_ALL}` : `${count(grp)}. ${readA}`)
|
|
1208
1216
|
: crowded
|
|
1209
1217
|
? `${count(grp)}. A thousand or more? The field is crowded. These are ${crowd[g]}. ${A_CROWDS_ONE_ROW} ${firstPage}${pageWhereALineNeedsIt(g)}.${
|
|
1210
1218
|
g === "b" ? ` ${A_CROWDS_ROW_COVERS_A_READ_FLOOR} ${pageTheFloorsLeft(g)}` : ""}`
|
|
1211
1219
|
: grp.lines <= READ_ALL_LINES
|
|
1212
|
-
? `${count(grp)}.
|
|
1220
|
+
? `${count(grp)}. ${READ_THEM_ALL}`
|
|
1213
1221
|
: `${count(grp)}. ${SORT_THE_NEAR_ONES}`, "");
|
|
1214
1222
|
md.push(...head);
|
|
1215
1223
|
for (const l of crowded ? grp.list.slice(0, groups.first_page) : grp.list) { rows.push([md.length, l.key]); md.push(row(l)); }
|
|
@@ -1223,7 +1231,7 @@ function renderGroupsMd(md, groups, rows = []) {
|
|
|
1223
1231
|
md.push(`**The rest: ${count(rest)}${outside}** — every record in none of (a) to (d) above. ${!rest.lines ? ONE_SET_ASIDE_ROW
|
|
1224
1232
|
: restCrowded
|
|
1225
1233
|
? `A thousand or more? The field is crowded. ${A_CROWDS_ONE_ROW} ${firstPage}${pageWhereALineNeedsIt("rest")}.`
|
|
1226
|
-
: `${rest.lines <= READ_ALL_LINES ?
|
|
1234
|
+
: `${rest.lines <= READ_ALL_LINES ? READ_THEM_ALL : SORT_THE_NEAR_ONES} ${ONE_SET_ASIDE_ROW}`}`);
|
|
1227
1235
|
if (rest.lines) {
|
|
1228
1236
|
md.push("", ...head);
|
|
1229
1237
|
for (const l of restCrowded ? rest.list.slice(0, groups.first_page) : rest.list) { rows.push([md.length, l.key]); md.push(row(l)); }
|
|
@@ -1231,8 +1239,8 @@ function renderGroupsMd(md, groups, rows = []) {
|
|
|
1231
1239
|
if (groups.no_goods.lines) {
|
|
1232
1240
|
// The lines the goods test could not place, read by the same size rule (ruled 2026-10-12): under a thousand
|
|
1233
1241
|
// records, a fate per line by name, opened where goods are on file; at a thousand or more, the first page,
|
|
1234
|
-
// and one row on the rest whose goods are unreadable in a crowded field. A floor is
|
|
1235
|
-
//
|
|
1242
|
+
// and one row on the rest whose goods are unreadable in a crowded field. A floor is set aside on its goods only
|
|
1243
|
+
// once they are read in full, which unknown goods never are, so a floor here is placed or set aside by name.
|
|
1236
1244
|
const ng = groups.no_goods;
|
|
1237
1245
|
const crowded = ng.records >= groups.crowded_at;
|
|
1238
1246
|
const lead = tested.source === "none"
|
|
@@ -1240,7 +1248,7 @@ function renderGroupsMd(md, groups, rows = []) {
|
|
|
1240
1248
|
: `**Goods the test could not read: ${count(ng)}.**`;
|
|
1241
1249
|
md.push("", crowded
|
|
1242
1250
|
? `${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")}`
|
|
1243
|
-
: `${lead} ${ng.lines <= READ_ALL_LINES ?
|
|
1251
|
+
: `${lead} ${ng.lines <= READ_ALL_LINES ? READ_THEM_ALL : READ_THE_LIVE_ONES_FIRST} ${A_FATE_BY_NAME}${tested.source === "none" ? "" : ` ${AN_UNREAD_LINE_IS_CHECKED}`} ${AN_UNREAD_LISTS_FLOOR}`, "");
|
|
1244
1252
|
md.push(...head);
|
|
1245
1253
|
for (const l of crowded ? ng.list.slice(0, groups.first_page) : ng.list) { rows.push([md.length, l.key]); md.push(row(l)); }
|
|
1246
1254
|
}
|
|
@@ -530,9 +530,9 @@ export const INNER_CODES = Object.freeze([
|
|
|
530
530
|
// ONE CODE, TWO TOKENS, and the split is by `cause` rather than by code: verify.mjs peels the
|
|
531
531
|
// `axis_invalid` cause into its own family before counting the rest. A ruling naming only
|
|
532
532
|
// `coverage_no_status` would be true of most `no_status` records and false of the ones that matter most.
|
|
533
|
-
{ code: "no_status", mints: ["driver/coverage-form.mjs:
|
|
533
|
+
{ code: "no_status", mints: ["driver/coverage-form.mjs:939"], rollsUpTo: ["coverage_no_status", "coverage_form_axis_invalid"],
|
|
534
534
|
why: "Two composites, split on the record's `cause`: coverageFormFail() in verify.mjs for cause `axis_invalid`, and the same function for the rest. NOT the `@returns` annotation of findCoverageFormViolations() in coverage-form.mjs — that is the annotation, not the mint." },
|
|
535
|
-
{ code: "engine_vocabulary", mints: ["driver/coverage-form.mjs:
|
|
535
|
+
{ code: "engine_vocabulary", mints: ["driver/coverage-form.mjs:929"], rollsUpTo: ["coverage_form_engine_vocabulary"],
|
|
536
536
|
why: "#669 — the seat wrote an engine token into the `reason` sentence that reaches the reader's page. Checked on settled rows too, because a row the seat considers finished is exactly the one whose sentence gets printed. Namespaced by coverageFormFail() in verify.mjs; the bare code names a row, never a stage." },
|
|
537
537
|
|
|
538
538
|
// ── ONE CODE, TWO MODULES ────────────────────────────────────────────────────────────────────────
|
|
@@ -544,7 +544,7 @@ export const INNER_CODES = Object.freeze([
|
|
|
544
544
|
// failure tokens" already states the rule this row records: `coverage_form_damaged`, never a bare
|
|
545
545
|
// `form_damaged`.
|
|
546
546
|
{ code: "form_damaged",
|
|
547
|
-
mints: ["driver/connotation-search.mjs:2002", "driver/connotation-search.mjs:2078", "driver/coverage-form.mjs:
|
|
547
|
+
mints: ["driver/connotation-search.mjs:2002", "driver/connotation-search.mjs:2078", "driver/coverage-form.mjs:921"],
|
|
548
548
|
rollsUpTo: ["connotation_form_damaged", "coverage_form_damaged"],
|
|
549
549
|
why: "Minted in two lanes and namespaced per lane: connotationDispositionFail() in verify.mjs for the meaning sweep, coverageFormFail() in verify.mjs for the register digest. The namespacing is what keeps them apart — see verify.mjs's block headed \"the register coverage-form failure tokens\"." },
|
|
550
550
|
]);
|
|
@@ -32,6 +32,7 @@ import { COVERAGE_STATUSES, COVERAGE_FORM_NAME, REGISTER_AXES } from "./coverage
|
|
|
32
32
|
import { coverageFormSidecarName, parseCoverageForm } from "./coverage-form.mjs";
|
|
33
33
|
import { capabilitiesFor } from "./register-capabilities.mjs";
|
|
34
34
|
import { readWithheldFamilies, splitWaitingFamilies } from "./withheld-families.mjs";
|
|
35
|
+
import { slicesTakenOutByAxis } from "../shared/slices-taken-out.mjs"; // the slices the searches record took out of the band
|
|
35
36
|
|
|
36
37
|
export { COVERAGE_FORM_NAME };
|
|
37
38
|
|
|
@@ -150,7 +151,8 @@ export function coverageFormInput(runDir) {
|
|
|
150
151
|
return { input: { skeleton: exec.skeleton, plan, bandBlocksByAxis, deferredReasons, activeAxes,
|
|
151
152
|
bandsUnreadable, orderedTerritories, capabilities, unknownAxisUnits,
|
|
152
153
|
awaiting: Array.isArray(exec.awaiting) ? exec.awaiting : [], withheld: readWithheldFamilies(runDir),
|
|
153
|
-
mainMarkets: Array.isArray(exec.main_markets) ? exec.main_markets : []
|
|
154
|
+
mainMarkets: Array.isArray(exec.main_markets) ? exec.main_markets : [],
|
|
155
|
+
takenOut: Object.fromEntries(slicesTakenOutByAxis(runDir)) }, absent: null };
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
/**
|
package/driver/coverage-form.mjs
CHANGED
|
@@ -353,10 +353,14 @@ export const DEFERRED_OPEN_BECAUSE = Object.freeze({
|
|
|
353
353
|
});
|
|
354
354
|
const deferredOpenBecause = (block, receiptReason) => block?.gateway_timeout === true ? DEFERRED_OPEN_BECAUSE.timeout
|
|
355
355
|
: isProviderHardErrorReason(receiptReason) ? DEFERRED_OPEN_BECAUSE.hardError : DEFERRED_OPEN_BECAUSE.capability;
|
|
356
|
+
// A SLICE THE RUN TOOK OUT OF THE BAND (the searches not completed, parts-not-completed.mjs) is open for the reason its
|
|
357
|
+
// record gives, which is the shipped line the reader gets for it (ruled 2026-10-27): the provider could express it, and
|
|
358
|
+
// what is missing is the evidence that it ran. A taken-out slice with no qid has no plan row to defer it, so it gets a
|
|
359
|
+
// row of its own, keyed on its query.
|
|
356
360
|
|
|
357
361
|
export function coverageFormRows({ skeleton = [], activeAxes = null, plan = null,
|
|
358
362
|
bandBlocksByAxis = {}, deferredReasons = {}, bandsUnreadable = [],
|
|
359
|
-
orderedTerritories = [], capabilities = null, awaiting = [], withheld = {}, mainMarkets = [] } = {}) {
|
|
363
|
+
orderedTerritories = [], capabilities = null, awaiting = [], withheld = {}, mainMarkets = [], takenOut = {} } = {}) {
|
|
360
364
|
const skel = Array.isArray(skeleton) ? skeleton.filter((s) => s && typeof s === "object") : [];
|
|
361
365
|
// WHAT WAS RETRIEVED UNDER EACH CROWD (register-plan.mjs, crowdNarrowing), worked out from the same blocks these rows
|
|
362
366
|
// are built from and carried on its row as data: a narrowed list retrieved in full (and which), none, or for the
|
|
@@ -423,6 +427,8 @@ export function coverageFormRows({ skeleton = [], activeAxes = null, plan = null
|
|
|
423
427
|
status: null, reason: null,
|
|
424
428
|
});
|
|
425
429
|
}
|
|
430
|
+
const out = Array.isArray(takenOut?.[axis]) ? takenOut[axis] : [];
|
|
431
|
+
const takenReason = new Map(out.filter((x) => x?.qid && x?.reason).map((x) => [String(x.qid), String(x.reason)]));
|
|
426
432
|
for (const qid of deferredQids) {
|
|
427
433
|
rows.push({
|
|
428
434
|
row_id: shortId("CD", `deferred:${qid}`),
|
|
@@ -433,7 +439,20 @@ export function coverageFormRows({ skeleton = [], activeAxes = null, plan = null
|
|
|
433
439
|
// It was never searched, or it was asked and never answered, and nothing in this run asks it again, so a clean
|
|
434
440
|
// claim over it is not a judgment the seat gets to make.
|
|
435
441
|
open: true,
|
|
436
|
-
open_because: deferredOpenBecause(blockByQid.get(qid), deferredReasons?.[qid]),
|
|
442
|
+
open_because: takenReason.get(qid) ?? deferredOpenBecause(blockByQid.get(qid), deferredReasons?.[qid]),
|
|
443
|
+
status: null, reason: null,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
for (const x of out) {
|
|
447
|
+
if (x?.qid || !x?.query || !x?.reason) continue;
|
|
448
|
+
rows.push({
|
|
449
|
+
row_id: shortId("CD", `deferred:${axis}:query:${x.query}`),
|
|
450
|
+
axis, kind: "deferred",
|
|
451
|
+
unit: unitLabel(axis, { term: String(x.query) }),
|
|
452
|
+
qid: null, query: String(x.query),
|
|
453
|
+
receipt_reason: String(x.reason),
|
|
454
|
+
open: true,
|
|
455
|
+
open_because: String(x.reason),
|
|
437
456
|
status: null, reason: null,
|
|
438
457
|
});
|
|
439
458
|
}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
// `validators` at module scope, so verify importing stages would TDZ-crash whenever verify loads
|
|
24
24
|
// first; stages.mjs re-exports both names, keeping every existing import site working).
|
|
25
25
|
|
|
26
|
+
import { plainDeferralReason } from "./deferral-row.mjs";
|
|
26
27
|
export const REGISTER_AXES = ["saturation-probe", "primary-sweep", "transliteration-numeric", "incumbent-class"];
|
|
27
28
|
|
|
28
29
|
// ── — THE READER'S NAME FOR AN AXIS, EMITTED AT SOURCE ──────────────────────────────────────────
|
|
@@ -347,13 +348,18 @@ export function coverageLedgerTableRows(rows, { reasonMax = 180 } = {}) {
|
|
|
347
348
|
// confirmed-clean rows on tainted material axes move; coverage-limited/deferred rows and non-material
|
|
348
349
|
// axes (saturation-probe) are untouched; an empty taint set is a no-op. PURE; the caller pre-reads the
|
|
349
350
|
// taint verdicts (pipeline loadCoverageLedger → readRegisterTaint).
|
|
351
|
+
// The reason a tainted axis's withdrawn clean claim carries: the shipped gap sentence (deferral-row.mjs).
|
|
352
|
+
export const TAINTED_CLEAN_REASON = plainDeferralReason("unfinished");
|
|
353
|
+
|
|
350
354
|
export function applyTaintDeferred(rows, taintedAxes) {
|
|
351
355
|
const tainted = new Set((taintedAxes ?? []).map((a) => String(a).toLowerCase()));
|
|
352
356
|
if (!tainted.size) return rows ?? [];
|
|
353
357
|
const material = (a) => !NON_MATERIAL_AXES.includes(String(a ?? "").toLowerCase());
|
|
358
|
+
// The pass that claimed clean was killed before it finished reading, so the claim is withdrawn whole: the row is open,
|
|
359
|
+
// with the shipped reason a reader gets for a part not completed, and none of the claim's words.
|
|
354
360
|
return (rows ?? []).map((r) =>
|
|
355
361
|
(r && r.status === "confirmed-clean" && tainted.has(String(r.axis ?? "").toLowerCase()) && material(r.axis))
|
|
356
|
-
? { ...r, status: "deferred", reason:
|
|
362
|
+
? { ...r, status: "deferred", reason: TAINTED_CLEAN_REASON }
|
|
357
363
|
: r);
|
|
358
364
|
}
|
|
359
365
|
|
|
@@ -117,6 +117,13 @@ export const DECLINATION_REASONS = Object.freeze({
|
|
|
117
117
|
gloss: "an off-field name that is not a major brand, an active dispute or proceeding, or a "
|
|
118
118
|
+ "well-known enforcer — omitted, never listed for completeness",
|
|
119
119
|
},
|
|
120
|
+
// A COMPARISON OF MARKS (ruled 2026-10-27): the writer may set aside a near spelling on its sign, as the write-up
|
|
121
|
+
// may, with the write-up's own gloss. It is refused on the applied-for mark itself, which no comparison sets aside.
|
|
122
|
+
sign: {
|
|
123
|
+
discretionary: true,
|
|
124
|
+
rules: 'synthesis-rules.md, "The consumer confusion test — the governing gate"',
|
|
125
|
+
gloss: "a near spelling a buyer in this market could not take for the mark, by sound, by look or by meaning",
|
|
126
|
+
},
|
|
120
127
|
"not-worth-the-line": {
|
|
121
128
|
discretionary: true,
|
|
122
129
|
rules: 'synthesis-rules.md, "Promote-or-omit"',
|
|
@@ -206,7 +213,7 @@ export function contradictionFor(reason, row, scope) {
|
|
|
206
213
|
// class is closed to the entry that holds it. Each record is judged on its own mark, status and classes.
|
|
207
214
|
if (Array.isArray(row?.members) && row.members.length) {
|
|
208
215
|
for (const m of row.members) {
|
|
209
|
-
const clash = contradictionFor(reason, { mark: m?.mark ?? row.mark, status: m?.status ?? null, classes: m?.classes ?? [] }, scope);
|
|
216
|
+
const clash = contradictionFor(reason, { mark: m?.mark ?? row.mark, status: m?.status ?? null, classes: m?.classes ?? [], goods: m?.goods ?? null }, scope);
|
|
210
217
|
if (clash) return clash;
|
|
211
218
|
}
|
|
212
219
|
return "";
|
|
@@ -218,14 +225,26 @@ export function contradictionFor(reason, row, scope) {
|
|
|
218
225
|
// it switched the identical-mark refusal below off on exactly those runs.
|
|
219
226
|
const marks = Array.isArray(scope?.marks) ? scope.marks : (scope?.marks ? [scope.marks] : []);
|
|
220
227
|
|
|
228
|
+
// ── AN IDENTICAL MARK IN A FILED CLASS MAY BE DECLINED ON ITS GOODS, AND ON NOTHING ELSE DISCRETIONARY ──
|
|
229
|
+
//
|
|
230
|
+
// Ruled 2026-10-27: identical spelling and a shared class no longer bar a supported judgment that the registered
|
|
231
|
+
// goods are unrelated. This refused every discretionary token on such a record, so on a test run 29 goods
|
|
232
|
+
// decisions were refused and every one came back as a finding. A goods decision now stands where the run holds the
|
|
233
|
+
// record's goods complete (goods-evidence.mjs), because cut, unknown or missing goods establish nothing. The
|
|
234
|
+
// record's field, the report's length and a comparison with the applied-for mark itself are still refused.
|
|
221
235
|
if (isIdenticalToAppliedMark(row?.mark, marks) && isLive(row?.status) && shared.length) {
|
|
222
|
-
|
|
223
|
-
+
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
236
|
+
const facts = `this record's mark is IDENTICAL to the applied-for mark, its status is `
|
|
237
|
+
+ `${String(row?.status ?? "").trim() || "live"}, and it is in filed class ${shared.join(", ")}`;
|
|
238
|
+
if (reason === "unrelated-goods") {
|
|
239
|
+
if (row?.goods === "complete") return "";
|
|
240
|
+
return `${facts}. The run holds its goods as ${String(row?.goods ?? "").trim() || "not recorded"}, not in full, `
|
|
241
|
+
+ `and incomplete goods cannot show the goods are unrelated. Deliver and rate it, or use own-right or `
|
|
242
|
+
+ `duplicate-of-delivered if one is true.`;
|
|
243
|
+
}
|
|
244
|
+
return `${facts}. Such a mark is never declined for its field, for want of room, or on its sign. Deliver and `
|
|
245
|
+
+ `rate it; a Stage-2 mitigant goes into the rating. If its registered goods, read in full, do not meet the `
|
|
246
|
+
+ `matter's goods, decline it as unrelated-goods and say so in the ground. If it is the applicant's own right `
|
|
247
|
+
+ `or already delivered, use own-right or duplicate-of-delivered.`;
|
|
229
248
|
}
|
|
230
249
|
|
|
231
250
|
// ── ECONOMY IS NOT A REASON ABOUT A LIVE IN-CLASS MARK (ruling 2026-09-07) ─────────────────
|
|
@@ -250,14 +269,12 @@ export function contradictionFor(reason, row, scope) {
|
|
|
250
269
|
// It does effectively retire this token for live in-class records, because the sweep is already scoped
|
|
251
270
|
// to the instructed classes. That was put to the owner as the consequence and ruled anyway.
|
|
252
271
|
if (reason === "not-worth-the-line" && isLive(row?.status) && shared.length) {
|
|
253
|
-
return `this record is ${String(row?.status ?? "").trim() || "live"}
|
|
254
|
-
+
|
|
255
|
-
+ `
|
|
256
|
-
+ `
|
|
257
|
-
+ `
|
|
258
|
-
+ `if
|
|
259
|
-
+ `own-right if it belongs to the applicant, duplicate-of-delivered if the same right is already `
|
|
260
|
-
+ `reported. Say what makes this mark distant, or give it its line.`;
|
|
272
|
+
return `this record is ${String(row?.status ?? "").trim() || "live"} in instructed class ${shared.join(", ")}. `
|
|
273
|
+
+ `A live mark in an instructed class is never declined for want of room. Deliver it, or decline it on a fact `
|
|
274
|
+
+ `about this mark: unrelated-goods if its goods do not meet the matter's; off-field-not-major if it is `
|
|
275
|
+
+ `off-field and not a major brand, an active dispute or a well-known enforcer; sign if a buyer in this market `
|
|
276
|
+
+ `could not take its spelling for the mark; own-right if it belongs to the applicant; duplicate-of-delivered `
|
|
277
|
+
+ `if the same right is already reported.`;
|
|
261
278
|
}
|
|
262
279
|
|
|
263
280
|
// THERE IS NO CLASS-OVERLAP REFUSAL, AND THE FIRST DRAFT HAD ONE. It refused `unrelated-goods` and
|
|
@@ -171,6 +171,12 @@ function register(runDir, log) {
|
|
|
171
171
|
const open = readPartNotCompleted(runDir, "assessment");
|
|
172
172
|
if (open && ["placement-inquiry", "register-digest"].includes(open.stage))
|
|
173
173
|
failed.push([{ fail: `${open.stage} did not complete: ${open.reason ?? "no cause recorded"}` }, "the register write-up"]);
|
|
174
|
+
// The searches a check could not prove ran, taken out of the band and delivered as open rows (parts-not-completed.mjs).
|
|
175
|
+
const searches = readPartNotCompleted(runDir, "searches");
|
|
176
|
+
if (searches) failed.push([{ fail: [
|
|
177
|
+
(searches.slices ?? []).length ? `${searches.slices.length} slice(s) taken out of the band` : "",
|
|
178
|
+
(searches.disclosed_axes ?? []).length ? `the pass on ${searches.disclosed_axes.join(", ")} timed out, read as it stands with its clean claims open` : "",
|
|
179
|
+
].filter(Boolean).join("; ") + `: ${searches.reason ?? "no cause recorded"}` }, "the searches"]);
|
|
174
180
|
let reopen = null;
|
|
175
181
|
try { reopen = JSON.parse(readFileSync(driverDir(runDir, "frame-reopen.json"), "utf8")); } catch { reopen = null; }
|
|
176
182
|
if (reopen?.dominant_element_gap === true && reopen?.domClosed !== true)
|
|
@@ -280,9 +280,9 @@ function bandShape(params) {
|
|
|
280
280
|
...(read ? { served_lines: read.keys[part - 1], group_parts: read.groupParts } : {}) });
|
|
281
281
|
const head = format === "json"
|
|
282
282
|
? `[band_shape part ${part}/${total} of the json; concatenate the parts before parsing it.]`
|
|
283
|
-
: `[band_shape part ${part}/${total}. Read each group by the size sentence at its head; the next part continues the groups.]`;
|
|
283
|
+
: `[band_shape part ${part}/${total}. ${GROUP_PAGES ? "Read each group by the size sentence at its head; the next part continues the groups." : "The next part continues the groups."}]`;
|
|
284
284
|
const tail = part < total
|
|
285
|
-
? `\n[end of part ${part}/${total} —
|
|
285
|
+
? `\n[end of part ${part}/${total} — part ${part + 1} is available with {"part": ${part + 1}}]`
|
|
286
286
|
: `\n[end of the shape]`;
|
|
287
287
|
return head + "\n" + parts[part - 1] + tail;
|
|
288
288
|
}
|
|
@@ -351,7 +351,7 @@ function groupLookup(params) {
|
|
|
351
351
|
const body = () => JSON.stringify({
|
|
352
352
|
group: g, lines_total: lines.length, returned: page.length, ...(offset ? { offset } : {}),
|
|
353
353
|
...(lines.length > offset + page.length ? { next_offset: offset + page.length,
|
|
354
|
-
note: `
|
|
354
|
+
note: `the next lines of group ${g} are available with offset: ${offset + page.length}` } : {}),
|
|
355
355
|
lines: page.map(({ _key, ...l }) => l),
|
|
356
356
|
}, null, 2);
|
|
357
357
|
let out = body();
|
|
@@ -440,8 +440,8 @@ function bandLookup(params = {}) {
|
|
|
440
440
|
const body = () => JSON.stringify({
|
|
441
441
|
matched, returned: hits.length, ...(offset ? { offset } : {}),
|
|
442
442
|
...(matched > offset + hits.length ? { next_offset: offset + hits.length } : {}),
|
|
443
|
-
...(matched > offset + hits.length ? { note: `capped at ${hits.length} —
|
|
444
|
-
...(byteCapped ? { byte_cap_note: `${byteCapped} matched record(s) withheld to fit one tool response —
|
|
443
|
+
...(matched > offset + hits.length ? { note: `capped at ${hits.length} — the next page is available with offset: ${offset + hits.length}; the count above is the full match` } : {}),
|
|
444
|
+
...(byteCapped ? { byte_cap_note: `${byteCapped} matched record(s) withheld to fit one tool response — the rest are available with offset: ${offset + hits.length}` } : {}),
|
|
445
445
|
records: hits,
|
|
446
446
|
...(crowds.length ? { matching_crowds: crowds, crowd_note: "these matching slices were NOT enumerated — records under them may exist that no lookup can return; weigh the descriptor, never treat this result as their clean"
|
|
447
447
|
+ (refusedCrowds ? ` — and ${refusedCrowds} of them was/were NEVER ANSWERED at all (see "refusal"): no count exists for those, so they are unsearched rather than empty, and nothing about them can be read off a zero` : "")
|
|
@@ -515,7 +515,7 @@ function bandRecord(params = {}) {
|
|
|
515
515
|
logRead("band_record", { record_id: uri, part, parts: total }, { ok: true, bytes: parts[part - 1].length, total_bytes: text.length, ...resolution, ...served.audit });
|
|
516
516
|
const head = `[band_record part ${part}/${total}. This record is served in ${total} parts; the next part continues it.]`;
|
|
517
517
|
const tail = part < total
|
|
518
|
-
? `\n[end of part ${part}/${total} —
|
|
518
|
+
? `\n[end of part ${part}/${total} — part ${part + 1} is available with {"record_id": "${uri}", "part": ${part + 1}}]`
|
|
519
519
|
: `\n[end of the record]`;
|
|
520
520
|
return head + "\n" + parts[part - 1] + tail;
|
|
521
521
|
}
|
|
@@ -537,7 +537,7 @@ function bandRecord(params = {}) {
|
|
|
537
537
|
const part = Number.isInteger(wanted) && wanted >= 1 && wanted <= parts.length ? wanted : 1;
|
|
538
538
|
logRead("band_record", { record_id: uri }, { ok: true, bytes: text.length, via: "ledger", part, parts: parts.length, ...served.audit });
|
|
539
539
|
return { type: "text", text: `[record ${uri} — part ${part}/${parts.length}]\n` + parts[part - 1]
|
|
540
|
-
+ (part < parts.length ? `\n[end of part ${part}/${parts.length} —
|
|
540
|
+
+ (part < parts.length ? `\n[end of part ${part}/${parts.length} — part ${part + 1} is available with {"record_id": "${uri}", "part": ${part + 1}}]` : "") };
|
|
541
541
|
}
|
|
542
542
|
|
|
543
543
|
// A GENUINE miss — neither materialised NOR in this run's ledger. Now the sentence below is true.
|
|
@@ -581,7 +581,7 @@ function bandRecord(params = {}) {
|
|
|
581
581
|
...(parts.length > 1 ? { part, parts: parts.length } : {}), ...(failed ? { fetch_failed: true } : {}) });
|
|
582
582
|
if (parts.length === 1) return { isError: true, text };
|
|
583
583
|
return { isError: true, text: `[record ${uri} — part ${part}/${parts.length}]\n` + parts[part - 1]
|
|
584
|
-
+ (part < parts.length ? `\n[end of part ${part}/${parts.length} —
|
|
584
|
+
+ (part < parts.length ? `\n[end of part ${part}/${parts.length} — part ${part + 1} is available with {"record_id": "${uri}", "part": ${part + 1}}]` : "") };
|
|
585
585
|
}
|
|
586
586
|
|
|
587
587
|
// The band row a record id names, from the band file, read once per server. null where the band holds none.
|
|
@@ -610,10 +610,10 @@ const GROUP_PAGES = process.env.CLEAROTRON_BAND_GROUPS === "1";
|
|
|
610
610
|
// served the shape without them (the owner's ruling, 2026-10-14). Each is removed with the space before it, inside
|
|
611
611
|
// its line, so no line moves and the line each listed mark is printed on, which `linesByPart` reads, still holds.
|
|
612
612
|
// (band-shape.mjs `withoutPlacementOrders` removes them, with the shape's other placement-only orders and its group-page commands.)
|
|
613
|
-
const LOOKUP_DESCRIPTION = "Look up enumerated records in this run's frozen register band (read-only; logged to the reading audit). Filters AND together: record_id (exact uri), owner (substring), nice_class, tier (identical|near-identical|same-family|other|unclassifiable — classified against the shape's frozen targets), qid (exact plan-entry id — the plan-execution ledger join; a record surfaced by several slices matches under EVERY one of them), query (the _query slice that surfaced the record), text (substring of mark_text), status (substring), live_only, goods (words: records whose goods contain any of them, as whole words, singular or plural; a record with no goods on file matches too), group (a, b, c, d, rest or no_goods: that list's lines in the shape's order, one per mark of an owner), limit (default 20, max 100), offset (default 0: where in the matching records or lines to start — a capped answer names the
|
|
613
|
+
const LOOKUP_DESCRIPTION = "Look up enumerated records in this run's frozen register band (read-only; logged to the reading audit). Filters AND together: record_id (exact uri), owner (substring), nice_class, tier (identical|near-identical|same-family|other|unclassifiable — classified against the shape's frozen targets), qid (exact plan-entry id — the plan-execution ledger join; a record surfaced by several slices matches under EVERY one of them), query (the _query slice that surfaced the record), text (substring of mark_text), status (substring), live_only, goods (words: records whose goods contain any of them, as whole words, singular or plural; a record with no goods on file matches too), group (a, b, c, d, rest or no_goods: that list's lines in the shape's order, one per mark of an owner), limit (default 20, max 100), offset (default 0: where in the matching records or lines to start — a capped answer names the offset where the next page starts). On each record served, `goods_words` gives the client's goods words its goods hold, as the shape tests them (k of N: the words), or null where its goods cannot be read; `goods_from` says what those goods rest on: official record; official record, cut by the register; official record, completeness not established; official record, no goods listed; search answer; search answer, cut by the register; search answer, completeness not established; fetch failed; or nothing held. The goods text is in band_record. Matching un-enumerated crowd descriptors ride along so a lookup can never mistake a counted-only zone for a clean.";
|
|
614
614
|
const LOOKUP_GROUP_CLAUSE = "group (a, b, c, d, rest or no_goods: that list's lines in the shape's order, one per mark of an owner), ";
|
|
615
|
-
const LOOKUP_OFFSET_GROUPS = "offset (default 0: where in the matching records or lines to start — a capped answer names the
|
|
616
|
-
const LOOKUP_OFFSET_BAND = "offset (default 0: where in the matching records to start — a capped answer names the
|
|
615
|
+
const LOOKUP_OFFSET_GROUPS = "offset (default 0: where in the matching records or lines to start — a capped answer names the offset where the next page starts)";
|
|
616
|
+
const LOOKUP_OFFSET_BAND = "offset (default 0: where in the matching records to start — a capped answer names the offset where the next page starts)";
|
|
617
617
|
/** The lookup's description as a stage is offered it: whole where it reads the groups, without them elsewhere. PURE. */
|
|
618
618
|
const lookupDescription = (groupPages) => (groupPages ? LOOKUP_DESCRIPTION
|
|
619
619
|
: LOOKUP_DESCRIPTION.replace(LOOKUP_GROUP_CLAUSE, "").replace(LOOKUP_OFFSET_GROUPS, LOOKUP_OFFSET_BAND));
|
|
@@ -623,7 +623,7 @@ serve({
|
|
|
623
623
|
tools: [
|
|
624
624
|
{
|
|
625
625
|
name: "band_shape",
|
|
626
|
-
description: "The deterministic shape of this run's merged register band: totals, mechanical tiers, the floors (every live in-class identical/near-identical record), each marked on its line in the band in the lawyer's order, class/status/registry/recency census, owner concentrations, crowd descriptors (with crowd-context joins) and blind spots.
|
|
626
|
+
description: "The deterministic shape of this run's merged register band: totals, mechanical tiers, the floors (every live in-class identical/near-identical record), each marked on its line in the band in the lawyer's order, class/status/registry/recency census, owner concentrations, crowd descriptors (with crowd-context joins) and blind spots. What it names can be looked up with band_lookup and band_record. A large shape is served in parts, and the end of each part names the next one. Every call is logged to the run's reading audit.",
|
|
627
627
|
inputSchema: { type: "object", properties: { format: { type: "string", enum: ["md", "json"], description: "md (default, readable) or json (machine shape)" }, part: { type: "number", description: "1-based part number when the shape is served in parts (default 1)" } } },
|
|
628
628
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
629
629
|
handler: bandShape,
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
// `match_mode: "phonetic"` on register_search / register_enumerate remains fully available; it is the
|
|
30
30
|
// PREVIEW of the expansion that this provider cannot give, not phonetic search itself.
|
|
31
31
|
import { serve } from "./stdio-server.mjs";
|
|
32
|
+
import { sendUntilFromEnv } from "../../../providers/_shared/send-until.mjs";
|
|
32
33
|
import { crowdNoteFor } from "./crowd-note.mjs"; // how the unit decides on a crowd, beside an answer that carries one
|
|
33
34
|
import {
|
|
34
35
|
CAPABILITIES, doSearch, doRecordFetch, doImageFetch, doBatchScreen, doEnumerate, doExecutePlan, DEFAULT_BASE,
|
|
@@ -43,6 +44,7 @@ const tctx = (kind) => ({
|
|
|
43
44
|
agentId: process.env.CLEAROTRON_GATHER_AGENT || "localagent",
|
|
44
45
|
sessionKey: process.env.CLEAROTRON_GATHER_SESSION_KEY || "", // run session key — telemetry attribution
|
|
45
46
|
sessionId: process.env.CLEAROTRON_GATHER_SESSION_ID || "",
|
|
47
|
+
sendUntilMs: sendUntilFromEnv(), // this turn's time to stop sending (providers/_shared/send-until.mjs)
|
|
46
48
|
});
|
|
47
49
|
const passthrough = (r) => (r && typeof r === "object" ? (r.text ?? JSON.stringify(r)) : String(r));
|
|
48
50
|
const guard = (fn) => async (args) => {
|
|
@@ -275,10 +275,15 @@ export function featuresToml(withheld = []) {
|
|
|
275
275
|
// codex's `workspace-write` sandbox confines where a command WRITES and lets it read the whole disk, the
|
|
276
276
|
// install's settings files and the program's own sign-in included. A permission profile confines reading
|
|
277
277
|
// too (the vendor's Permissions guide, a beta feature since 0.138.0; the floor was 0.154.0 when this was written and is 0.159.0 now, for the judgment tier's model — driver.config.mjs): a stage's
|
|
278
|
-
// commands read the platform's own paths
|
|
279
|
-
//
|
|
280
|
-
// cannot get a command to print the account's files.
|
|
281
|
-
//
|
|
278
|
+
// commands read the platform's own paths and the instruction trees, and write the run folder, their
|
|
279
|
+
// working folder and their Codex home's working folder. Nothing else is readable, so a page a stage
|
|
280
|
+
// fetched cannot get a command to print the account's files.
|
|
281
|
+
//
|
|
282
|
+
// NO SHARED TEMP FOLDER. The profile granted `:tmpdir` and `:slash_tmp`, which every stage of every run on
|
|
283
|
+
// the machine shares, and a later run's commands read what an earlier run left in `/tmp` (measured on codex
|
|
284
|
+
// 0.162.0 with that profile: a command read a file planted in `/tmp` and wrote another beside it). The
|
|
285
|
+
// turn's TMPDIR, TMP and TEMP now name its home's working folder (openai-agent.mjs, `withWorkFolder`), and
|
|
286
|
+
// this grants that folder, named, in their place.
|
|
282
287
|
//
|
|
283
288
|
// ONLY WITH THE SANDBOX ON. Under the bypass codex builds no sandbox, so a profile would enforce nothing;
|
|
284
289
|
// the caller passes no `fence` then, and the file stays what it was. With the sandbox on the command line
|
|
@@ -297,10 +302,10 @@ export function featuresToml(withheld = []) {
|
|
|
297
302
|
// wins over the run folder's: measured on codex 0.162.0 with this profile, a write into the run folder
|
|
298
303
|
// succeeded, a read of `_driver/` succeeded, and a write or an append there was refused.
|
|
299
304
|
export const FENCE_PROFILE = "clearotron-stage";
|
|
300
|
-
export function fenceToml({ runDir = null, readRoots = [] } = {}) {
|
|
305
|
+
export function fenceToml({ runDir = null, workDir = null, readRoots = [] } = {}) {
|
|
301
306
|
const key = (p) => tomlString(p);
|
|
302
|
-
const out = [`[permissions.${FENCE_PROFILE}.filesystem]`,
|
|
303
|
-
|
|
307
|
+
const out = [`[permissions.${FENCE_PROFILE}.filesystem]`, `":minimal" = "read"`];
|
|
308
|
+
if (workDir) out.push(`${key(workDir)} = "write"`);
|
|
304
309
|
for (const r of [...new Set(readRoots.filter(Boolean))]) out.push(`${key(r)} = "read"`);
|
|
305
310
|
if (runDir) out.push(`${key(runDir)} = "write"`, `${key(driverDir(runDir))} = "read"`);
|
|
306
311
|
out.push("", `[permissions.${FENCE_PROFILE}.filesystem.":workspace_roots"]`, `"." = "write"`, "");
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// session key (tctx.sessionKey) — we thread the run's session key via CLEAROTRON_GATHER_SESSION_KEY so the
|
|
8
8
|
// $0 provider-usage diff still attributes calls to the run.
|
|
9
9
|
import { serve } from "./stdio-server.mjs";
|
|
10
|
+
import { sendUntilFromEnv } from "../../../providers/_shared/send-until.mjs";
|
|
10
11
|
import { crowdNoteFor } from "./crowd-note.mjs"; // how the unit decides on a crowd, beside an answer that carries one
|
|
11
12
|
import { CAPABILITIES, doSearch, doRecordFetch, doImageFetch, doExpandPhoneme, doBatchScreen, doEnumerate, doExecutePlan } from "../../../providers/corsearch/src/core.js";
|
|
12
13
|
import { proposeSupplemental } from "./supplemental.mjs";
|
|
@@ -18,6 +19,7 @@ const tctx = (kind) => ({
|
|
|
18
19
|
agentId: process.env.CLEAROTRON_GATHER_AGENT || "localagent",
|
|
19
20
|
sessionKey: process.env.CLEAROTRON_GATHER_SESSION_KEY || "", // run session key — telemetry attribution
|
|
20
21
|
sessionId: process.env.CLEAROTRON_GATHER_SESSION_ID || "",
|
|
22
|
+
sendUntilMs: sendUntilFromEnv(), // this turn's time to stop sending (providers/_shared/send-until.mjs)
|
|
21
23
|
});
|
|
22
24
|
const passthrough = (r) => (r && typeof r === "object" ? (r.text ?? JSON.stringify(r)) : String(r));
|
|
23
25
|
const guard = (fn) => async (args) => {
|
|
@@ -83,12 +83,11 @@ serve({
|
|
|
83
83
|
description:
|
|
84
84
|
"Record, per position or page, your decision NOT to deliver something that reached your findings surface. " +
|
|
85
85
|
"Every position the digest carried to you, and every page the web notes marked as a candidate or conflict, " +
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"a defect of this run. One call carries a batch; the answer tells you what was accepted, " +
|
|
86
|
+
"ends as a finding or as a declination with its reason. One left with neither is reported as a defect " +
|
|
87
|
+
"of this run. One call carries a batch; the answer tells you what was accepted, " +
|
|
89
88
|
"what was refused and why, and WHICH ROWS STILL CARRY NO DECISION, so you can finish inside this " +
|
|
90
|
-
"turn. A refusal is about
|
|
91
|
-
"
|
|
89
|
+
"turn. A refusal is about the record's facts, never your legal judgment: a reason is refused only when it " +
|
|
90
|
+
"contradicts the matter's instructions or the record's mark, status, classes or goods as the run holds them.",
|
|
92
91
|
inputSchema: { type: "object", required: ["declinations"], properties: {
|
|
93
92
|
declinations: {
|
|
94
93
|
type: "array",
|