clearotron 0.4.0-beta.0 → 0.4.0-beta.2
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 +9 -0
- package/INSTALL.md +4 -31
- package/README.md +5 -5
- package/bin/brandowner.mjs +1 -1
- package/bin/onboard.mjs +18 -74
- package/bin/start.mjs +58 -14
- package/bin/update.mjs +10 -2
- package/build-info.json +2 -2
- package/demo/full-country-search/run/_driver/search-policy.json +1 -1
- package/demo/full-country-search/run/status.json +1 -1
- package/demo/global-preliminary-search/run/_driver/search-policy.json +1 -1
- package/demo/global-preliminary-search/run/status.json +1 -1
- package/demo/knockout-search/run/_driver/search-policy.json +1 -1
- package/demo/knockout-search/run/status.json +1 -1
- package/demo/multi-country-focus-search/run/_driver/search-policy.json +1 -1
- package/demo/multi-country-focus-search/run/status.json +1 -1
- package/docs/README.md +1 -0
- package/docs/SECURITY-OWASP.md +298 -0
- package/docs/SECURITY.md +18 -13
- package/docs/architecture/02-architecture.md +5 -5
- package/docs/architecture/03-run-lifecycle.md +28 -29
- package/docs/architecture/04-configuration-reference.md +5 -10
- package/docs/architecture/05-config-governance.md +5 -7
- package/docs/architecture/06-operations-runbook.md +0 -12
- package/docs/architecture/07-quality-and-audit.md +12 -13
- package/docs/architecture/09-security-and-data.md +9 -4
- package/driver/CHANGELOG.md +70 -0
- package/driver/ask-ledger.mjs +4 -2
- package/driver/authority-trees.mjs +90 -8
- package/driver/band-shape.mjs +13 -13
- package/driver/blind-frame-model.mjs +1 -1
- package/driver/case-law-ledger.mjs +19 -3
- package/driver/claim-liveness.mjs +30 -1
- package/driver/clearance-variants-record.mjs +10 -3
- package/driver/common-law-receipts.mjs +64 -14
- package/driver/commonlaw-carry.mjs +2 -2
- package/driver/compare.mjs +2 -2
- package/driver/connotation-search.mjs +33 -104
- package/driver/contract-arm2-baseline.json +6 -5
- package/driver/contract-e3-backlog.mjs +62 -62
- package/driver/contract-vocabulary.mjs +38 -31
- package/driver/coverage-form-io.mjs +13 -1
- package/driver/coverage-form.mjs +42 -4
- package/driver/coverage-ledger.mjs +3 -5
- package/driver/cross-check-wait.mjs +43 -0
- package/driver/declination-call.mjs +34 -10
- package/driver/declination-tool.mjs +27 -15
- package/driver/deferral-row.mjs +139 -0
- package/driver/degraded-parts.mjs +377 -0
- package/driver/deliver-trigger.sh +1 -1
- package/driver/demo-container.mjs +4 -1
- package/driver/dev-portal.mjs +5 -5
- package/driver/dispatch-record.mjs +2 -2
- package/driver/driver.config.mjs +193 -96
- package/driver/e2e/README.md +1 -1
- package/driver/effective-scope.mjs +58 -2
- package/driver/engine/anthropic-agent.mjs +103 -27
- package/driver/engine/auth.mjs +2 -2
- package/driver/engine/child-record.mjs +11 -0
- package/driver/engine/cli-version.mjs +4 -3
- package/driver/engine/common.mjs +17 -2
- package/driver/engine/deny-authority-write.mjs +3 -1
- package/driver/engine/engine-env.mjs +174 -0
- package/driver/engine/engine-spawn.mjs +179 -0
- package/driver/engine/mcp/band-server.mjs +1 -1
- package/driver/engine/mcp/clarivate-server.mjs +2 -1
- package/driver/engine/mcp/codex-config.mjs +112 -3
- package/driver/engine/mcp/corsearch-server.mjs +1 -0
- package/driver/engine/mcp/declination-server.mjs +9 -8
- package/driver/engine/mcp/dispositions-server.mjs +2 -1
- package/driver/engine/mcp/euipo-server.mjs +1 -0
- package/driver/engine/mcp/fetch-server.mjs +14 -11
- package/driver/engine/mcp/free-tier-server.mjs +1 -1
- package/driver/engine/mcp/gather-config.mjs +23 -11
- package/driver/engine/mcp/perplexity-server.mjs +97 -17
- package/driver/engine/mcp/probe-server.mjs +43 -21
- package/driver/engine/mcp/public-fetch.mjs +195 -0
- package/driver/engine/mcp/recording-server.mjs +79 -14
- package/driver/engine/mcp/signa-server.mjs +1 -0
- package/driver/engine/mcp/supplemental.mjs +14 -14
- package/driver/engine/mcp/unit-note-server.mjs +50 -1
- package/driver/engine/mcp/uspto-local-server.mjs +1 -0
- package/driver/engine/openai-agent.mjs +109 -28
- package/driver/engine/probe.mjs +152 -19
- package/driver/enqueue-schema.mjs +5 -5
- package/driver/feedback-issues.mjs +1 -1
- package/driver/feedback-store.mjs +1 -1
- package/driver/findings-model.mjs +67 -16
- package/driver/flag-snapshot.mjs +1 -1
- package/driver/floor-duty.mjs +23 -7
- package/driver/form-neighbourhood.mjs +52 -17
- package/driver/frame-diff-model.mjs +3 -3
- package/driver/framework-method.mjs +304 -0
- package/driver/gateway.mjs +49 -21
- package/driver/hand-off-exits.mjs +129 -0
- package/driver/jx-lanes.mjs +1 -1
- package/driver/jx.mjs +8 -5
- package/driver/knockout-assess-record.mjs +7 -1
- package/driver/knockout-frame-record.mjs +96 -34
- package/driver/knockout-review-record.mjs +7 -2
- package/driver/log.mjs +2 -2
- package/driver/matter-frame-record.mjs +31 -2
- package/driver/methodology-witness.mjs +6 -3
- package/driver/named-band.mjs +1 -1
- package/driver/owner-use-check.mjs +45 -10
- package/driver/package.json +1 -1
- package/driver/partial-payload-baseline.json +4 -0
- package/driver/phase0.mjs +2 -2
- package/driver/pipeline-knockout.mjs +256 -129
- package/driver/pipeline.mjs +417 -614
- package/driver/placement-carry.mjs +22 -3
- package/driver/placement-form-io.mjs +18 -4
- package/driver/placement-form.mjs +53 -4
- package/driver/placement-union.mjs +32 -5
- package/driver/portal-report.mjs +3 -3
- package/driver/portal-service.mjs +6 -5
- package/driver/portal-static.mjs +11 -18
- package/driver/predelivery-lint.mjs +10 -10
- package/driver/profile-page.html +5 -5
- package/driver/profiles.mjs +5 -5
- package/driver/progress.mjs +6 -4
- package/driver/provider-usage.mjs +25 -2
- package/driver/publish/index.mjs +97 -25
- package/driver/publish/knockout.mjs +136 -14
- package/driver/publish/profiles-page.mjs +2 -0
- package/driver/publish/publish-inputs.mjs +10 -4
- package/driver/publish/render-knockout.mjs +48 -21
- package/driver/publish/render.mjs +13 -8
- package/driver/publish/report-data.mjs +2 -0
- package/driver/publish/search-depth.mjs +77 -4
- package/driver/publish/templates/report.css +1 -1
- package/driver/publish/xlsx.mjs +34 -15
- package/driver/reasoning-tripwires.mjs +2 -160
- package/driver/recall-receipt.mjs +27 -0
- package/driver/recall-reconciliation.mjs +1 -1
- package/driver/record-carry.mjs +14 -14
- package/driver/recording-agreement.mjs +2 -2
- package/driver/reference-score.mjs +169 -47
- package/driver/register-count.mjs +61 -4
- package/driver/register-digest-record.mjs +14 -1
- package/driver/register-plan.mjs +161 -80
- package/driver/register-records.mjs +37 -4
- package/driver/registration-scripts.mjs +22 -0
- package/driver/registry-fidelity.mjs +4 -4
- package/driver/repair-composers.mjs +23 -7
- package/driver/repair-contract.mjs +1 -1
- package/driver/repairs.mjs +5 -2
- package/driver/reviewer-open-points.mjs +33 -0
- package/driver/rule-shape.mjs +8 -8
- package/driver/run-economics.mjs +3 -3
- package/driver/run-integrity.mjs +2 -2
- package/driver/runner.mjs +17 -5
- package/driver/scope-facts.mjs +89 -12
- package/driver/score-redaction.mjs +247 -0
- package/driver/screen-gate.mjs +1 -1
- package/driver/search-policy.mjs +17 -0
- package/driver/skills/README.md +2 -2
- package/driver/skills/blind-frame/SKILL.md +2 -2
- package/driver/skills/clearance-common-law/SKILL.md +29 -36
- package/driver/skills/clearance-common-law/perplexity-prompts.md +8 -30
- package/driver/skills/clearance-register/SKILL.md +15 -16
- package/driver/skills/clearance-register/digest.md +15 -31
- package/driver/skills/clearance-register/register-recipes.md +14 -56
- package/driver/skills/clearance-register/unit.md +37 -48
- package/driver/skills/clearance-search/SKILL.md +6 -8
- package/driver/skills/clearance-search/delivery-contract.md +0 -7
- package/driver/skills/clearance-search/firm-wide-reasoning.md +5 -4
- package/driver/skills/clearance-search/phase2-execution.md +5 -6
- package/driver/skills/clearance-search/report-prose.md +5 -5
- package/driver/skills/clearance-search/risk-framework-triage.md +2 -2
- package/driver/skills/clearance-search/synthesis-rules.md +9 -9
- package/driver/skills/clearance-search/template-formatting.md +2 -2
- package/driver/skills/clearance-variants/SKILL.md +8 -8
- package/driver/skills/clearance-variants/transliteration-scripts.md +2 -2
- package/driver/skills/frame-diff/SKILL.md +2 -2
- package/driver/skills/knockout-assess/SKILL.md +40 -10
- package/driver/skills/knockout-frame/SKILL.md +42 -10
- package/driver/skills/matter-frame/SKILL.md +12 -5
- package/driver/skills/matter-frame/watchlist-reference.md +1 -1
- package/driver/skills/narrative-refutation/SKILL.md +5 -3
- package/driver/skills/placement-inquiry/SKILL.md +5 -1
- package/driver/stage-context.mjs +27 -6
- package/driver/stages-knockout.mjs +92 -79
- package/driver/stages.mjs +111 -68
- package/driver/stray-artifacts.mjs +9 -5
- package/driver/suite-census.json +696 -150
- package/driver/synthesis-record.mjs +26 -4
- package/driver/systemd/README.md +5 -6
- package/driver/systemd/clearotron-client-mcp.service +24 -0
- package/driver/systemd/clearotron-mcp-face.service +24 -0
- package/driver/systemd/clearotron-portal.service +24 -0
- package/driver/systemd/clearotron-worker.service +29 -0
- package/driver/tokens.mjs +10 -9
- package/driver/turnaround-bands.mjs +1 -1
- package/driver/unit-inventory.mjs +24 -43
- package/driver/variant-manifest-model.mjs +22 -3
- package/driver/verify-knockout.mjs +163 -4
- package/driver/verify.mjs +42 -16
- package/driver/web-grid.mjs +150 -0
- package/driver/whatif-memo-run.mjs +1 -1
- package/driver/withheld-families.mjs +113 -4
- package/driver/worker-heartbeat.mjs +13 -2
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/README.md +1 -1
- package/mcp-server/lib/knockout.mjs +1 -1
- package/mcp-server/lib/ops.mjs +10 -10
- package/mcp-server/lib/scrub.mjs +1 -1
- package/mcp-server/lib/trace.mjs +3 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +3 -3
- package/package.json +1 -1
- package/portal-ui/dist/assets/{index-DVtz44vH.js → index-BPAUjcI0.js} +3 -2
- package/portal-ui/dist/assets/{index-5CCwiJG7.css → index-D2wrw9cH.css} +18 -2
- package/portal-ui/dist/assets/plus-jakarta-sans-BUCHxqJ-.woff2 +0 -0
- package/portal-ui/dist/index.html +2 -15
- package/portal-ui/package.json +1 -1
- package/providers/_shared/README.md +1 -1
- package/providers/_shared/answer-memory.mjs +199 -0
- package/providers/_shared/enumerate.mjs +155 -50
- package/providers/_shared/execute-plan.mjs +62 -16
- package/providers/_shared/ledger-path.mjs +1 -1
- package/providers/_shared/ledger.mjs +48 -6
- package/providers/_shared/plan-guards.mjs +7 -0
- package/providers/_shared/script-form.mjs +24 -5
- package/providers/_shared/term-shape.mjs +5 -5
- package/providers/clarivate/src/capabilities.js +11 -0
- package/providers/clarivate/src/core.js +193 -18
- package/providers/corsearch/README.md +1 -2
- package/providers/corsearch/src/core.js +2 -2
- package/providers/jx-subclass/lookup.mjs +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/README.md +8 -7
- package/providers/oauth-mcp-bridge/bridge.mjs +21 -5
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/oauth-mcp-bridge/systemd/courtlistener-mcp.service +1 -1
- package/providers/oauth-mcp-bridge/warm-server.mjs +16 -5
- package/providers/perplexity/src/core.js +246 -21
- package/providers/signa/src/capabilities.js +24 -0
- package/providers/signa/src/core.js +93 -25
- package/scripts/README.md +2 -1
- package/scripts/added-reference-check.mjs +2 -1
- package/scripts/backup-recall-stores.mjs +5 -5
- package/scripts/demo-evidence.mjs +2 -1
- package/scripts/deprecate-below.mjs +114 -2
- package/scripts/drive-env-check.mjs +2 -1
- package/scripts/e2e-first-time.mjs +469 -0
- package/scripts/e2e-scenario-ops.mjs +441 -0
- package/scripts/e2e.mjs +104 -16
- package/scripts/env-audit.mjs +8 -0
- package/scripts/freeze-example-run.mjs +7 -7
- package/scripts/generated-files-are-current.mjs +2 -1
- package/scripts/hand-off-exits-probe.mjs +75 -0
- package/scripts/import-cycle-check.mjs +2 -1
- package/scripts/merge-shape-check.mjs +2 -1
- package/scripts/package-size-budget.mjs +2 -1
- package/scripts/record-carry-probe.mjs +1 -1
- package/scripts/release-entry-catch-up.mjs +77 -8
- package/scripts/release-install-check.mjs +4 -1
- package/scripts/release-note-required.mjs +139 -9
- package/scripts/release-rehearsal-version.mjs +60 -0
- package/scripts/release-sbom.mjs +104 -0
- package/scripts/release-visible-check.mjs +7 -5
- package/scripts/render-check.mjs +5 -2
- package/scripts/report-offline-render-check.mjs +169 -0
- package/scripts/score.mjs +79 -8
- package/scripts/strip-titles-and-attributions.mjs +2 -1
- package/scripts/strip-tracker-citations.mjs +2 -1
- package/scripts/test-full.mjs +2 -1
- package/scripts/test-run.mjs +10 -3
- package/scripts/third-party-notices.mjs +3 -1
- package/scripts/travelling-predicates.mjs +1 -1
- package/scripts/writing-standard-check.mjs +2 -1
- package/shared/brand-fonts.mjs +59 -0
- package/shared/brand.mjs +6 -6
- package/shared/browser-temp-root.mjs +3 -2
- package/shared/doctrine-overlay.mjs +1 -1
- package/shared/driver-dir.mjs +47 -11
- package/shared/fonts/OFL-fira-code.txt +93 -0
- package/shared/fonts/OFL-plus-jakarta-sans.txt +93 -0
- package/shared/fonts/README.md +37 -0
- package/shared/fonts/fira-code.woff2 +0 -0
- package/shared/fonts/plus-jakarta-sans.woff2 +0 -0
- package/shared/names-in-force.mjs +2 -0
- package/shared/npm-cli.mjs +23 -0
- package/shared/os-advice.mjs +14 -2
- package/shared/path-seps.mjs +42 -0
- package/shared/process-table.mjs +71 -3
- package/shared/reference-guard-classes.mjs +30 -6
- package/shared/root-doc-commands.mjs +6 -2
- package/shared/running-start.mjs +24 -7
- package/shared/scope.mjs +2 -2
- package/shared/wsl.mjs +1 -1
- package/driver/known-conflicts.mjs +0 -327
|
@@ -164,7 +164,7 @@ structural, or dev seam); [dev] = dev/test seam, never set in prod.
|
|
|
164
164
|
`CLEAROTRON_OPENAI_MODEL_CHEAP` (all gpt-5.6-sol),
|
|
165
165
|
`CLEAROTRON_CLAUDE_PATH` (claude on PATH, then the copy Clearotron installed),
|
|
166
166
|
`CLEAROTRON_SYNTHESIS_MODEL` (opus), `CLEAROTRON_KNOCKOUT_MODEL` (opus),
|
|
167
|
-
`
|
|
167
|
+
`CLEAROTRON_MAX_BUDGET_USD` (unset).
|
|
168
168
|
|
|
169
169
|
The Claude program's own cloud settings — `CLAUDE_CODE_USE_FOUNDRY`, `CLAUDE_CODE_USE_VERTEX` and
|
|
170
170
|
`CLAUDE_CODE_USE_BEDROCK`, each cloud's own settings, the gateway pair and the model pins — are the
|
|
@@ -206,7 +206,7 @@ move once the same window has been read across more runs.
|
|
|
206
206
|
`CLEAROTRON_PLAN_DISPATCH`, `CLEAROTRON_SATPROBE_CODESIDE`,
|
|
207
207
|
`CLEAROTRON_BAND_TRUTH_GATE` (**never disable in prod — restores the fabrication**),
|
|
208
208
|
`CLEAROTRON_FRAME_REOPEN` (+`CLEAROTRON_FRAME_REOPEN_MAX`=1, `CLEAROTRON_REOPEN_MAX_FETCH`=150),
|
|
209
|
-
`CLEAROTRON_REGISTER_GAP_CLAMP`, `
|
|
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
|
|
212
212
|
and never the record: `modelActual`/`modelMismatch` keep landing on every dispatch row), `CLEAROTRON_FORM_REPAIR`
|
|
@@ -277,8 +277,8 @@ edited — that box runs pre-rebuild code and genuinely still sets it.
|
|
|
277
277
|
|
|
278
278
|
### 5.7 Delivery & comms — T3/T4
|
|
279
279
|
|
|
280
|
-
`CLEAROTRON_DEFAULT_AGENT` (
|
|
281
|
-
|
|
280
|
+
`CLEAROTRON_DEFAULT_AGENT` (localagent),
|
|
281
|
+
`CLEAROTRON_AGENT_WHATSAPP`, `CLEAROTRON_OUTBOX_BACKOFF_BASE_SEC` (60) / `CLEAROTRON_OUTBOX_BACKOFF_CAP_SEC` (900) /
|
|
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).
|
|
@@ -456,9 +456,7 @@ Dev cockpit: `PORTAL_PORT`, `PORTAL_HOST`.
|
|
|
456
456
|
|
|
457
457
|
Diagnostics and fixtures: `CLEAROTRON_DUMP_JSON`,
|
|
458
458
|
`CLEAROTRON_REPLAY_SNAPSHOT`, `CLEAROTRON_REPLAY_ROOTS`, `CLEAROTRON_JX_FIXTURES`,
|
|
459
|
-
`CLEAROTRON_KNOCKOUT_SWEEP_FIXTURES`, `SIGNA_FIXTURES_DIR
|
|
460
|
-
`CLAWDI_SIGNA_FIXTURES_DIR` — `providers/signa/src/core.js` reads the plain name first and falls back
|
|
461
|
-
to the prefixed one, so an operator who sets only the alias must be able to find it here),
|
|
459
|
+
`CLEAROTRON_KNOCKOUT_SWEEP_FIXTURES`, `SIGNA_FIXTURES_DIR`,
|
|
462
460
|
`PORT` (smoke test), and the `MOCK_*` fixture vars. Those are part of
|
|
463
461
|
the harness-only set `scripts/env-audit.mjs` counts separately and this register deliberately omits.
|
|
464
462
|
|
|
@@ -110,18 +110,6 @@ and the environment file holding the secrets.
|
|
|
110
110
|
- **A scheduled gateway restart** (chat-channel session and plugin-loader hygiene) should *defer*
|
|
111
111
|
off live work rather than skip: wait while a run is live, up to a bound, then restart anyway.
|
|
112
112
|
`XDG_RUNTIME_DIR` must be set for `systemctl --user` to work from cron.
|
|
113
|
-
- **Pin the agent id before upgrading an install made before 0.2.2.** The default agent id changed
|
|
114
|
-
from `clawdi` to `localagent`, and that id is a path segment: runs live under
|
|
115
|
-
`<workspaceRoot>/workspace-<agent>/studio/clearance-search/`. An install that never set one starts
|
|
116
|
-
reading an empty workspace, and empty reads as "no runs" rather than as an error. Set **both**
|
|
117
|
-
variables in the environment file — the gather servers read their own:
|
|
118
|
-
|
|
119
|
-
```
|
|
120
|
-
CLEAROTRON_DEFAULT_AGENT=clawdi
|
|
121
|
-
CLEAROTRON_GATHER_AGENT=clawdi
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
An install that already sets them is unaffected, and a fresh install needs neither.
|
|
125
113
|
- **Integrator-platform upgrades** go through that platform's own guarded upgrade path — dry-run,
|
|
126
114
|
exact-version confirm, stop → update → start. Never `npm i -g` / `pnpm add -g` directly.
|
|
127
115
|
|
|
@@ -92,9 +92,15 @@ under-grading ships a wrong CLEAR. The reviewer's context includes code-computed
|
|
|
92
92
|
and a mandatory plan-execution check. CONDITIONAL/BLOCKING triggers corrective re-synthesis, a
|
|
93
93
|
freshness gate proving the named corrections actually reached `findings.json`, and a warm verdict
|
|
94
94
|
re-check that can only fall back to the *entry* verdict. A terminal BLOCKING first gets the
|
|
95
|
-
degenerate-artifact refusal (a "BLOCKING" with zero cited defects is re-asked fresh, once),
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
degenerate-artifact refusal (a "BLOCKING" with zero cited defects is re-asked fresh, once), and
|
|
96
|
+
then the run **delivers**. Ruling 2026-08-26, verbatim: "Deliver always, with open points printed.
|
|
97
|
+
The refusal on a blocking review goes." The corrective ladder is still the fix arm and still runs
|
|
98
|
+
first; what changed is only what happens once it is exhausted. "Never ships" and "never ships
|
|
99
|
+
*looking finished*" were treated as one requirement and are two — only the second was ever
|
|
100
|
+
load-bearing, and it is met by the open-points section being code-built from the review. Ruling
|
|
101
|
+
2026-09-24 settled where that section goes: reviewer notes never reach the client page, and a
|
|
102
|
+
report the reviewer still refuses ships with its rating and nothing added, the points recorded
|
|
103
|
+
beside the review for the reviewing lawyer. The reviewer's verdict is treated as documented-noisy;
|
|
98
104
|
that is precisely why every *honesty* signal below derives from ledgers and receipts, not review
|
|
99
105
|
prose.
|
|
100
106
|
|
|
@@ -138,16 +144,10 @@ What makes the coverage statement trustworthy:
|
|
|
138
144
|
- **The clamps** (`applyCoverageFloor`) only ever raise CLEAR → CONDITIONAL, never lower, never
|
|
139
145
|
manufacture BLOCKING: live condition actions, the lawyer's own `coverage_judgment.sufficient:
|
|
140
146
|
false` (sufficiency is judgment's call — code only carries it), frame residuals, register gaps
|
|
141
|
-
(deferred rows ∪ taint axes
|
|
142
|
-
self-report), and in-window deadlines delivered without their dates. The verdict sidecar
|
|
147
|
+
(deferred rows ∪ taint axes — independent of the model's self-report). The verdict sidecar
|
|
143
148
|
(`_driver/verdict.json`) is then the single label authority for every surface; one shared
|
|
144
149
|
predicate keeps the validator and the auto-correct judging "unconditional proceed" with the same
|
|
145
150
|
eyes.
|
|
146
|
-
- **Recall memory**: confirmed conflicts persist per mark in a workspace store
|
|
147
|
-
(`_known-conflicts/`, rows are only added — never deleted or rewritten, save the machine-provenance
|
|
148
|
-
`terminal` a delivered run may upgrade — human edits always win, non-Latin marks handled); the next run
|
|
149
|
-
folds them in as deterministic plan probes, and a prior-confirmed conflict that neither resurfaces
|
|
150
|
-
nor gets a recorded justification is a *material regression* that clamps.
|
|
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
|
|
@@ -194,9 +194,8 @@ pass over inherited artifacts is visible as such.
|
|
|
194
194
|
`reasoning-tripwires.mjs` is the mechanical net under principles held holistically in the skills:
|
|
195
195
|
recall floor (a live identical registration dropped in the noise rows but not carried), seed
|
|
196
196
|
neutrality (upstream artifacts must state facts, never grades), probative grading, matrix ceilings
|
|
197
|
-
(legacy scale only), status honesty (clean headline over material gaps), deadline urgency
|
|
198
|
-
|
|
199
|
-
recall regression. All of it lands in `_driver/reasoning-integrity.json` and surfaces on the audit
|
|
197
|
+
(legacy scale only), status honesty (clean headline over material gaps), deadline urgency,
|
|
198
|
+
unresolved disagreements, orphan register findings, uncross-checked demotions. All of it lands in `_driver/reasoning-integrity.json` and surfaces on the audit
|
|
200
199
|
workbook and quality pages — never as a banner on the rendered report.
|
|
201
200
|
Two of these pure functions *also* feed the verdict clamps (register gap, deadline gap) — but via
|
|
202
201
|
the clamp path on ledger facts, never as a tripwire "failure". `rule-shape.mjs` (rating decided by
|
|
@@ -3,10 +3,15 @@
|
|
|
3
3
|
> Part of the architecture pack (`docs/architecture/`). The driver's module tree and the headless
|
|
4
4
|
> integrator contract are in [`driver/README.md`](../../driver/README.md).
|
|
5
5
|
|
|
6
|
-
The security posture in
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
The security posture in brief: **nothing listens on the network except loopback services behind an
|
|
7
|
+
authenticated tunnel. A stage on Claude is offered no tool that runs a command. A stage on Codex runs its
|
|
8
|
+
commands inside Codex's own sandbox, or with the permissions of the account Clearotron runs as where a
|
|
9
|
+
machine sets the bypass. The process that runs everything else is ordinary code, not an agent. Each
|
|
10
|
+
stage's AI program starts with a named list of settings, and the key that signs access keys is handed
|
|
11
|
+
only to the portal and the two doors. On Claude, a stage's file tools can read nothing outside its run folder, its
|
|
12
|
+
instruction folders and any folder the machine's own Claude settings add. On Codex with its sandbox on,
|
|
13
|
+
a stage's commands can read nothing outside its run folder, its instruction folders, the temporary
|
|
14
|
+
folders, and the system and program files a command needs to run.**
|
|
10
15
|
|
|
11
16
|
## Trust boundaries
|
|
12
17
|
|
package/driver/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# clearotron-driver
|
|
2
2
|
|
|
3
|
+
## 0.4.0-beta.2
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- New: A knockout search now also asks whether each name is already in use in the client's field, such as a game character or a drink.
|
|
8
|
+
- New: Searching by judgment.
|
|
9
|
+
|
|
10
|
+
This release changes how a clearance decides what to search. Until now the engine followed fixed rules: a set number of spellings, a fixed list of stores, a stop after the first identical mark. Fixed rules are fast and cheap, but they miss things. On a recent matter, a lawyer's review found marks the engine had counted but never read, and others it had raised that did not matter.
|
|
11
|
+
|
|
12
|
+
So the approach changes. Wherever the engine holds a pile of results, it now looks at what is there and decides what a lawyer would raise. It carries that forward and writes down what it set aside, and why. Nothing is dropped silently. Every wider search, every spelling set aside and every store left out appears in the audit workbook with its reason.
|
|
13
|
+
|
|
14
|
+
What you will notice:
|
|
15
|
+
- Searches widen where it could change the advice, and narrow where it cannot.
|
|
16
|
+
- Near spellings no buyer would confuse with the mark are set aside, with the reason recorded.
|
|
17
|
+
- Searches in other scripts run only in markets that file marks in that script.
|
|
18
|
+
- A large company's register is read for the marks that share the searched name. The rest is counted, not fetched.
|
|
19
|
+
- Marketplace searches cover the stores that sell the client's goods, and say which stores were left out and why.
|
|
20
|
+
- The report no longer opens with the internal reviewer's notes. They reach the reviewing lawyer separately.
|
|
21
|
+
- A large marketplace search no longer fails because its results were too big to return in one piece.
|
|
22
|
+
- Where a company's framework rates through named inputs, the report shows them beside each band.
|
|
23
|
+
|
|
24
|
+
A word on regressions. While testing this line we found two. A crowded search failed before delivering its report, and a knockout's ratings moved one step away from the lawyer's. Both were caught by running the same matters against a lawyer's answers, and both are fixed here. Every build is now tested that way before it ships. We are tuning for three things at once: a report that is right the first time, delivered fast, at a sensible cost. They pull against each other, and each release is our best current balance.
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- For operators: on the Anthropic engine, a search can no longer read the install's settings or other private files.
|
|
29
|
+
- For operators: stages on the Anthropic engine can no longer run commands on the server; every tool that runs a command is removed from them.
|
|
30
|
+
- Fixed: In a crowded field, the search now reads the spellings a lawyer would raise, rather than the ones that fit under a record limit.
|
|
31
|
+
- Fixed: When a knockout's search for filings fails, the report and the audit workbook no longer show the raw error.
|
|
32
|
+
- Fixed: On Windows, a failed finding card no longer shows as covered in the audit workbook.
|
|
33
|
+
- Fixed: When a search could not be made, the report says so instead of giving a wrong reason.
|
|
34
|
+
- Fixed: A register search limited to the client's goods now searches every other office when one office cannot filter by goods.
|
|
35
|
+
- Fixed: a right the report keeps is no longer dropped because other registrations of the same right were ruled out.
|
|
36
|
+
- Fixed: A knockout search no longer drops a store listing or fan wiki page its web search found; the rating now reads every result.
|
|
37
|
+
- Fixed: A knockout's audit workbook also lists a part that failed as a whole, such as its filings listing or plain-language review. A filing shown without its link now says why.
|
|
38
|
+
- Fixed: on a Mac, `clearotron start` no longer offers to run in the background, which only works on Linux.
|
|
39
|
+
- Fixed: on a Mac, a search step could write into Clearotron's own protected folders by naming them in a different letter case. Those writes are now refused.
|
|
40
|
+
- Fixed: A register search that times out across many countries is asked again in smaller parts; one that still times out is reported as incomplete.
|
|
41
|
+
- Fixed: on the OpenAI engine, Codex's own sandbox no longer refuses the register searches a clearance runs.
|
|
42
|
+
- For operators: on the OpenAI engine with Codex's sandbox on, the commands a search runs can no longer read the install's settings or other private files.
|
|
43
|
+
- Fixed: the audit workbook's What was searched tab no longer shows a web or marketplace search that found similar listings as clean.
|
|
44
|
+
- Fixed: Worldwide searches are no longer narrowed to an account's default territories, and results too crowded to read in full are no longer reported clean.
|
|
45
|
+
- Fixed: A report no longer offers a native-language investigation to a client who ordered one, and says when that investigation was not completed.
|
|
46
|
+
- Fixed: When a knockout's lookup of a filing owner fails, its read now says the lookup did not answer, instead of saying it found nothing.
|
|
47
|
+
- New: Clearotron runs natively on Windows, from PowerShell: no WSL2, no Git and no administrator rights.
|
|
48
|
+
- Fixed: a full country search's report no longer says court decisions were searched when that search failed or its source was down.
|
|
49
|
+
- New: Global preliminary, multi-country and full country searches now search the web in more depth and keep more results from each search.
|
|
50
|
+
- Fixed: opening a report no longer contacts any font service; the report carries its typeface, now Plus Jakarta Sans, inside itself.
|
|
51
|
+
- Fixed: searches no longer reuse earlier results.
|
|
52
|
+
- Fixed: skipped searches are no longer shown as unfinished in the report.
|
|
53
|
+
- For operators: each stage's AI program now receives only the settings it needs, and never the key that signs access keys.
|
|
54
|
+
- Fixed: The audit workbook now lists, on Coverage & gaps, any part of a report that could not be completed on the run. A knockout's Audit Trail also lists an owner lookup that got no answer.
|
|
55
|
+
- Fixed: on the OpenAI engine, a Full country search now reads case law from CourtListener and Legal Data Hunter, not EUR-Lex alone.
|
|
56
|
+
- Fixed: setup, `clearotron doctor` and the start of every search now check that the engine can write a file where a search writes its results.
|
|
57
|
+
- New: The meaning and reputation search now asks the questions the matter's framing names, in the languages whose markets matter. Where the framing names none, the report says no meaning search ran.
|
|
58
|
+
- For operators: the old default agent name is gone from the product. An install upgrading from a version before 0.2.2 must set its own agent name to keep seeing its earlier runs.
|
|
59
|
+
- Fixed: The operator tool no longer accepts instructions, which never reached the run; a call sending them is refused.
|
|
60
|
+
- Fixed: on the OpenAI engine, the tool that reads web pages now refuses addresses on the server's own network, such as cloud metadata addresses.
|
|
61
|
+
- For operators: the background worker no longer receives the key that signs access keys. Run `clearotron start --background` once to update an installed worker.
|
|
62
|
+
|
|
63
|
+
## 0.4.0-beta.1
|
|
64
|
+
|
|
65
|
+
### Patch Changes
|
|
66
|
+
|
|
67
|
+
- Fixed: Clarivate clearances no longer count look-alike spellings that mix Latin with Greek or Cyrillic letters as searches that could not be completed.
|
|
68
|
+
- For operators: Clarivate clearances no longer fetch a record, or repeat a count, search or owner lookup, that the same run already made.
|
|
69
|
+
- For operators: Knockouts on Signa and Clarivate take their name and close-variation counts from the listing, so each is asked of the register once.
|
|
70
|
+
- Fixed: Knockouts on the Signa register now say how many register hits the listed filings were drawn from.
|
|
71
|
+
- Fixed: Clearances on the Signa register no longer search look-alike spellings that mix Latin with Greek or Cyrillic letters; those searches found only unrelated marks.
|
|
72
|
+
|
|
3
73
|
## 0.4.0-beta.0
|
|
4
74
|
|
|
5
75
|
### Minor Changes
|
package/driver/ask-ledger.mjs
CHANGED
|
@@ -179,7 +179,7 @@ function recallEnding(name, qid, join, ts) {
|
|
|
179
179
|
if (discharges) return { ending, handoff: null };
|
|
180
180
|
return {
|
|
181
181
|
ending: null,
|
|
182
|
-
handoff: `the probe ran and ${why} — a recall probe discharges on what came back, not on having run
|
|
182
|
+
handoff: `the probe ran and ${why} — a recall probe discharges on what came back, not on having run. `
|
|
183
183
|
+ `Ships OPEN in the audit's ask ledger for the reviewing lawyer.`,
|
|
184
184
|
};
|
|
185
185
|
}
|
|
@@ -428,6 +428,8 @@ export function deriveAsks({
|
|
|
428
428
|
}
|
|
429
429
|
|
|
430
430
|
// 9 ── cross-checks: the common-law→register recovery nets (xcheck + recall) — directives carry
|
|
431
|
+
// (the recall receipt exists only on runs from before the recall store's removal, 2026-09-24;
|
|
432
|
+
// on a newer run `recall` is null and this family lists the cross-checks alone)
|
|
431
433
|
// qids (computed executed); over-cap rows are real unanswered questions (OPEN → closure/lawyer).
|
|
432
434
|
for (const [name, doc] of [["xcheck", xcheck], ["recall", recall]]) {
|
|
433
435
|
for (const d of doc?.directives ?? []) {
|
|
@@ -601,7 +603,7 @@ const normMark = (s) => String(s ?? "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
|
601
603
|
* as immaterial only when the mark it went looking for is in the client's findings. Otherwise the mark
|
|
602
604
|
* is owed a finding and the ask stays open.
|
|
603
605
|
*
|
|
604
|
-
* The defect this answers: a recall probe found
|
|
606
|
+
* The defect this answers: a recall probe found HALVER KORPHI, the closure stage pointed at
|
|
605
607
|
* `register-findings.md` — the run's own working sheet, where the mark genuinely IS reasoned — and the
|
|
606
608
|
* ask closed. The sheet is a citable file and the citation was true. It is simply not the document the
|
|
607
609
|
* client reads, and nothing checked that.
|
|
@@ -44,17 +44,97 @@
|
|
|
44
44
|
// set-GID scar on the pool root). The existing detect-and-journal sweep STAYS as corroboration; its
|
|
45
45
|
// never-kill-a-run property is sound and survives.
|
|
46
46
|
|
|
47
|
-
import {
|
|
47
|
+
import { realpathSync, statSync } from "node:fs";
|
|
48
|
+
import { resolve, win32, posix } from "node:path";
|
|
48
49
|
import { driverDir } from "../shared/driver-dir.mjs"; //
|
|
49
50
|
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Is `child` inside `root` — by path segment, never by string prefix? PURE.
|
|
53
|
+
*
|
|
54
|
+
* ON WINDOWS BY WINDOWS RULES. There a path may use either separator and a name matches in any case, so
|
|
55
|
+
* `c:/users/x/SKILLS/a.md` is a file inside `C:\Users\x\skills`. Compared as Linux compares, the
|
|
56
|
+
* write lands in the protected tree and the boundary lets it through. `platform` is a parameter so that
|
|
57
|
+
* branch runs on a Linux CI. `foldCase` says whether to ignore letter case: always on Windows, and
|
|
58
|
+
* elsewhere as foldsCaseAt finds the protected folder's disk, which denyReason asks for each tree.
|
|
59
|
+
*/
|
|
60
|
+
export function isInside(root, child, { platform = process.platform, foldCase = platform === "win32" } = {}) {
|
|
61
|
+
const P = platform === "win32" ? win32 : posix;
|
|
62
|
+
const fold = foldCase ? (s) => s.toLowerCase() : (s) => s;
|
|
63
|
+
const r = fold(P.resolve(String(root ?? "")));
|
|
64
|
+
const c = fold(P.resolve(String(child ?? "")));
|
|
54
65
|
if (!r || r === "." || !c) return false;
|
|
55
66
|
// `/a/skills-backup` is NOT inside `/a/skills`. A `startsWith` without the separator says it is, and
|
|
56
67
|
// that is the classic form of this check being wrong in the direction that blocks real work.
|
|
57
|
-
return c === r || c.startsWith(r.endsWith(sep) ? r : r + sep);
|
|
68
|
+
return c === r || c.startsWith(r.endsWith(P.sep) ? r : r + P.sep);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The one spelling of a Windows path: no device prefix, and the long names Windows itself reports.
|
|
73
|
+
*
|
|
74
|
+
* WINDOWS REACHES ONE FILE BY SEVERAL NAMES, and the boundary compared the one it was handed. Measured on
|
|
75
|
+
* a Windows runner, 2026-09-23: a write spelled `\\?\C:\…\skills\x.md`, and a write spelled with the
|
|
76
|
+
* long name of a folder the policy had recorded by its 8.3 short name (`RUNNER~1` against
|
|
77
|
+
* `runneradmin`), both reached the protected folder and were allowed. The device prefix (`\\?\`,
|
|
78
|
+
* `\\.\`) is taken off, and the longest part of the path that exists is asked of Windows itself, which
|
|
79
|
+
* answers with the long names and follows a junction, so a junction pointing into a protected folder is
|
|
80
|
+
* inside it too. The part that does not exist yet is joined back as written. `realpath` is injected so
|
|
81
|
+
* this runs on Linux.
|
|
82
|
+
*/
|
|
83
|
+
export function canonicalWindowsPath(p, { realpath = realpathSync.native } = {}) {
|
|
84
|
+
const bare = String(p ?? "").replace(/^[\\/]{2}[?.][\\/]UNC[\\/]/i, "\\\\").replace(/^[\\/]{2}[?.][\\/]/, "");
|
|
85
|
+
const abs = win32.resolve(bare);
|
|
86
|
+
const rest = [];
|
|
87
|
+
for (let cur = abs; ;) {
|
|
88
|
+
try { return win32.join(realpath(cur), ...rest); }
|
|
89
|
+
catch {
|
|
90
|
+
const parent = win32.dirname(cur);
|
|
91
|
+
if (parent === cur) return abs;
|
|
92
|
+
rest.unshift(win32.basename(cur));
|
|
93
|
+
cur = parent;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const swapCase = (s) => [...s].map((ch) => (ch === ch.toLowerCase() ? ch.toUpperCase() : ch.toLowerCase())).join("");
|
|
99
|
+
const foldMemo = new Map();
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Does the disk holding `root` ignore letter case? Asked of THAT folder, never of the platform.
|
|
103
|
+
*
|
|
104
|
+
* A Mac's default volume ignores case, so `P/SKILLS/x.md` is a file inside `P/skills`, and a boundary
|
|
105
|
+
* comparing letter for letter let the write through: measured on the macOS runner, 2026-09-23. But a Mac
|
|
106
|
+
* volume can be formatted to mind case, and a Linux folder can be set to ignore it, so the platform does
|
|
107
|
+
* not answer. The folder is stat'ed under its own name and under that name with every letter's case
|
|
108
|
+
* swapped: the same file under both means this disk ignores case. It writes nothing, so it adds nothing to
|
|
109
|
+
* a protected tree.
|
|
110
|
+
*
|
|
111
|
+
* WINDOWS ALWAYS FOLDS, as it did before this was asked, because NTFS ignores case unless a folder is set
|
|
112
|
+
* otherwise. Where the question cannot be put (the folder does not exist yet, or its name has no letters)
|
|
113
|
+
* the answer is the platform's default disk, Windows and macOS folding, so a Mac errs toward refusing. The
|
|
114
|
+
* answer is kept per folder for the life of the process. `stat` is injected so every branch runs on Linux.
|
|
115
|
+
*/
|
|
116
|
+
export function foldsCaseAt(root, { platform = process.platform, stat = statSync } = {}) {
|
|
117
|
+
if (platform === "win32") return true;
|
|
118
|
+
const abs = posix.resolve(String(root ?? ""));
|
|
119
|
+
const key = `${platform}|${abs}`;
|
|
120
|
+
if (foldMemo.has(key) && stat === statSync) return foldMemo.get(key);
|
|
121
|
+
const byDefault = platform === "darwin";
|
|
122
|
+
const name = posix.basename(abs);
|
|
123
|
+
let answer = byDefault;
|
|
124
|
+
const other = swapCase(name);
|
|
125
|
+
if (name && other !== name) {
|
|
126
|
+
try {
|
|
127
|
+
const own = stat(abs);
|
|
128
|
+
try {
|
|
129
|
+
const swapped = stat(posix.join(posix.dirname(abs), other));
|
|
130
|
+
answer = own.dev === swapped.dev && own.ino === swapped.ino;
|
|
131
|
+
} catch (e) {
|
|
132
|
+
answer = e?.code === "ENOENT" || e?.code === "ENOTDIR" ? false : byDefault;
|
|
133
|
+
}
|
|
134
|
+
} catch { answer = byDefault; }
|
|
135
|
+
}
|
|
136
|
+
if (stat === statSync) foldMemo.set(key, answer);
|
|
137
|
+
return answer;
|
|
58
138
|
}
|
|
59
139
|
|
|
60
140
|
/**
|
|
@@ -87,11 +167,13 @@ export function authorityTrees({ skillsRoots = [], profilesDir = null, runDir =
|
|
|
87
167
|
* why it is refused, that a retry gets the same answer, and where stage output lives. No second person, no
|
|
88
168
|
* imperative. The seat decides what to do with a fact, which is the whole difference.
|
|
89
169
|
*/
|
|
90
|
-
export function denyReason(targetPath, trees) {
|
|
170
|
+
export function denyReason(targetPath, trees, { platform = process.platform, foldsCase = (root) => foldsCaseAt(root, { platform }),
|
|
171
|
+
canonical = platform === "win32" ? (p) => canonicalWindowsPath(p) : (p) => p } = {}) {
|
|
91
172
|
const t = String(targetPath ?? "").trim();
|
|
92
173
|
if (!t) return null;
|
|
174
|
+
const target = canonical(t);
|
|
93
175
|
for (const tree of trees ?? []) {
|
|
94
|
-
if (!isInside(tree.path,
|
|
176
|
+
if (!isInside(canonical(tree.path), target, { platform, foldCase: foldsCase(tree.path) })) continue;
|
|
95
177
|
return `REFUSED by the driver's write boundary: ${t} is inside ${tree.why} (${tree.path}). `
|
|
96
178
|
+ `That tree is authored at deploy time and never by a running stage, so the refusal is configuration `
|
|
97
179
|
+ `rather than a permission prompt, and a retry of the same path returns this same answer. Stage `
|
package/driver/band-shape.mjs
CHANGED
|
@@ -65,7 +65,7 @@ const tokensOf = (s) => String(s ?? "").split(/[^\p{L}\p{N}]+/u).map((t) => norm
|
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* The ALTERNATIVE NAMES a label carries — `VENZAL / VENZALMONO / VENZALKOMB` is one relabelled entry,
|
|
68
|
-
* `
|
|
68
|
+
* `LUMIVANE & Device` is one mark plus a device note.
|
|
69
69
|
*
|
|
70
70
|
* The separator class is reference-score's `labelAliases`, character for character, and the two are
|
|
71
71
|
* meant to stay that way: the scorer and the engine must agree about what a relabelling is, or a record
|
|
@@ -130,7 +130,7 @@ export function classifyRecord(markText, targets) {
|
|
|
130
130
|
if (!norm) {
|
|
131
131
|
// ── — A MARK WITH NO LATIN SKELETON CAN STILL BE IDENTICAL, AND IT WAS (2026-08-14) ──────────
|
|
132
132
|
//
|
|
133
|
-
// R1 retrieved TEN exact-match
|
|
133
|
+
// R1 retrieved TEN exact-match 澜珀 registrations — four REGISTERED, three in class 9, all in the
|
|
134
134
|
// matter's own classes — and the delivered report did not mention the token once. This line is
|
|
135
135
|
// where they went. `normalizeElement` keeps only Latin characters, so a Han mark normalises to the
|
|
136
136
|
// empty string, fell straight to `unclassifiable`, and `unclassifiable` is not one of the two tiers
|
|
@@ -139,7 +139,7 @@ export function classifyRecord(markText, targets) {
|
|
|
139
139
|
// weighed. Retrieved, banded, positioned, and never an obligation.
|
|
140
140
|
//
|
|
141
141
|
// The classifier could not COMPARE them; that is true, and it is not the same as could not MATCH
|
|
142
|
-
// them.
|
|
142
|
+
// them. 澜珀 equals 澜珀 by inspection. The precedent is in this repo already: reference-score's
|
|
143
143
|
// matchesReference makes RAW comparison its rule 1 for exactly this case, because normalizeElement
|
|
144
144
|
// keeps only [a-z0-9] and a CJK mark folds to the empty string that every Latin rule would match
|
|
145
145
|
// against everything. The scorer has had that rule since R1's Chinese registration was specced. The
|
|
@@ -147,13 +147,13 @@ export function classifyRecord(markText, targets) {
|
|
|
147
147
|
//
|
|
148
148
|
// EQUALITY ONLY, DELIBERATELY. The floors are mechanical obligations a lawyer must answer row by
|
|
149
149
|
// row, so every member has to be defensible without judgment: raw-equal after NFKC (which folds
|
|
150
|
-
// full-width and compatibility forms of the same characters) is that. Containment is not —
|
|
151
|
-
// contains
|
|
150
|
+
// full-width and compatibility forms of the same characters) is that. Containment is not — 澜珀計
|
|
151
|
+
// contains 澜珀 and is a different mark — and whether it matters is judgment's call, reached
|
|
152
152
|
// through the same-family tiers and the crowd descriptors, not by widening a floor.
|
|
153
153
|
//
|
|
154
154
|
// THIS PARAGRAPH USED TO SAY "narrower than the scorer's rule", and it was right when it was
|
|
155
155
|
// written: matchesReference's script rule was unowned containment in both directions until,
|
|
156
|
-
// which scored gold
|
|
156
|
+
// which scored gold 澜珀 as retrieved off a different proprietor's longer mark that contains it. The scorer now takes
|
|
157
157
|
// equality too, with one owner-gated containment escape the floors deliberately do not have. The
|
|
158
158
|
// two agree; neither is the looser one any more.
|
|
159
159
|
const raw = text.normalize("NFKC").trim();
|
|
@@ -175,7 +175,7 @@ export function classifyRecord(markText, targets) {
|
|
|
175
175
|
|
|
176
176
|
// ── member 2 — A RELABELLED RECORD IS THE SAME MARK, AND IT WAS TIERING BELOW THE FLOOR ────────
|
|
177
177
|
//
|
|
178
|
-
// Measured against the scorer on `origin/main`: `VENZAL / VENZALMONO / VENZALKOMB` and `
|
|
178
|
+
// Measured against the scorer on `origin/main`: `VENZAL / VENZALMONO / VENZALKOMB` and `LUMIVANE &
|
|
179
179
|
// Device` are `alias` to reference-score and `same-family / token-identical` here. Not dropped —
|
|
180
180
|
// tiered BELOW the line. The floors take `identical` and `near-identical` only, so a register record
|
|
181
181
|
// that IS the mark under a relabelling never became a row a lawyer had to answer. Same consequence as
|
|
@@ -184,7 +184,7 @@ export function classifyRecord(markText, targets) {
|
|
|
184
184
|
// ALIAS SEPARATORS ONLY, and the distinction is reference-score's, kept deliberately identical
|
|
185
185
|
// because the two must agree about what a relabelling IS: `/ , · & |` separate ALTERNATIVE NAMES for
|
|
186
186
|
// one record; whitespace and hyphens separate WORDS WITHIN one name. Conflating them makes every
|
|
187
|
-
// multi-word mark match its own first word — `
|
|
187
|
+
// multi-word mark match its own first word — `WAVO TWIST` would become `WAVO`, which is a different
|
|
188
188
|
// proprietor's different mark and the collision the scorer's own doc block warns about. Whitespace is
|
|
189
189
|
// NOT in the class here for exactly that reason.
|
|
190
190
|
//
|
|
@@ -213,7 +213,7 @@ export function classifyRecord(markText, targets) {
|
|
|
213
213
|
// The candidate set of the retrieved→judgment reconciliation: every LIVE, in-scope, SCREEN-SURFACED
|
|
214
214
|
// record whose mark carries the manifest's dominant element — as a standalone token
|
|
215
215
|
// (`token-identical`), a token one edit away (`token-edit-1`), or CONCATENATED inside a longer
|
|
216
|
-
// squashed word (`concatenation` — the
|
|
216
|
+
// squashed word (`concatenation` — the WAVOTONK class: same-family in substance, "other" to the
|
|
217
217
|
// token classifier, and the proven silent-death seam). Screen-surfaced only (screen_verdict
|
|
218
218
|
// surface:*): the obligation this feeds is "everything the screen surfaced ends somewhere a reader
|
|
219
219
|
// can see"; drop:* records were already ended by the screen with a policed drop row.
|
|
@@ -227,7 +227,7 @@ export function classifyRecord(markText, targets) {
|
|
|
227
227
|
// itself never spells. They rank last so they never reached the top slice, but they inflate the
|
|
228
228
|
// residual a declared crowd count must cover — a count written one short blocks the run (the
|
|
229
229
|
// reviewer reproduced 378 declared vs 379 residual → blocked). A concatenation is a SQUASHED WORD
|
|
230
|
-
// (
|
|
230
|
+
// (WAVOTONK), which is a property of one token.
|
|
231
231
|
export const MIN_CONCAT_LEN = 4;
|
|
232
232
|
|
|
233
233
|
/**
|
|
@@ -323,8 +323,8 @@ export function dominantElementComposites(band, { dominantElement, inScopeClasse
|
|
|
323
323
|
// identity, and the arithmetic becomes a cross-reference instead.
|
|
324
324
|
// (2) owner identity is compared on a CORE + LEGAL-FORM pair, not on the suffix-stripped core
|
|
325
325
|
// alone. Stripping entity words is what makes "Fairmile Snack Foods Corp." = "Fairmile Snack Foods
|
|
326
|
-
// Corporation" work, but on the same band it also collapsed "
|
|
327
|
-
// and "
|
|
326
|
+
// Corporation" work, but on the same band it also collapsed "Wavo Group Limited", "Wavo AG"
|
|
327
|
+
// and "Wavo Corporation" to the bare core "wavo" and merged three distinct registrants on the
|
|
328
328
|
// mark-owner edge alone. So: same core AND compatible legal forms (one side's form set a
|
|
329
329
|
// SUBSET of the other's — a register that simply omits the form still matches, two different
|
|
330
330
|
// stated forms never do).
|
|
@@ -359,7 +359,7 @@ export function ownerIdentical(a, b) {
|
|
|
359
359
|
}
|
|
360
360
|
|
|
361
361
|
/** The union-find bucket key for the mark+owner edge — core + the SORTED form set, so
|
|
362
|
-
* "
|
|
362
|
+
* "Wavo AG" and "Wavo Corporation" land in different buckets while "X Corp."/"X Corporation" share
|
|
363
363
|
* one. The subset relaxation (a register omitting the form) is applied by the second pass below. */
|
|
364
364
|
const ownerKey = (s) => { const { core, forms } = ownerIdentity(s); return core ? `${core}|${[...forms].sort().join("+")}` : ""; };
|
|
365
365
|
const normOwner = (s) => ownerIdentity(s).core;
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// PURE (no node imports) → tests offline. Mirrors coverage-ledger.mjs / findings-model.mjs.
|
|
14
14
|
|
|
15
15
|
// add/drop = the both-directions element neighbours (VELTRIN→VELTRI is "drop"); phonetic/homophone =
|
|
16
|
-
// sound-alikes; neighbour = a one-keystroke real-word / famous-mark neighbour (
|
|
16
|
+
// sound-alikes; neighbour = a one-keystroke real-word / famous-mark neighbour (NOVAPULSO on NOVAPULSE);
|
|
17
17
|
// composite = the element inside a larger mark (VELTRI-composite cluster).
|
|
18
18
|
export const VARIANT_DIRECTIONS = ["add", "drop", "phonetic", "homophone", "neighbour", "composite"];
|
|
19
19
|
// the spine is ranked by goods-overlap with the actual product, not by class number + registration status.
|
|
@@ -36,7 +36,18 @@
|
|
|
36
36
|
// "no node imports → tests offline" invariant, exactly as connotation-search.mjs already does.
|
|
37
37
|
import { abbrev } from "./repair-contract.mjs";
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
// Version 2 is the one whose instruction tells the model to write `null` for a query it could not send,
|
|
40
|
+
// and never 0. At version 1 a 0 could mean either, so a reader may not count one as a search that ran.
|
|
41
|
+
export const CASE_LAW_LEDGER_SCHEMA_VERSION = 2;
|
|
42
|
+
|
|
43
|
+
// WHAT A RECORD THAT NAMES NO VERSION IS. The oldest, never the newest. Defaulting an unversioned record
|
|
44
|
+
// to whatever this build happens to be at hands it every promise the current instruction makes, which is
|
|
45
|
+
// exactly backwards: the records that name no version are the ones written before the field was asked
|
|
46
|
+
// for, and their zeros are the ambiguous kind. Read low, and a record only gains a promise by claiming it.
|
|
47
|
+
export const CASE_LAW_LEDGER_OLDEST_SCHEMA = 1;
|
|
48
|
+
|
|
49
|
+
/** Version 2 and up separate "sent and returned nothing" (0) from "never sent" (null). */
|
|
50
|
+
export const ledgerSeparatesUnsentQueries = (ledger) => Number(ledger?.schema_version) >= 2;
|
|
40
51
|
|
|
41
52
|
/**
|
|
42
53
|
* How far a proceeding got. `read` is the only state that discharges a depth dive, and the distinction
|
|
@@ -91,11 +102,16 @@ export function parseCaseLawLedger(raw) {
|
|
|
91
102
|
return {
|
|
92
103
|
error: null,
|
|
93
104
|
ledger: {
|
|
94
|
-
schema_version: Number(parsed.schema_version) ||
|
|
105
|
+
schema_version: Number(parsed.schema_version) || CASE_LAW_LEDGER_OLDEST_SCHEMA,
|
|
95
106
|
queries: queries.filter((q) => q && typeof q === "object").map((q) => ({
|
|
96
107
|
query: str(q.query),
|
|
97
108
|
jurisdiction: str(q.jurisdiction),
|
|
98
|
-
|
|
109
|
+
// NULL STAYS NULL, and this is load-bearing from version 2 on. `Number(null)` is 0 and
|
|
110
|
+
// `Number("")` is 0, both finite, so the obvious coercion turns the record's "I could not send
|
|
111
|
+
// this query" into "I sent it and got nothing" — the very two the version exists to separate,
|
|
112
|
+
// collapsed by the parser before any reader sees them. Only a value that genuinely is a number
|
|
113
|
+
// becomes one; everything else is null, which is the record declining to give a count.
|
|
114
|
+
results: q.results == null || q.results === "" || Number.isNaN(Number(q.results)) ? null : Number(q.results),
|
|
99
115
|
})),
|
|
100
116
|
citations: citations.filter((c) => c && typeof c === "object").map((c) => ({
|
|
101
117
|
proceeding: str(c.proceeding),
|
|
@@ -35,6 +35,7 @@ import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
|
35
35
|
import { spawnSync } from "node:child_process"; // — the birth stamp where there is no /proc
|
|
36
36
|
import { join } from "node:path";
|
|
37
37
|
import { config } from "./driver.config.mjs";
|
|
38
|
+
import { windowsProcessScript, parseWindowsRows, defaultRunPowerShell } from "../shared/process-table.mjs"; // the birth stamp on Windows
|
|
38
39
|
|
|
39
40
|
// ── B2 — fail-safe claim liveness ────────────────────────────────────────────────────────────────────
|
|
40
41
|
// The .pid sidecar records "<pid>:<starttime>" (starttime = field 22 of /proc/<pid>/stat, the kernel's
|
|
@@ -42,7 +43,7 @@ import { config } from "./driver.config.mjs";
|
|
|
42
43
|
// impersonate a dead claimer and rot its `.processing` forever. Legacy bare-pid sidecars still parse
|
|
43
44
|
// (starttime null → pid-aliveness is all we have, today's behavior). All exported for unit tests.
|
|
44
45
|
export function procStarttime(pid, readStat = undefined,
|
|
45
|
-
{ platform = process.platform, readPsStart = defaultReadPsStart } = {}) {
|
|
46
|
+
{ platform = process.platform, readPsStart = defaultReadPsStart, runPowerShell = defaultRunPowerShell } = {}) {
|
|
46
47
|
// AN INJECTED READER IS THE CALLER'S STATEMENT ABOUT HOW TO READ, and it outranks the platform.
|
|
47
48
|
//
|
|
48
49
|
// Measured on macOS (the verification run): the arm that pins the field-22
|
|
@@ -61,6 +62,7 @@ export function procStarttime(pid, readStat = undefined,
|
|
|
61
62
|
return stat.slice(stat.lastIndexOf(")") + 2).trim().split(/\s+/)[19] ?? null;
|
|
62
63
|
} catch { return null; }
|
|
63
64
|
}
|
|
65
|
+
if (platform === "win32") return windowsStarttime(pid, runPowerShell);
|
|
64
66
|
// ── — THE SAME DEFENCE WHERE THERE IS NO /proc ─────────────────────────
|
|
65
67
|
//
|
|
66
68
|
// Returning null here was not a neutral degradation. `claimToken` announces it and falls back to a
|
|
@@ -82,6 +84,33 @@ export function procStarttime(pid, readStat = undefined,
|
|
|
82
84
|
} catch { return null; }
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
// ── WINDOWS: NEITHER /proc NOR ps ────────────────────────────────────────────────────────────────────
|
|
88
|
+
//
|
|
89
|
+
// Without a stamp on Windows the portal said nothing was draining the queue while the worker ran, which
|
|
90
|
+
// is the false alarm described above, and a claim held by a dead runner stayed held until the max-claim-age
|
|
91
|
+
// ceiling. That second cost is larger on Windows than anywhere else, because Windows hands a freed pid to
|
|
92
|
+
// the next process soon after. So the stamp is read from the system's process list, through PowerShell:
|
|
93
|
+
// the creation time as epoch milliseconds, the same reader `processTable` uses, so a stamp written from
|
|
94
|
+
// one is compared with a stamp read by the same code.
|
|
95
|
+
//
|
|
96
|
+
// ONE PROCESS IS REMEMBERED: this one. Its start cannot change while it runs, and the heartbeat and the
|
|
97
|
+
// status stamp ask for it on every tick, each a PowerShell start. Every other pid is read afresh, for the
|
|
98
|
+
// reason above: a remembered stamp is the dead process's, handed to whatever now wears its number.
|
|
99
|
+
let ownWindowsStamp;
|
|
100
|
+
function windowsStarttime(pid, runPowerShell) {
|
|
101
|
+
if (!Number.isInteger(pid) || pid <= 0) return null;
|
|
102
|
+
const own = pid === process.pid && runPowerShell === defaultRunPowerShell;
|
|
103
|
+
if (own && ownWindowsStamp) return ownWindowsStamp;
|
|
104
|
+
let row = null;
|
|
105
|
+
try {
|
|
106
|
+
const r = runPowerShell(windowsProcessScript(pid));
|
|
107
|
+
if (r && r.status === 0) row = parseWindowsRows(r.stdout).find((p) => p.pid === pid) ?? null;
|
|
108
|
+
} catch { return null; }
|
|
109
|
+
const stamp = row && Number.isFinite(row.startedAt) ? String(row.startedAt) : null;
|
|
110
|
+
if (own && stamp) ownWindowsStamp = stamp;
|
|
111
|
+
return stamp;
|
|
112
|
+
}
|
|
113
|
+
|
|
85
114
|
/** `ps` is POSIX and is on macOS. Empty stdout (no such pid) parses to NaN above, which is `null`. */
|
|
86
115
|
function defaultReadPsStart(pid) {
|
|
87
116
|
const r = spawnSync("ps", ["-o", "lstart=", "-p", String(pid)], { encoding: "utf8" });
|
|
@@ -130,8 +130,15 @@ export function renderClearanceVariants(model, scopeRows) {
|
|
|
130
130
|
const out = ["# Variant manifest", ""];
|
|
131
131
|
out.push(`## Mark: ${model.mark}`, "", `Dominant element: ${model.dominant_element}`, "");
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
// The famous-mark column appears only when an element carries the flag, so a manifest with none reads
|
|
134
|
+
// exactly as it did before the flag existed.
|
|
135
|
+
if (model.elements.some((e) => e.famous_mark_flag)) {
|
|
136
|
+
out.push("### Elements", "", "| Value | Kind | Famous-mark check |", "|---|---|---|");
|
|
137
|
+
for (const e of model.elements) out.push(`| ${e.value} | ${e.kind} | ${e.famous_mark_flag ? "yes" : ""} |`);
|
|
138
|
+
} else {
|
|
139
|
+
out.push("### Elements", "", "| Value | Kind |", "|---|---|");
|
|
140
|
+
for (const e of model.elements) out.push(`| ${e.value} | ${e.kind} |`);
|
|
141
|
+
}
|
|
135
142
|
out.push("");
|
|
136
143
|
|
|
137
144
|
out.push(`### Variants (${model.variants.length})`, "", "| Value | Category | Romanisation | Rationale |", "|---|---|---|---|");
|
|
@@ -172,7 +179,7 @@ export function renderClearanceVariants(model, scopeRows) {
|
|
|
172
179
|
/** The shape this tool declares, at every depth — what the ACCEPTOR enforces. */
|
|
173
180
|
const DECLARED = Object.freeze({
|
|
174
181
|
"": ["mark", "dominant_element", "elements", "variants", "incumbent_classes", "search_floor", "watchlist_owners", "goods_words", "scope_ledger"],
|
|
175
|
-
elements: ["value", "kind"],
|
|
182
|
+
elements: ["value", "kind", "famous_mark_flag"],
|
|
176
183
|
variants: ["value", "category", "rationale", "romanization"],
|
|
177
184
|
scope_ledger: ["layer", "item", "status", "reason", "reopen_trigger"],
|
|
178
185
|
});
|