clearotron 0.5.0-beta.1 → 0.5.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +7 -0
- package/INSTALL.md +1 -1
- package/THIRD-PARTY-NOTICES.md +38 -7
- package/build-info.json +2 -2
- package/docs/DELIVERY.md +4 -5
- package/docs/architecture/03-run-lifecycle.md +1 -2
- package/docs/architecture/04-configuration-reference.md +1 -1
- package/docs/architecture/05-config-governance.md +2 -2
- package/docs/architecture/06-operations-runbook.md +35 -40
- package/docs/architecture/07-quality-and-audit.md +1 -5
- package/driver/CHANGELOG.md +18 -0
- package/driver/band-shape.mjs +32 -11
- package/driver/close-verify.mjs +3 -2
- package/driver/contract-e3-backlog.mjs +17 -17
- package/driver/contract-vocabulary.mjs +6 -6
- package/driver/coverage-form-io.mjs +3 -1
- package/driver/coverage-form.mjs +42 -6
- package/driver/coverage-ledger.mjs +37 -7
- package/driver/declination-call.mjs +44 -17
- package/driver/declination-duty.mjs +21 -10
- package/driver/degraded-parts.mjs +43 -4
- package/driver/deliver-trigger.sh +3 -2
- package/driver/driver.config.mjs +26 -28
- package/driver/engine/CONTRACT.md +3 -1
- package/driver/engine/mcp/README.md +1 -1
- package/driver/engine/mcp/clarivate-server.mjs +4 -1
- package/driver/engine/mcp/corsearch-server.mjs +4 -1
- package/driver/engine/mcp/crowd-note.mjs +34 -0
- package/driver/engine/mcp/declination-server.mjs +6 -7
- package/driver/engine/mcp/euipo-server.mjs +4 -1
- package/driver/engine/mcp/free-tier-server.mjs +4 -1
- package/driver/engine/mcp/gather-config.mjs +15 -0
- package/driver/engine/mcp/held-answers.mjs +100 -12
- package/driver/engine/mcp/perplexity-server.mjs +4 -1
- package/driver/engine/mcp/signa-server.mjs +4 -1
- package/driver/engine/mcp/stdio-server.mjs +8 -3
- package/driver/engine/mcp/supplemental.mjs +66 -8
- package/driver/engine/mcp/uspto-local-server.mjs +4 -1
- package/driver/engine/openai-agent.mjs +61 -8
- package/driver/engine/session-record.mjs +21 -3
- package/driver/envelope-settle.mjs +13 -3
- package/driver/findings-model.mjs +16 -0
- package/driver/frame-diff-model.mjs +53 -1
- package/driver/gateway.mjs +31 -10
- package/driver/outbox.mjs +2 -1
- package/driver/package.json +1 -1
- package/driver/parts-not-completed.mjs +128 -0
- package/driver/pipeline-knockout.mjs +5 -4
- package/driver/pipeline.mjs +1470 -787
- package/driver/placement-decisions.mjs +21 -13
- package/driver/placement-form-io.mjs +21 -1
- package/driver/placement-form.mjs +40 -21
- package/driver/placement-union.mjs +56 -11
- package/driver/publish/audit-from-spine.mjs +6 -3
- package/driver/publish/index.mjs +45 -14
- package/driver/publish/publish-inputs.mjs +8 -0
- package/driver/publish/render.mjs +48 -11
- package/driver/publish/report-data.mjs +4 -0
- package/driver/publish/search-depth.mjs +10 -3
- package/driver/publish/xlsx.mjs +30 -16
- package/driver/record-fold.mjs +4 -1
- package/driver/register-digest-record.mjs +57 -5
- package/driver/register-plan.mjs +87 -20
- package/driver/registration-scripts.mjs +56 -0
- package/driver/remedy-accounting.mjs +1 -1
- package/driver/repair-composers.mjs +35 -49
- package/driver/report-overview-record.mjs +1 -1
- package/driver/research-answers.mjs +108 -0
- package/driver/run-economics.mjs +3 -2
- package/driver/runner.mjs +4 -4
- package/driver/scope-ledger.mjs +13 -6
- package/driver/skeptic-search.mjs +19 -3
- package/driver/skills/blind-frame/SKILL.md +1 -1
- package/driver/skills/case-law-citation/SKILL.md +5 -5
- package/driver/skills/case-law-citation/sources/courtlistener.md +2 -2
- package/driver/skills/case-law-citation/sources/eurlex.md +2 -2
- package/driver/skills/case-law-citation/sources/legaldatahunter.md +1 -1
- package/driver/skills/clearance-common-law/SKILL.md +26 -34
- package/driver/skills/clearance-common-law/perplexity-prompts.md +4 -6
- package/driver/skills/clearance-register/SKILL.md +18 -26
- package/driver/skills/clearance-register/digest.md +14 -10
- package/driver/skills/clearance-register/providers/clarivate.md +10 -8
- package/driver/skills/clearance-register/providers/corsearch.md +4 -5
- package/driver/skills/clearance-register/providers/euipo.md +1 -2
- package/driver/skills/clearance-register/providers/signa.md +5 -6
- package/driver/skills/clearance-register/providers/uspto-local.md +3 -3
- package/driver/skills/clearance-register/register-recipes.md +33 -42
- package/driver/skills/clearance-register/unit.md +18 -26
- package/driver/skills/clearance-search/SKILL.md +14 -30
- package/driver/skills/clearance-search/firm-wide-reasoning.md +3 -3
- package/driver/skills/clearance-search/phase2-execution.md +23 -37
- package/driver/skills/clearance-search/synthesis-rules.md +4 -3
- package/driver/skills/clearance-search/worked-examples.md +62 -62
- package/driver/skills/clearance-variants/SKILL.md +20 -20
- package/driver/skills/clearance-variants/transliteration-scripts.md +3 -3
- package/driver/skills/knockout-assess/SKILL.md +3 -2
- package/driver/skills/knockout-frame/SKILL.md +3 -3
- package/driver/skills/matter-frame/SKILL.md +1 -1
- package/driver/skills/narrative-refutation/SKILL.md +6 -6
- package/driver/skills/placement-inquiry/SKILL.md +9 -9
- package/driver/stages.mjs +65 -31
- package/driver/suite-census.json +506 -44
- package/driver/systemd/README.md +54 -150
- package/driver/verify.mjs +19 -10
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +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 +11 -8
- package/providers/_shared/execute-plan.mjs +122 -11
- package/providers/_shared/follow-up-searches.mjs +59 -8
- package/providers/_shared/send-until.mjs +25 -0
- package/providers/clarivate/src/core.js +63 -4
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/perplexity/src/core.js +42 -6
- package/scripts/band-shape-probe.mjs +1 -1
- package/scripts/e2e-first-time.mjs +7 -1
- package/scripts/e2e-scenario-ops.mjs +17 -2
- package/scripts/freeze-example-run.mjs +8 -8
- package/shared/client-failure-note.mjs +13 -0
- package/shared/names-in-force.mjs +1 -1
- package/shared/slices-taken-out.mjs +65 -0
- package/skills/clearotron-account/SKILL.md +1 -1
- package/skills/clearotron-ops/COURIER.md +11 -3
- package/skills/clearotron-ops/SKILL.md +4 -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/INSTALL.md
CHANGED
|
@@ -59,7 +59,7 @@ run is [mcp-server/CONNECT.md](mcp-server/CONNECT.md), and why something is the
|
|
|
59
59
|
it first. VS Code's Remote-SSH port forwarding is the common case. If the page that opens is not this
|
|
60
60
|
install's sign-in, run the same command again with `--port 28802`, or any free number.
|
|
61
61
|
|
|
62
|
-
A *hosted* deployment
|
|
62
|
+
A *hosted* deployment runs the product as systemd user units, so it needs Linux —
|
|
63
63
|
[driver/systemd/README.md](driver/systemd/README.md).
|
|
64
64
|
- **Setup installs the reasoning program.** Clearotron runs each step of a clearance as a short,
|
|
65
65
|
unattended session of Claude Code or the Codex CLI. Setup installs the one your engine uses, for this
|
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -9,7 +9,7 @@ file rather than a courtesy.
|
|
|
9
9
|
production tree; `--check` fails when it no longer matches. Development dependencies are excluded:
|
|
10
10
|
they are not distributed, so they carry no obligation for anyone who installs this.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
178 packages.
|
|
13
13
|
|
|
14
14
|
> **Declared but NOT INSTALLED, so not distributed and not attributed below:** `@cfworker/json-schema`.
|
|
15
15
|
> npm lists these with no version and no path — an unmet or optional peer. Recorded because a name
|
|
@@ -534,7 +534,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
534
534
|
THE SOFTWARE.
|
|
535
535
|
```
|
|
536
536
|
|
|
537
|
-
## body-parser@2.
|
|
537
|
+
## body-parser@2.4.0
|
|
538
538
|
|
|
539
539
|
- **Licence declared:** `MIT`
|
|
540
540
|
- **Repository:** expressjs/body-parser
|
|
@@ -932,6 +932,37 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
|
932
932
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
933
933
|
```
|
|
934
934
|
|
|
935
|
+
## content-type@2.1.0
|
|
936
|
+
|
|
937
|
+
- **Licence declared:** `MIT`
|
|
938
|
+
- **Repository:** jshttp/content-type
|
|
939
|
+
- **Licence file:** `LICENSE`
|
|
940
|
+
|
|
941
|
+
```
|
|
942
|
+
(The MIT License)
|
|
943
|
+
|
|
944
|
+
Copyright (c) 2015 Douglas Christopher Wilson
|
|
945
|
+
|
|
946
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
947
|
+
a copy of this software and associated documentation files (the
|
|
948
|
+
'Software'), to deal in the Software without restriction, including
|
|
949
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
950
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
951
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
952
|
+
the following conditions:
|
|
953
|
+
|
|
954
|
+
The above copyright notice and this permission notice shall be
|
|
955
|
+
included in all copies or substantial portions of the Software.
|
|
956
|
+
|
|
957
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
958
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
959
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
960
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
961
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
962
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
963
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
964
|
+
```
|
|
965
|
+
|
|
935
966
|
## content-type@2.0.0
|
|
936
967
|
|
|
937
968
|
- **Licence declared:** `MIT`
|
|
@@ -1968,7 +1999,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
1968
1999
|
SOFTWARE.
|
|
1969
2000
|
```
|
|
1970
2001
|
|
|
1971
|
-
## fast-uri@3.1.
|
|
2002
|
+
## fast-uri@3.1.9
|
|
1972
2003
|
|
|
1973
2004
|
- **Licence declared:** `BSD-3-Clause`
|
|
1974
2005
|
- **Repository:** https://github.com/fastify/fast-uri
|
|
@@ -2626,7 +2657,7 @@ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
|
2626
2657
|
PERFORMANCE OF THIS SOFTWARE.
|
|
2627
2658
|
```
|
|
2628
2659
|
|
|
2629
|
-
## ip-address@10.7.
|
|
2660
|
+
## ip-address@10.7.5
|
|
2630
2661
|
|
|
2631
2662
|
- **Licence declared:** `MIT`
|
|
2632
2663
|
- **Repository:** https://github.com/beaugunderson/ip-address
|
|
@@ -4927,7 +4958,7 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
|
4927
4958
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
4928
4959
|
```
|
|
4929
4960
|
|
|
4930
|
-
## qs@6.16.
|
|
4961
|
+
## qs@6.16.1
|
|
4931
4962
|
|
|
4932
4963
|
- **Licence declared:** `BSD-3-Clause`
|
|
4933
4964
|
- **Repository:** https://github.com/ljharb/qs
|
|
@@ -6193,7 +6224,7 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
|
6193
6224
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
6194
6225
|
```
|
|
6195
6226
|
|
|
6196
|
-
## undici@7.
|
|
6227
|
+
## undici@7.30.1
|
|
6197
6228
|
|
|
6198
6229
|
- **Licence declared:** `MIT`
|
|
6199
6230
|
- **Repository:** https://github.com/nodejs/undici
|
|
@@ -6476,7 +6507,7 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
|
6476
6507
|
OTHER DEALINGS IN THE SOFTWARE.
|
|
6477
6508
|
```
|
|
6478
6509
|
|
|
6479
|
-
## zod@4.
|
|
6510
|
+
## zod@4.6.7
|
|
6480
6511
|
|
|
6481
6512
|
- **Licence declared:** `MIT`
|
|
6482
6513
|
- **Repository:** https://github.com/colinhacks/zod
|
package/build-info.json
CHANGED
package/docs/DELIVERY.md
CHANGED
|
@@ -142,7 +142,7 @@ drives the whole delivery side over the ops MCP face — mint its token verb-sco
|
|
|
142
142
|
`sendPending` until a send is confirmed, whichever lane wrote the packet, and `mark_sent` is the only
|
|
143
143
|
thing that clears it. Acknowledging the event instead removes the marker and leaves the run owed, so
|
|
144
144
|
the backstop scan re-arms it and the requester is told again on the next sweep.
|
|
145
|
-
4. For the remaining kinds — `intake-rejected`, `duplicate-skipped`, `late-bind-ack` — route the
|
|
145
|
+
4. For the remaining kinds — `intake-rejected`, `duplicate-skipped`, `pre-run-failed`, `late-bind-ack` — route the
|
|
146
146
|
packet's `text` → **`ack_event(file)`** (idempotent; validated as a bare `*.pending` name). These
|
|
147
147
|
describe something that did not become a run, so there is no run to settle.
|
|
148
148
|
|
|
@@ -188,10 +188,9 @@ double-sends (`.sent` guards it).
|
|
|
188
188
|
## Legacy notes
|
|
189
189
|
|
|
190
190
|
- The `delivered` outbox marker's plain-text body (agent id) predates the JSON packets and is
|
|
191
|
-
kept for compatibility with
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
`_driver/delivery.json`.
|
|
191
|
+
kept for compatibility with `driver/deliver-trigger.sh`, **one deployment's reference wake**. Its units no
|
|
192
|
+
longer ship, and the packaged install runs no outbox watcher: the integrator wakes itself. The script is not
|
|
193
|
+
part of the headless product contract; the full packet is always in `_driver/delivery.json`.
|
|
195
194
|
|
|
196
195
|
## One report per run
|
|
197
196
|
|
|
@@ -244,8 +244,7 @@ Reading order for the phases, with what code decides at each:
|
|
|
244
244
|
by receipt; survivors become a front-matter coverage note, not a halt.
|
|
245
245
|
6. **Frame-diff + bounded reopen** — the blind frame is diffed against the run's own framing;
|
|
246
246
|
directives (including deterministic mechanical form-gap directives) can reopen register and
|
|
247
|
-
source arms once,
|
|
248
|
-
per-directive closure verification. The whole block is non-fatal; unclosed directives demote to
|
|
247
|
+
source arms once, with per-directive closure verification. The whole block is non-fatal; unclosed directives demote to
|
|
249
248
|
disclosed deferrals that later clamp the verdict.
|
|
250
249
|
7. **Placement → register-digest** — both fatal. Every digest pass (fresh, escalation, envelope,
|
|
251
250
|
late-bind, stale-repair) goes through the single `runDigest` chokepoint, which drops stale ledgers,
|
|
@@ -282,7 +282,6 @@ unrecognised policy value that leaves the default behaviour standing.
|
|
|
282
282
|
| `CLEAROTRON_PLAN_DISPATCH` (`0` or `off` disables) | Pure-code provider `executePlan` repairs at fan-in and reopen. **Never silently inert:** every entry in `PROVIDERS` ships an `executePlan` adapter, and `preflightCredentials` refuses the run before any spend under one that does not — a credential is not a capability. §5.4 of [05-config-governance.md](05-config-governance.md) still lists `signa` as the exception to that and has not been updated since its two missing tools were mounted. |
|
|
283
283
|
| `CLEAROTRON_FRAME_REOPEN` (+ `CLEAROTRON_FRAME_REOPEN_MAX`, default 1) | The bounded frame-diff reopen. |
|
|
284
284
|
| `CLEAROTRON_REGISTER_GAP_CLAMP` | The registerGap verdict clamp arm. |
|
|
285
|
-
| `CLEAROTRON_REOPEN_MAX_FETCH` (default 150) | Detail-fetch ceiling inside the reopen closure pass. |
|
|
286
285
|
| `CLEAROTRON_UNREACHABLE_SENIOR` (`open-item` \| `clamp`, default `open-item`) | Policy when a verdict-driving senior right can't be retrieved. |
|
|
287
286
|
|
|
288
287
|
### Payload ceilings
|
|
@@ -374,6 +373,7 @@ cannot be read as one list.
|
|
|
374
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. |
|
|
375
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. |
|
|
376
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). |
|
|
377
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. |
|
|
378
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. |
|
|
379
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. |
|
|
@@ -205,7 +205,7 @@ move once the same window has been read across more runs.
|
|
|
205
205
|
|
|
206
206
|
`CLEAROTRON_PLAN_DISPATCH`, `CLEAROTRON_SATPROBE_CODESIDE`,
|
|
207
207
|
`CLEAROTRON_BAND_TRUTH_GATE` (**never disable in prod — restores the fabrication**),
|
|
208
|
-
`CLEAROTRON_FRAME_REOPEN` (+`CLEAROTRON_FRAME_REOPEN_MAX`=1
|
|
208
|
+
`CLEAROTRON_FRAME_REOPEN` (+`CLEAROTRON_FRAME_REOPEN_MAX`=1),
|
|
209
209
|
`CLEAROTRON_REGISTER_GAP_CLAMP`, `CLEAROTRON_WARM_RETRY`,
|
|
210
210
|
`CLEAROTRON_MODEL_WIRE_CHECK` (fails a turn whose
|
|
211
211
|
provider reports a different model FAMILY than the driver asked for; disarming it silences the refusal
|
|
@@ -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`,
|
|
@@ -20,41 +20,37 @@ directives); `%h` is that account's home. The load-bearing operational fact: the
|
|
|
20
20
|
```mermaid
|
|
21
21
|
flowchart TB
|
|
22
22
|
subgraph VM["Single VM — systemd --user (service account)"]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
PS["profile-service.service<br/>long-running, loopback :18794"]
|
|
30
|
-
GW["integrator agent-platform gateway<br/>(reference integration; comms plane)"]
|
|
31
|
-
DP --> DS
|
|
23
|
+
WK["clearotron-worker.service<br/>simple: node runner.mjs --watch<br/>drains the queue, resumes parked runs"]
|
|
24
|
+
PT["clearotron-portal.service<br/>simple: node portal-service.mjs"]
|
|
25
|
+
MF["clearotron-mcp-face.service<br/>simple: node mcp-server/http-server.mjs"]
|
|
26
|
+
CM["clearotron-client-mcp.service<br/>simple: node mcp-server/http-server-client.mjs"]
|
|
27
|
+
DT["clearotron-deploy.timer<br/>boot+5min, every 1h (optional)"]
|
|
28
|
+
DS["clearotron-deploy.service<br/>oneshot: clearotron update"]
|
|
32
29
|
DT --> DS
|
|
33
|
-
|
|
34
|
-
OT --> OS
|
|
30
|
+
PT --> MF
|
|
35
31
|
end
|
|
36
|
-
Q[("per-agent queue dirs")] -.
|
|
37
|
-
OB[("outbox: runId.pending")] -.watched.-> OP
|
|
32
|
+
Q[("per-agent queue dirs")] -.polled.-> WK
|
|
38
33
|
POOL[("publish pool — CLEAROTRON_REPORTS_DIR<br/>(web-served, group-readable)")]
|
|
39
|
-
|
|
34
|
+
WK --> POOL
|
|
35
|
+
CF["auth proxy + tunnel<br/>(this deployment: Cloudflare)"] --> CADDY["reverse proxy<br/>(this deployment: Caddy)"] --> PT
|
|
40
36
|
CADDY --> POOL
|
|
41
|
-
DS --> POOL
|
|
42
|
-
OS --> GW
|
|
43
37
|
```
|
|
44
38
|
|
|
45
39
|
| Unit | Trigger | Execs | Key settings (why) |
|
|
46
40
|
|---|---|---|---|
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
41
|
+
| `clearotron-worker.service` | long-running (`[Install]`ed) | `node driver/runner.mjs --watch` | `Type=simple`; polls for queued jobs and for parked runs whose window has passed; `Restart=on-failure`, `RestartSec=10`. The one unit that spends |
|
|
42
|
+
| `clearotron-portal.service` | long-running | `node driver/portal-service.mjs` | The UI, the API, the sign-in and the configuration surface; ordered after the MCP face, which it calls |
|
|
43
|
+
| `clearotron-mcp-face.service` | long-running | `node mcp-server/http-server.mjs` | The engine door the portal calls over MCP |
|
|
44
|
+
| `clearotron-client-mcp.service` | long-running | `node mcp-server/http-server-client.mjs` | The door for a company's signed-in people and their access keys |
|
|
45
|
+
| `clearotron-deploy.timer` | boot+5min, every 1h | `clearotron-deploy.service` | Optional: keeps the checkout on `origin/main` |
|
|
46
|
+
| `clearotron-deploy.service` | (timer only) | `clearotron update` | `Type=oneshot`; refuses while a run is queued or in flight and exits 4, which `SuccessExitStatus=4` counts as success, so a busy box skips that firing; `TimeoutStartSec=30min`, so a hung `npm ci` cannot block later firings |
|
|
47
|
+
|
|
48
|
+
Every unit loads `EnvironmentFile=%h/.env`, expands `${CLEAROTRON_CHECKOUT_DIR}` in `ExecStart=` and sets
|
|
49
|
+
`CLEAROTRON_NO_ENV_FILE=1`. `clearotron start --background` installs the four services; the deploy pair is copied by
|
|
50
|
+
hand ([`driver/systemd/README.md`](../../driver/systemd/README.md)). The worker's normal state is `active (running)`.
|
|
51
|
+
|
|
52
|
+
The packaged install runs no outbox watcher: the integrator wakes itself to courier what the outbox holds
|
|
53
|
+
([`docs/DELIVERY.md`](../DELIVERY.md)), and `driver/deliver-trigger.sh` is one agent platform's reference wake.
|
|
58
54
|
|
|
59
55
|
Delivery truth lives in the run dir, not the marker: `status.sendPending` + the `.sent` sentinel
|
|
60
56
|
make wakes idempotent — a lost marker can delay a send (≤50 min), never lose or double it.
|
|
@@ -64,7 +60,7 @@ settle it).
|
|
|
64
60
|
|
|
65
61
|
## Deploy
|
|
66
62
|
|
|
67
|
-
This repository ships the unit files (`driver/systemd/`,
|
|
63
|
+
This repository ships the unit files (`driver/systemd/`, six of them) and a reference delivery wake
|
|
68
64
|
(`driver/deliver-trigger.sh`); it does not ship the script that installs them. Whatever plays that
|
|
69
65
|
role on your host — a dispatcher, a config-management run, a hand-written script — owes the
|
|
70
66
|
properties below, each of which was learned the expensive way:
|
|
@@ -75,21 +71,20 @@ properties below, each of which was learned the expensive way:
|
|
|
75
71
|
deployment would drain plus the run-slot locks, and refuse on any of them. `--override
|
|
76
72
|
"<reason>"` is the operator's deliberate exception; the reason is printed into the deploy output, and a
|
|
77
73
|
blank one exits 2 rather than passing. This step used to be a line of prose asking a human to
|
|
78
|
-
check, which is not a guard on the night it matters. Then **stop the
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
check, which is not a guard on the night it matters. Then **stop the worker** for the deploy
|
|
75
|
+
window and install an **EXIT trap that restarts it on every exit path**, success or abort — trap
|
|
76
|
+
*after* stop, or an early abort leaves the machine silent.
|
|
81
77
|
2. **Skill scanner gate** over `driver/skills/` — the tree the driver actually reads — and over any
|
|
82
78
|
skills tree your integrator carries. Abort on high/critical findings, and if the scanner is
|
|
83
79
|
absent say so loudly rather than passing: a gate that cannot run is not a gate that passed.
|
|
84
80
|
3. **Drift detection on live agent workspaces** — abort on local skill edits rather than promoting
|
|
85
81
|
them; silent auto-promotion twice resurrected intentionally deleted files. A skill relocated into
|
|
86
82
|
the driver tree is relocation, not drift.
|
|
87
|
-
4. **Driver block** — install dependencies, copy the
|
|
88
|
-
install/enable/restart the sibling services (`
|
|
89
|
-
**pre-create the queue dirs and the outbox
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
trigger units.
|
|
83
|
+
4. **Driver block** — install dependencies, copy the unit files + `daemon-reload`,
|
|
84
|
+
install/enable/restart the deployment's sibling services (`client-access`, `client-mcp`), and
|
|
85
|
+
**pre-create the queue dirs and the outbox**.
|
|
86
|
+
5. **Restart the gateway last**, with no live run and the worker stopped, then start the worker
|
|
87
|
+
again.
|
|
93
88
|
|
|
94
89
|
### Never hand-edit (a deploy overwrites these silently)
|
|
95
90
|
|
|
@@ -117,8 +112,8 @@ and the environment file holding the secrets.
|
|
|
117
112
|
|
|
118
113
|
| Surface | What it tells you |
|
|
119
114
|
|---|---|
|
|
120
|
-
| `systemctl --user status
|
|
121
|
-
| `journalctl --user -u
|
|
115
|
+
| `systemctl --user status clearotron-worker clearotron-portal clearotron-mcp-face clearotron-client-mcp clearotron-deploy.timer` | Unit health; the four services read `active (running)` |
|
|
116
|
+
| `journalctl --user -u clearotron-worker.service -f` | Runner notes (stderr): claims, dedup parks, preflight failures, orphan reclaims |
|
|
122
117
|
| Run dir `status.json` / `run.jsonl` | Per-run state + the append-only decision trace; grep keys: `axes`, `profile`, `verdict`, `escalation`, `postponed`, `delivered`, `profile-mismatch` |
|
|
123
118
|
| `_driver/<stage>.jsonl` | Per-attempt telemetry: status, fail token, kill signals, wall, tokens |
|
|
124
119
|
| Queue dirs | Marker states at a glance ([03 §2](03-run-lifecycle.md#2--claim-identity-and-crash-safety)): `.json`, `.processing` (+`.skips` — a growing skip tally is the wedge signature), `.postponed`, `.duplicate`, `.done`, `.failed` |
|
|
@@ -161,7 +156,7 @@ survive until terminal state.
|
|
|
161
156
|
| Run parked with `*.tainted-N` artifacts | Timeout-taint convergence loop ([07 §3](07-quality-and-audit.md#3--completed-coverage-honesty-in-code)) | Let it converge; repeated signature goes terminal honestly |
|
|
162
157
|
| Chat failure ping never arrived for a failed run | By design: nothing here sends. The failure packet IS the notice, and an integrator consumes it | Check `_driver/failure.json` + the outbox lane (the guaranteed notice) |
|
|
163
158
|
| Deploy refused because a run is in flight | The in-flight guard above | Wait for the run. A force-restart of the gateway is not the answer — that is for a wedged gateway |
|
|
164
|
-
| Everything quiet after a deploy abort | Should not happen (EXIT trap restarts triggers) — if it does: `systemctl --user start
|
|
159
|
+
| Everything quiet after a deploy abort | Should not happen (EXIT trap restarts triggers) — if it does: `systemctl --user start clearotron-worker.service` and file it |
|
|
165
160
|
|
|
166
161
|
## Selftest — retired
|
|
167
162
|
|
|
@@ -151,11 +151,7 @@ What makes the coverage statement trustworthy:
|
|
|
151
151
|
|
|
152
152
|
Register budgets deserve one honest sentence: the per-worker call budgets in the skill prose
|
|
153
153
|
(enumerates, phoneme ≤ 5, image ≤ 10) are **observed, prompt-level budgets, not code-enforced
|
|
154
|
-
quotas
|
|
155
|
-
`reopenFetchCeiling` resolves the figure in code, then the reopen prompt builders
|
|
156
|
-
(`buildFrameReopenFollowup`, `buildFrameReopenRetryMessage`) interpolate it into the instruction the
|
|
157
|
-
model reads; nothing counts a run's detail-fetches and nothing refuses the next one. The bound that
|
|
158
|
-
*is* code is the executor's enumerate resource guard (`CLEAROTRON_ENUMERATE_CEILING`): it owns the page
|
|
154
|
+
quotas**. The bound that *is* code is the executor's enumerate resource guard (`CLEAROTRON_ENUMERATE_CEILING`): it owns the page
|
|
159
155
|
loop and returns an honest `incomplete` descriptor for an over-ceiling band rather than a truncated
|
|
160
156
|
one. Calls are metered per run (billing-grade ledger), never hard-capped.
|
|
161
157
|
|
package/driver/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# clearotron-driver
|
|
2
2
|
|
|
3
|
+
## 0.5.0-beta.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fixed: The delivery email for a result the review could not sign off now lists the report's "Only you can close these" items.
|
|
8
|
+
- Fixed: When a clearance fails, its chat notice now goes to the person who asked for it, with a copy to the operator.
|
|
9
|
+
- Fixed: A clearance no longer repeats a finished step because a web lookup inside it timed out.
|
|
10
|
+
- Fixed: A clearance whose register search cannot finish, such as one that keeps timing out, now delivers its report instead of ending with none.
|
|
11
|
+
- Fixed: A search that cannot be shown to have run is now listed as not searched, and the report is still delivered.
|
|
12
|
+
- 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.
|
|
13
|
+
|
|
14
|
+
## 0.5.0-beta.2
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Fixed: Reports no longer show "Nothing found" in "Where it stands" for a country the register could not search; that country is left out.
|
|
19
|
+
- Fixed: A finding registered in several countries now shows under each of them in "Where it stands". Before, only its first country showed it, and the others could read "Nothing found".
|
|
20
|
+
|
|
3
21
|
## 0.5.0-beta.1
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/driver/band-shape.mjs
CHANGED
|
@@ -664,11 +664,12 @@ export function recordGroup({ tier, live, inScope, hits, dominant = false }) {
|
|
|
664
664
|
* EACH GROUP RUNS IN THE ORDER THE OWNER RULED (2026-10-12): a line with the client's goods on a live record
|
|
665
665
|
* in the ordered classes first; then more live records in those classes; then more goods words; then a
|
|
666
666
|
* client market; then more records. Code sorts on fields the records carry and judges nothing. A line
|
|
667
|
-
* carries the goods words it matched,
|
|
667
|
+
* carries the goods words it matched, and not the goods text, which is one lookup away, except a floor in (b):
|
|
668
|
+
* it matched none of the client's words, so it shows the goods the test read, cut short.
|
|
668
669
|
*
|
|
669
670
|
* A line sits in the best group its records in scope reach. `keysOf` gives a record's line and its owner's
|
|
670
671
|
* set-aside key, both read through the positions fold, so a line covers exactly what a row naming one of
|
|
671
|
-
* its records reaches. A line carries the words matched, so nothing on it is cut; the one line that cannot
|
|
672
|
+
* its records reaches. A line carries the words matched, so nothing on it is cut but a (b) floor's goods; the one line that cannot
|
|
672
673
|
* be judged from itself is one the goods test placed while its records carry no goods on file.
|
|
673
674
|
*
|
|
674
675
|
* @param records the band's enumerated records
|
|
@@ -708,6 +709,19 @@ function bindingOffices(territories, code) {
|
|
|
708
709
|
return out;
|
|
709
710
|
}
|
|
710
711
|
|
|
712
|
+
// The goods text the test read for a record: the reader hands it as text, or as one statement per class, each with its
|
|
713
|
+
// text. Then that text cut short for a line.
|
|
714
|
+
const goodsReadText = (g) => (Array.isArray(g) ? g.map((x) => (typeof x === "string" ? x : x?.text)) : [typeof g === "string" ? g : g?.text])
|
|
715
|
+
.filter((t) => typeof t === "string" && t.trim()).join("; ").replace(/\s+/g, " ").trim() || null;
|
|
716
|
+
export const FLOOR_GOODS_SHOWN = 140;
|
|
717
|
+
function cutGoods(text) {
|
|
718
|
+
const t = String(text ?? "");
|
|
719
|
+
if (t.length <= FLOOR_GOODS_SHOWN) return t;
|
|
720
|
+
const head = t.slice(0, FLOOR_GOODS_SHOWN);
|
|
721
|
+
const space = head.lastIndexOf(" ");
|
|
722
|
+
return `${(space > FLOOR_GOODS_SHOWN / 2 ? head.slice(0, space) : head).replace(/[\s,;:.]+$/, "")}…`;
|
|
723
|
+
}
|
|
724
|
+
|
|
711
725
|
export function bandGroups(records, { classify, inScope, goods = null, dominantTerm = "", keysOf = null, markets = [], territories = [], evidenceOf = null } = {}) {
|
|
712
726
|
const fold = (x) => foldDiacritics(String(x ?? "")).toLowerCase();
|
|
713
727
|
const dom = fold(dominantTerm).trim();
|
|
@@ -746,7 +760,8 @@ export function bandGroups(records, { classify, inScope, goods = null, dominantT
|
|
|
746
760
|
const group = outside && !floor ? "rest" : recordGroup({ tier, live, inScope: s, hits, dominant });
|
|
747
761
|
// "On file" is a body `band_record` can open: goods read off the band row decide the group, never that.
|
|
748
762
|
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))
|
|
763
|
+
const rec = { r, tier, hits, words, live, inScope: s, group, outside, floor, onFile, territories: ts, inMarket: ts.some((t) => market.has(t)),
|
|
764
|
+
...(floor ? { read: goodsReadText(g) } : {}) };
|
|
750
765
|
const keys = typeof keysOf === "function" ? keysOf(r) : null;
|
|
751
766
|
const ownerKey = keys?.owner ?? (normKey(r?.owner_name) ? `owner:${normKey(r?.owner_name)}` : `record:${String(r?.record_id ?? "").trim().toLowerCase()}`);
|
|
752
767
|
const key = keys?.line ?? `line:${ownerKey}|${normMark(r?.mark_text) || String(r?.record_id ?? "").trim().toLowerCase()}`;
|
|
@@ -788,6 +803,9 @@ export function bandGroups(records, { classify, inScope, goods = null, dominantT
|
|
|
788
803
|
return { goods_evidence: ev?.state ?? null, ...(ev?.state === "unknown" && ev?.readable == null ? { goods_text: false } : {}) };
|
|
789
804
|
})() : {}),
|
|
790
805
|
floors: l.recs.filter((x) => x.floor).length,
|
|
806
|
+
// A FLOOR IN (b) SHOWS THE GOODS THE TEST READ, cut short: a (b) line matched none of the client's words, so
|
|
807
|
+
// without them a page of floors could not be judged without opening each record.
|
|
808
|
+
...(group === "b" && known ? (() => { const read = l.recs.find((x) => x.floor && x.read)?.read; return read ? { floor_goods: cutGoods(read) } : {}; })() : {}),
|
|
791
809
|
...(known ? {} : { goods_on_file: inGroup.some((x) => x.onFile) }),
|
|
792
810
|
...(offices.size && l.recs.every((x) => x.outside) ? { outside: true } : {}),
|
|
793
811
|
};
|
|
@@ -1109,13 +1127,14 @@ export const PLACEMENT_GROUP_ORDERS = Object.freeze([SORT_THE_NEAR_ONES, ONE_SET
|
|
|
1109
1127
|
// group's identity, members and count stay, and so does the fact that a crowded group prints only its first page.
|
|
1110
1128
|
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.";
|
|
1111
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
|
+
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.";
|
|
1112
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.";
|
|
1113
|
-
export const AN_UNREAD_CROWDS_FLOOR = "
|
|
1114
|
-
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.";
|
|
1115
1134
|
// What placement does with a line of that list before its fate, by its marker (approved wording, 2026-10-19). Placement's
|
|
1116
1135
|
// alone, as its other orders are: every other stage keeps the markers as evidence and is not told of a fate.
|
|
1117
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.';
|
|
1118
|
-
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]);
|
|
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]);
|
|
1119
1138
|
const SHAPE_LISTS = ["a", "b", "c", "d", "rest", "no_goods"];
|
|
1120
1139
|
const pageEveryOtherLine = (g) => `; page every other line with ${groupPageCall(g)}`;
|
|
1121
1140
|
const pageWhereALineNeedsIt = (g) => `; page with ${groupPageCall(g)} only where a line needs reading`;
|
|
@@ -1166,13 +1185,14 @@ function renderGroupsMd(md, groups, rows = []) {
|
|
|
1166
1185
|
if (l.goods_evidence === "complete") return "on file, not readable";
|
|
1167
1186
|
return l.goods_on_file ? "on file, not readable" : "no goods on file";
|
|
1168
1187
|
};
|
|
1169
|
-
const words = (l) => (l.goods_words == null ? unread(l)
|
|
1188
|
+
const words = (l) => (l.goods_words == null ? unread(l)
|
|
1189
|
+
: `${fmtN(l.goods_words.length)} of ${fmtN(total)}${l.goods_words.length ? `: ${l.goods_words.join(", ")}` : ""}${l.floor_goods ? ` «${l.floor_goods}»` : ""}`);
|
|
1170
1190
|
const count = (grp) => `${fmtN(grp.records)} records in ${fmtN(grp.lines)} lines from ${fmtN(grp.owners)} owners`;
|
|
1171
1191
|
const where = (l) => `${l.territories.join(",")}${l.outside ? " (outside the order)" : ""}`;
|
|
1172
1192
|
const row = (l) => `| ${cell(l.mark)} | ${cell(l.owner_name)} | ${cell(l.classes.join(","))} | ${fmtN(l.live)} of ${fmtN(l.records)} | ${cell(where(l))} | ${cell(words(l))} | ${l.floors > 0 ? "yes" : "no"} | ${cell(l.record_id)} |`;
|
|
1173
1193
|
const head = ["| mark | owner | classes | live | territories | goods words | floor | record |", "|---|---|---|---|---|---|---|---|"];
|
|
1174
1194
|
// A crowded group's own words for what it holds, in the crowded-field method's sentence (ruled 2026-10-12).
|
|
1175
|
-
const crowd = { b: "identical marks in the ordered classes
|
|
1195
|
+
const crowd = { b: "identical marks in the ordered classes whose goods name none of the client's product words",
|
|
1176
1196
|
c: "marks of the same family as the mark, or carrying the searched word, for the client's goods", d: "other marks in the ordered classes for the client's goods" };
|
|
1177
1197
|
const firstPage = `The first ${fmtN(groups.first_page)} lines are below`;
|
|
1178
1198
|
for (const g of ["a", "b", "c", "d"]) {
|
|
@@ -1186,7 +1206,8 @@ function renderGroupsMd(md, groups, rows = []) {
|
|
|
1186
1206
|
? `${count(grp)}. ${readA} ${firstPage}${pageEveryOtherLine(g)}. ${FLOORS_BY_NAME_HERE}`
|
|
1187
1207
|
: grp.lines <= READ_ALL_LINES ? `${count(grp)}. Fifty? Read them all.` : `${count(grp)}. ${readA}`)
|
|
1188
1208
|
: crowded
|
|
1189
|
-
? `${count(grp)}. A thousand or more? The field is crowded. These are ${crowd[g]}. ${A_CROWDS_ONE_ROW} ${firstPage}${pageWhereALineNeedsIt(g)}
|
|
1209
|
+
? `${count(grp)}. A thousand or more? The field is crowded. These are ${crowd[g]}. ${A_CROWDS_ONE_ROW} ${firstPage}${pageWhereALineNeedsIt(g)}.${
|
|
1210
|
+
g === "b" ? ` ${A_CROWDS_ROW_COVERS_A_READ_FLOOR} ${pageTheFloorsLeft(g)}` : ""}`
|
|
1190
1211
|
: grp.lines <= READ_ALL_LINES
|
|
1191
1212
|
? `${count(grp)}. Fifty? Read them all.`
|
|
1192
1213
|
: `${count(grp)}. ${SORT_THE_NEAR_ONES}`, "");
|
|
@@ -1210,8 +1231,8 @@ function renderGroupsMd(md, groups, rows = []) {
|
|
|
1210
1231
|
if (groups.no_goods.lines) {
|
|
1211
1232
|
// The lines the goods test could not place, read by the same size rule (ruled 2026-10-12): under a thousand
|
|
1212
1233
|
// records, a fate per line by name, opened where goods are on file; at a thousand or more, the first page,
|
|
1213
|
-
// and one row on the rest whose goods are unreadable in a crowded field. A floor is
|
|
1214
|
-
//
|
|
1234
|
+
// and one row on the rest whose goods are unreadable in a crowded field. A floor is set aside on its goods only
|
|
1235
|
+
// once they are read in full, which unknown goods never are, so a floor here is placed or set aside by name.
|
|
1215
1236
|
const ng = groups.no_goods;
|
|
1216
1237
|
const crowded = ng.records >= groups.crowded_at;
|
|
1217
1238
|
const lead = tested.source === "none"
|
package/driver/close-verify.mjs
CHANGED
|
@@ -62,13 +62,14 @@ export function blockCollapsed(block) {
|
|
|
62
62
|
/**
|
|
63
63
|
* The plan row that answers a minted slice: the slice itself, or, where the fold refused it as already-asked, the
|
|
64
64
|
* row it duplicates. Such a slice is never frozen into the plan and never runs; its question was asked by the twin.
|
|
65
|
-
*
|
|
65
|
+
* So does a spelling the re-sweep left on a list the reading turn read (`counted-on-list`): the list's row holds its
|
|
66
|
+
* count. ONLY THOSE TWO KINDS RESOLVE: an identity collision or a malformed term asked a question nothing else asked.
|
|
66
67
|
* `foldRefusals` maps a refused qid to `{kind, twin}`, as `foldSupplementalEntries` records it. One rule for the
|
|
67
68
|
* directive below and the remedy term ledger (remedy-accounting.mjs), so the two cannot read a slice apart. PURE.
|
|
68
69
|
*/
|
|
69
70
|
export function answeredAs(qid, foldRefusals) {
|
|
70
71
|
const r = foldRefusals?.get?.(qid);
|
|
71
|
-
return r && r.kind === "duplicate-question" && r.twin ? String(r.twin) : qid;
|
|
72
|
+
return r && (r.kind === "duplicate-question" || r.kind === "counted-on-list") && r.twin ? String(r.twin) : qid;
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
/**
|