clearotron 0.5.0-beta.4 → 0.5.0-beta.6
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/THIRD-PARTY-NOTICES.md +1 -1
- package/build-info.json +2 -2
- package/docs/architecture/04-configuration-reference.md +1 -1
- package/docs/architecture/05-config-governance.md +1 -1
- package/driver/CHANGELOG.md +21 -0
- package/driver/contract-e3-backlog.mjs +7 -7
- package/driver/coverage-form.mjs +5 -4
- package/driver/engine/mcp/perplexity-server.mjs +19 -6
- package/driver/engine/openai-agent.mjs +14 -11
- package/driver/findings-model.mjs +14 -1
- package/driver/gateway.mjs +18 -2
- package/driver/owner-screen.mjs +1 -1
- package/driver/owner-use-check.mjs +15 -6
- package/driver/package.json +1 -1
- package/driver/pipeline.mjs +82 -31
- package/driver/predelivery-lint.mjs +46 -11
- package/driver/publish/render-knockout.mjs +23 -4
- package/driver/record-replay.mjs +40 -0
- package/driver/register-digest-record.mjs +21 -5
- package/driver/register-plan.mjs +65 -7
- package/driver/repair-targets.mjs +81 -6
- package/driver/research-answers.mjs +106 -12
- package/driver/research-checks.mjs +84 -0
- package/driver/score-redaction.mjs +47 -2
- package/driver/skills/clearance-register/SKILL.md +2 -0
- package/driver/skills/clearance-register/digest.md +1 -1
- package/driver/skills/clearance-register/unit.md +2 -0
- package/driver/skills/clearance-search/phase2-execution.md +10 -7
- package/driver/skills/clearance-search/synthesis-rules.md +2 -2
- package/driver/skills/narrative-refutation/SKILL.md +1 -1
- package/driver/stage-context.mjs +14 -2
- package/driver/stages.mjs +8 -4
- package/driver/suite-census.json +79 -7
- package/driver/synthesis-record.mjs +123 -14
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/_shared/execute-plan.mjs +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/signa/src/core.js +58 -7
- package/scripts/deprecate-below.mjs +14 -3
- package/scripts/e2e-first-time.mjs +2 -1
- package/scripts/release-notes-lint.mjs +10 -4
- package/scripts/score.mjs +30 -3
package/THIRD-PARTY-NOTICES.md
CHANGED
package/build-info.json
CHANGED
|
@@ -191,7 +191,7 @@ timeout shot, warm patch, backoff), the lane-wedge re-dispatch, and the rate-lim
|
|
|
191
191
|
| `CLEAROTRON_AI_BILLING` | `subscription` (default) \| `api-key` \| `cloud` | Billing mode for the selected engine. ONE variable for both, and only the LIVE engine's setting is read — it fills each engine's billing knob, and the engine that is not selected is never consulted. **`anthropic-agent`:** subscription **deletes `ANTHROPIC_API_KEY` from the child env** so `claude -p` uses OAuth subscription credentials (a present key would override them); `api-key` keeps the key — the scale setting and standing fallback; **`cloud`** bills Claude per use through the reader's own cloud account, named by the Claude program's own switch — `CLAUDE_CODE_USE_VERTEX`, `CLAUDE_CODE_USE_FOUNDRY` or `CLAUDE_CODE_USE_BEDROCK`, or `ANTHROPIC_BASE_URL` alone for a gateway (see the credentials table below) — and deletes `ANTHROPIC_API_KEY` as subscription does. **`openai-agent`:** subscription seeds `auth.json` into the per-run `CODEX_HOME` from `CLEAROTRON_OPENAI_AUTH_FILE` (default `~/.codex/auth.json`) and strips API keys; `api-key` keeps `CODEX_API_KEY`; `cloud` is refused. **Fail-loud, never a guess, never the subscription in its place:** `api-key` with no `ANTHROPIC_API_KEY` / `CODEX_API_KEY` throws; on `anthropic-agent`, `cloud` with two switches on, or with none and no `ANTHROPIC_BASE_URL`, throws, and so does a switch left on under `subscription` or `api-key`, because the program bills that cloud whatever this says; a value that is none of the three throws on both engines. `clearotron start` and `clearotron doctor` report each refusal against the settings the services read, naming a value that is not a billing mode by its setting and never quoting it, and the runner refuses the search when it is ordered, before anything is spent. The resolved mode is stamped on every stage telemetry row (`engine`, `authMode`, `apiBilled`, and `cloud`, null when no cloud bills), and the attempt row also carries `providerReported`, the program's own word for who served the turn. |
|
|
192
192
|
| `CLEAROTRON_CLAUDE_PATH` · `CLEAROTRON_CODEX_PATH` | `claude` / `codex` on `PATH`, then the copy Clearotron installed | The engine binary to spawn, ONE PER ENGINE: `CLEAROTRON_CLAUDE_PATH` under `anthropic-agent`, `CLEAROTRON_CODEX_PATH` under `openai-agent`. Only the live engine's is read, and a machine that runs both sets both — they were one variable until it met a machine needing two different paths. A path set here must be **absolute**: stage subprocesses run with cwd set to the run directory, so a relative path does not resolve there, and `clearotron doctor` refuses one. **Unset**, or set to the bare word `claude` / `codex` (which means the same), the engine uses the program on `PATH` when the machine has one, and otherwise the copy Clearotron installed in `~/.local/share/clearotron/engines`: `clearotron install` offers to put the chosen engine's program there (`@anthropic-ai/claude-code` or `@openai/codex`, this platform's build only), and `clearotron update` refreshes it. Nothing is bundled into the package. A value naming anything else is used as given and never falls back. `clearotron doctor` says which copy it found, and its version when npm installed that copy. |
|
|
193
193
|
| `CLEAROTRON_OPENAI_AUTH_FILE` | `~/.codex/auth.json` | The credentials file seeded into the per-run `CODEX_HOME` under subscription billing. Was exempted from the August 2026 rename as an `openai-agent` internal rather than an install-surface name; a decision of 2026-09-04 **reversed that exemption** and renamed the whole namespace, so it carries the house prefix like everything else. One spelling, no exceptions. |
|
|
194
|
-
| `CLEAROTRON_OPENAI_MODEL_JUDGMENT` / `CLEAROTRON_OPENAI_MODEL_SWEEP` / `CLEAROTRON_OPENAI_MODEL_CHEAP` | `gpt-6.1-sol` / `gpt-6.1-
|
|
194
|
+
| `CLEAROTRON_OPENAI_MODEL_JUDGMENT` / `CLEAROTRON_OPENAI_MODEL_SWEEP` / `CLEAROTRON_OPENAI_MODEL_CHEAP` | `gpt-6.1-sol` / `gpt-6.1-terra` / `gpt-6-luna` (from 2026-10-28; from 2026-10-09 the sweep tier shared `gpt-6.1-sol`, and before that they were `gpt-5.6-sol` / `gpt-5.6-terra` / `gpt-5.6-luna`). Codex `0.159.0` or newer is required on a ChatGPT sign-in: older releases refuse `gpt-6.1-sol` with a 400. | Maps the driver's abstract `opus`/`sonnet`/`haiku` tiers onto the codex ladder, the way the anthropic engine maps onto its own (ruling 2026-09-02, superseding the single-model mapping). A tier that cannot hold its stage contract announces itself at that stage — `missing:negative-results`, `no_coverage_status_row`, `named_band_missing` in `_driver/<stage>.jsonl` — not at delivery. |
|
|
195
195
|
| ↳ why they are three, and what the old measurement still says | superseded 2026-09-02, not erased | The three defaulted to `sol` alone on a MEASUREMENT, never a placeholder: on 2026-08-11/12, with `SWEEP=gpt-5.6-terra` / `CHEAP=gpt-5.6-luna`, every codex clearance died on structural-output gates — `missing:negative-results`, `no_coverage_status_row`, `named_band_missing` — over 2 scenarios × 3 stage families, **byte-identically on retry**, so no retry budget rescued it. That measurement stands for the code and the codex CLI **of that date**, and it is why the judgment tier keeps `sol`. Three weeks of stage-contract work and several codex minor versions sit between it and the 2026-09-02 ruling that split the three. **The symptom to match against this cause is unchanged**: those same three gates, as a fail row in`_driver/<stage>.jsonl`, inside the first few dispatches of a clearance and identical on every retry — a model incapable of a contract is not flaky at it. It reads as an engine bug when the only fault is this configuration. A cheap-tier experiment still belongs in the three constants in `driver/engine/openai-agent.mjs` where a reviewer sees it, not in an untraceable env line. |
|
|
196
196
|
| `CLEAROTRON_DATABASE` | **REQUIRED — no default.** `corsearch` \| `clarivate` \| `signa` \| `euipo` \| `uspto-local` \| `free-tier` | Active register provider — one per run, set in the environment the deploy carries, in every environment including production. There is **no default**: unset resolves to `null` and every use of it throws, so a run refuses at start rather than calling a vendor nobody chose (the removed `corsearch` fallback named a vendor the deployment did not choose). Three are paid global sweeps; `euipo` (EU) and `uspto-local` (US) are free single-office sources, and `free-tier` composes those two as one register. Choosing a free value makes every territory outside its coverage a disclosed *deferred* row. Unknown ids throw loudly, and the gather layer throws at stage time for any provider without a built MCP server. |
|
|
197
197
|
| `CLEAROTRON_CODEX_SANDBOX_BYPASS` | unset | `1` runs `codex exec` with its own sandbox bypassed, for hosts where that sandbox cannot run (setup and `doctor --probe-engine` report it). It removes a defence: with it set, a stage's commands run with every permission of the account Clearotron runs as, not only in the run folder. Codex has no guard against a stage writing into the run's `_driver/` folder, with the sandbox or without it. Set it because the host forces it, never for convenience. |
|
|
@@ -161,7 +161,7 @@ structural, or dev seam); [dev] = dev/test seam, never set in prod.
|
|
|
161
161
|
`CLEAROTRON_AI` (anthropic-agent | openai-agent; default anthropic-agent), `CLEAROTRON_AI_BILLING`
|
|
162
162
|
(subscription|api-key|cloud; cloud is Claude only), `CLEAROTRON_CODEX_PATH`,
|
|
163
163
|
`CLEAROTRON_OPENAI_AUTH_FILE`, `CLEAROTRON_OPENAI_MODEL_JUDGMENT` / `CLEAROTRON_OPENAI_MODEL_SWEEP` /
|
|
164
|
-
`CLEAROTRON_OPENAI_MODEL_CHEAP` (gpt-6.1-sol / gpt-6.1-
|
|
164
|
+
`CLEAROTRON_OPENAI_MODEL_CHEAP` (gpt-6.1-sol / gpt-6.1-terra / gpt-6-luna),
|
|
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).
|
package/driver/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# clearotron-driver
|
|
2
2
|
|
|
3
|
+
## 0.5.0-beta.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fixed: A clearance on the Signa register no longer loses searches to a brief rate limit; it waits as long as the register asks and searches again.
|
|
8
|
+
- Fixed: A clearance resumed after an interruption now searches the territories the order named, not the account's default markets.
|
|
9
|
+
- Fixed: A wider register search is no longer set aside because a narrower search returned a short list; the engine now weighs what each waiting search could add to the advice and records its reason.
|
|
10
|
+
- Fixed: The lawyer's report now says why a search was not carried out: the register refused it for the day and a later run can repeat it, or the register cannot run that kind of search at all.
|
|
11
|
+
|
|
12
|
+
## 0.5.0-beta.5
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- Fixed: When a register search fails, the report's coverage still accounts for the unread searches beside it, instead of leaving them out.
|
|
17
|
+
- Fixed: When the search for an owner's use of a mark could not run, the report no longer says that search found nothing.
|
|
18
|
+
- Fixed: A register entry the engine placed on the watchlist now appears in the audit trail with its reason, instead of being left out.
|
|
19
|
+
- Fixed: A citation of a finding the review withdrew, in the report's summary panel or methodology, is now sent back for correction before delivery.
|
|
20
|
+
- Fixed: A clearance that searched common law or the United Kingdom is no longer flagged as claiming a search it did not run.
|
|
21
|
+
- New: Searches now use the latest models: Claude Haiku 5.5, Sonnet 5.5 and Opus 5.5, and OpenAI's GPT-6.1 Sol, GPT-6.1 Terra and GPT-6 Luna.
|
|
22
|
+
- Fixed: When two parties use the same name without a registration, the report keeps a finding for each, instead of keeping only the last one saved.
|
|
23
|
+
|
|
3
24
|
## 0.5.0-beta.4
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
|
@@ -171,7 +171,7 @@ export const E3_BACKLOG = [
|
|
|
171
171
|
{
|
|
172
172
|
stage: "common-law",
|
|
173
173
|
kind: "dictated-line-shape",
|
|
174
|
-
where: "driver/stages.mjs:
|
|
174
|
+
where: "driver/stages.mjs:1104 (emitted at 1891, 1920, 2100, 2320)",
|
|
175
175
|
surface: "stage-message",
|
|
176
176
|
evidence: "CROSS-CHECK HAND-OFF: … record it on its OWN line in EXACTLY the form \"CROSS-CHECK REQUIRED: <what> — <why>\" (that exact prefix; an em-dash between what and why; name the mark in CAPS in <what>). The driver parses ONLY this exact line shape…",
|
|
177
177
|
reparsedBy: "driver/doubt-ledger.mjs:183 CROSS_CHECK_RE = /^(?:[-*]\\s+)?CROSS-CHECK REQUIRED:\\s*(.+?)\\s+—\\s+(.+?)\\s*$/ → mintCrossCheckDoubts",
|
|
@@ -265,7 +265,7 @@ export const E3_BACKLOG = [
|
|
|
265
265
|
{
|
|
266
266
|
stage: "register-unit",
|
|
267
267
|
kind: "exactly-these-keys",
|
|
268
|
-
where: "driver/stages.mjs:
|
|
268
|
+
where: "driver/stages.mjs:2272",
|
|
269
269
|
surface: "stage-message",
|
|
270
270
|
evidence: "Every block you append MUST carry \"state\":\"enumerated\" (ONLY if you paged it to has_more:false) or \"state\":\"incomplete\" — EXACTLY those two strings; there is no \"verified\"/\"checked\"/\"complete\"/\"clean\" state, and any other value fails the stage.",
|
|
271
271
|
reparsedBy: "driver/named-band.mjs parseNamedBand (off-enum state fails validators.registerUnit)",
|
|
@@ -283,7 +283,7 @@ export const E3_BACKLOG = [
|
|
|
283
283
|
{
|
|
284
284
|
stage: "register-unit",
|
|
285
285
|
kind: "exactly-these-keys",
|
|
286
|
-
where: "driver/stages.mjs:
|
|
286
|
+
where: "driver/stages.mjs:4280 (the frame-reopen / scoped-retry message builder). A second number stood here and had been stale for some time: it pointed at a contract-element description rather than a builder, at its old line and at every mechanical shift of it. Two candidate builders sit beside 4164 and picking one would be a guess, so the wrong pointer is removed rather than moved a third time — one accurate citation beats one accurate and one invented.",
|
|
287
287
|
surface: "stage-message",
|
|
288
288
|
evidence: "Every block you append MUST carry \"state\":\"enumerated\" (ONLY if paged to has_more:false) or \"state\":\"incomplete\" — EXACTLY those two strings … (re-dispatch builders, which REPLACE def.message)",
|
|
289
289
|
reparsedBy: "driver/named-band.mjs parseNamedBand. Scope warning: these builders replace def.message on every escalation / envelope-close / frame-reopen dispatch, so an E3 lint that walks STAGES[*].message only never sees them",
|
|
@@ -409,7 +409,7 @@ export const E3_BACKLOG = [
|
|
|
409
409
|
{
|
|
410
410
|
stage: "register-digest",
|
|
411
411
|
kind: "exactly-these-keys",
|
|
412
|
-
where: "driver/skills/clearance-register/SKILL.md:
|
|
412
|
+
where: "driver/skills/clearance-register/SKILL.md:228-229",
|
|
413
413
|
surface: "skill-file",
|
|
414
414
|
evidence: "**The status vocabulary is CLOSED: EXACTLY one bare token of: `confirmed-clean` / `coverage-limited` / `deferred` / `withheld-by-judgment`.** Qualifiers never go in a status cell; they go in the reason.",
|
|
415
415
|
reparsedBy: "driver/coverage-form.mjs / driver/coverage-ledger.mjs. Since the typed-transport conversion the STAGE MESSAGE no longer restates the enum; the surviving copies are SKILL.md (here), digest.md:207, the dispatch brief (coverage-form.mjs coverageFormBrief), the record_coverage schema (coverage-server.mjs) and gateway.mjs's repair hints — still one enum spelled at five sites",
|
|
@@ -524,7 +524,7 @@ export const E3_BACKLOG = [
|
|
|
524
524
|
kind: "literal-json-skeleton",
|
|
525
525
|
where: "driver/stages.mjs:3294",
|
|
526
526
|
surface: "stage-message",
|
|
527
|
-
evidence: "use_check = {\"source\",\"quality\"}: … quality: OPTIONAL, EXACTLY one of owner-site / independent / register-mirror … own_rights = {\"source\"}",
|
|
527
|
+
evidence: "use_check = {\"source\",\"quality\",\"question\"}: … quality: OPTIONAL, EXACTLY one of owner-site / independent / register-mirror … own_rights = {\"source\"}",
|
|
528
528
|
reparsedBy: "driver/verify.mjs checkFindingsSibling (finding_use_check_missing); driver/own-rights.mjs:19-22",
|
|
529
529
|
removedByMove: "NOTHING ON THE #850 PLAN REMOVES THIS",
|
|
530
530
|
},
|
|
@@ -670,7 +670,7 @@ export const E3_BACKLOG = [
|
|
|
670
670
|
{
|
|
671
671
|
stage: "narrative-refutation",
|
|
672
672
|
kind: "dictated-line-shape",
|
|
673
|
-
where: "driver/stages.mjs:
|
|
673
|
+
where: "driver/stages.mjs:4835",
|
|
674
674
|
surface: "stage-message",
|
|
675
675
|
// RE-QUOTED BY CONVERSION 9. The LINE-TOKEN half is gone — no "anywhere on the line", no "[kind: …]
|
|
676
676
|
// token", no "a line with no token is treated as fact", because a kind is a typed field now and an
|
|
@@ -769,7 +769,7 @@ export const E3_BACKLOG = [
|
|
|
769
769
|
{
|
|
770
770
|
stage: "report-card",
|
|
771
771
|
kind: "literal-json-skeleton",
|
|
772
|
-
where: "driver/stages.mjs:
|
|
772
|
+
where: "driver/stages.mjs:3968",
|
|
773
773
|
surface: "stage-message",
|
|
774
774
|
evidence: "The finding's OWN record — the ONLY source for this card …:\\n```json\\n<JSON.stringify(finding, null, 2)>\\n```",
|
|
775
775
|
reparsedBy: "none — this is the INPUT side, and that is why it belongs in the survey: a full JSON object rendered into the prompt is exactly the mechanism #850 proves produced R-RECEIPT (the model pattern-matches a shown shape). E3's clause 1 as written (\"a code fence or inline example showing the exact object shape the model must emit\") does not reach an injected record, so the lint needs an explicit rule for shown-but-not-owed structure",
|
package/driver/coverage-form.mjs
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
import { REGISTER_AXES, COVERAGE_STATUSES, normalizeAxis, CROWD_RULING_TOKEN,
|
|
32
32
|
CROWD_RULING_UNIT_GRAMMAR } from "./coverage-ledger.mjs";
|
|
33
33
|
import { shortId } from "./connotation-search.mjs";
|
|
34
|
-
import { openBlocksByAxis, crowdNarrowing, blocksByQidOf, isProviderHardErrorReason } from "./register-plan.mjs";
|
|
34
|
+
import { openBlocksByAxis, crowdNarrowing, blocksByQidOf, isProviderHardErrorReason, refusedSearchSentence } from "./register-plan.mjs";
|
|
35
35
|
import { territoryLayerReport, unsearchedLayerReason } from "./binding-layers.mjs"; import { goodsTermsList } from "../providers/_shared/term-shape.mjs";
|
|
36
36
|
|
|
37
37
|
const STATUS_SET = new Set(COVERAGE_STATUSES);
|
|
@@ -373,7 +373,7 @@ export function coverageFormRows({ skeleton = [], activeAxes = null, plan = null
|
|
|
373
373
|
return r ? { narrowing: r.narrowing, ...(r.lists?.length ? { lists: r.lists } : {}), ...(r.not_covered?.length ? { not_covered: r.not_covered } : {}) } : {};
|
|
374
374
|
};
|
|
375
375
|
const entriesByQid = new Map((plan?.entries ?? []).map((e) => [e.qid, e]));
|
|
376
|
-
const open = openBlocksByAxis(skel, bandBlocksByAxis, plan);
|
|
376
|
+
const open = openBlocksByAxis(skel, bandBlocksByAxis, plan, { withDeferred: true });
|
|
377
377
|
// Axis order is the run's, not a set's iteration order: the plan-execution skeleton first (the order
|
|
378
378
|
// the plan dictated), then any active axis the skeleton does not name, then the canonical axis list as
|
|
379
379
|
// the final tie-break. Deterministic ⇒ the same run regenerates a byte-identical form every pass.
|
|
@@ -979,7 +979,8 @@ export function renderCoverageLedgerSection(rows) {
|
|
|
979
979
|
Number.isInteger(r.total_hits) ? `${r.total_hits} hits` : "",
|
|
980
980
|
r.unaccounted_classes?.length ? `classes unaccounted: ${r.unaccounted_classes.join(", ")}` : "",
|
|
981
981
|
r.unaccounted_terms?.length ? `terms unaccounted: ${r.unaccounted_terms.join(", ")}` : "",
|
|
982
|
-
|
|
982
|
+
// A search the register refused reads as the owner's sentence for it, not as its recorded line.
|
|
983
|
+
r.receipt_reason ? `receipt: ${refusedSearchSentence(r.receipt_reason) ?? r.receipt_reason}` : "",
|
|
983
984
|
].filter(Boolean).join("; ");
|
|
984
985
|
const reason = [cell(r.reason), detail ? `(${cell(detail)})` : ""].filter(Boolean).join(" ");
|
|
985
986
|
return `| ${cell(r.unit)} | ${cell(String(r.status).trim().toLowerCase())} | ${reason} | ${cell(r.qid ?? "—")} |`;
|
|
@@ -1075,7 +1076,7 @@ function briefRow(r) {
|
|
|
1075
1076
|
Number.isInteger(r.total_hits) ? `${r.total_hits} hits` : "",
|
|
1076
1077
|
r.unaccounted_classes?.length ? `classes unaccounted: ${r.unaccounted_classes.join(", ")}` : "",
|
|
1077
1078
|
r.unaccounted_terms?.length ? `terms unaccounted: ${r.unaccounted_terms.join(", ")}` : "",
|
|
1078
|
-
r.receipt_reason ? `receipt: ${String(r.receipt_reason).slice(0, 160)}` : "",
|
|
1079
|
+
r.receipt_reason ? `receipt: ${refusedSearchSentence(r.receipt_reason) ?? String(r.receipt_reason).slice(0, 160)}` : "",
|
|
1079
1080
|
// what was retrieved under the crowd (register-plan.mjs, crowdNarrowing), as data beside the counts it rests on
|
|
1080
1081
|
r.narrowing ? `narrowing: ${r.narrowing}${r.lists?.length ? ` (${r.lists.slice(0, 6).join(", ")})` : ""}${r.not_covered?.length ? `; not covered: ${r.not_covered.slice(0, 12).join(", ")}` : ""}` : "",
|
|
1081
1082
|
].filter(Boolean).join("; ");
|
|
@@ -30,7 +30,7 @@ import { parsePrRiskResults, connotationObligations, renderConnotationObligation
|
|
|
30
30
|
import { obligationsSidecarPath } from "../../disposition-tool.mjs";
|
|
31
31
|
import { writeGridProvenance } from "./grid-provenance.mjs"; // — what served the grid, beside the ledger
|
|
32
32
|
import { insideRun } from "../../../shared/inside-run.mjs"; // a path a tool is handed is served only inside this run
|
|
33
|
-
import { keepResearchAnswer, researchReplayOn, savedResearchAnswer } from "../../research-answers.mjs"; // each answer's text, kept for a later attempt of its stage; a replay answers from them
|
|
33
|
+
import { keepResearchAnswer, isResearchAnswer, replayAnswersFrom, researchReplayOn, savedResearchAnswer } from "../../research-answers.mjs"; // each answer's text, kept for a later attempt of its stage; a replay answers from them
|
|
34
34
|
|
|
35
35
|
const API_KEY = process.env.PERPLEXITY_API_KEY || "";
|
|
36
36
|
|
|
@@ -222,7 +222,14 @@ function judgeBlock(cands, part, gridSpecPath, reserve = 0) {
|
|
|
222
222
|
async function research(params) {
|
|
223
223
|
// A REPLAY SENDS NOTHING OUT, so it is answered before the key is asked for: an offline replay needs none.
|
|
224
224
|
if (researchReplayOn(RUN_DIR)) return replayResearch(params);
|
|
225
|
-
|
|
225
|
+
// A REFUSED QUESTION IS LOGGED as a failed one is. It left no line, so the run's record could not tell a question
|
|
226
|
+
// refused for want of a credential from one never asked, and a writer whose every question was refused wrote
|
|
227
|
+
// "no result" with nothing in the log to say otherwise. The reason names the refusal, never the key.
|
|
228
|
+
if (!API_KEY) {
|
|
229
|
+
logCall("perplexity_research", { task: params?.task ?? "", ...(params?.grid_spec_path ? { grid: true } : {}) },
|
|
230
|
+
{ ok: false, error: "refused: no research credential" });
|
|
231
|
+
return "ERROR: PERPLEXITY_API_KEY not set — marketplace research unavailable.";
|
|
232
|
+
}
|
|
226
233
|
const { task, depth, model: modelOverride, allow_fetch = false, domain_filter, enable_sandbox = false, response_schema, schema_name, grid_spec_path, part } = params;
|
|
227
234
|
|
|
228
235
|
// ── Deterministic grid mode: the caller dictates the cells; the tool runs + captures them. ──
|
|
@@ -305,7 +312,11 @@ async function research(params) {
|
|
|
305
312
|
const data = await callAgentAPI(API_KEY, body);
|
|
306
313
|
keepRaw("question", { task, preset }, data);
|
|
307
314
|
const text = enable_sandbox ? formatSandboxResponse(data, preset) : formatResponse(data, preset);
|
|
308
|
-
|
|
315
|
+
// A REPLY THE FORMATTER COULD NOT READ IS NO ANSWER, and is logged as a failure: it was logged as answered, beside
|
|
316
|
+
// completed searches, though it is not kept and is asked again (research-answers.mjs).
|
|
317
|
+
logCall("perplexity_research", asked, isResearchAnswer(text)
|
|
318
|
+
? { ok: true, bytes: String(text ?? "").length, citations: citationCount(data) }
|
|
319
|
+
: { ok: false, error: String(text ?? "").slice(0, 200) });
|
|
309
320
|
keepResearchAnswer(RUN_DIR, { session: SESSION, agent: AGENT, task, preset, text });
|
|
310
321
|
return text;
|
|
311
322
|
} catch (err) {
|
|
@@ -321,16 +332,18 @@ async function research(params) {
|
|
|
321
332
|
//
|
|
322
333
|
// In the experiment sandbox under `--research replay` (research-answers.mjs), a question the run already asked
|
|
323
334
|
// gets the answer the run got, and any other question, a grid among them, is refused: nothing goes out to the
|
|
324
|
-
// research service. Both are logged as a lookup is, marked as a replay, so the arm's reading log says which.
|
|
335
|
+
// research service. Both are logged as a lookup is, marked as a replay, so the arm's reading log says which. The answers
|
|
336
|
+
// are read from the copy the marker names, outside the arm's folder, where the stage's own commands cannot reach them.
|
|
325
337
|
function replayResearch(params) {
|
|
326
338
|
const { task, grid_spec_path } = params ?? {};
|
|
327
339
|
if (grid_spec_path) {
|
|
328
340
|
logCall("perplexity_research", { task: task ?? "", grid: true, replay: true }, { ok: false, error: "replay: grid not replayed" });
|
|
329
341
|
return "ERROR: replay — this replays the run's saved research answers and runs no grid; nothing was sent to the research service.";
|
|
330
342
|
}
|
|
331
|
-
const
|
|
343
|
+
const from = replayAnswersFrom(RUN_DIR);
|
|
344
|
+
const saved = savedResearchAnswer(from, task);
|
|
332
345
|
if (!saved) {
|
|
333
|
-
logCall("perplexity_research", { task: task ?? "", replay: true }, { ok: false, error: "replay: no saved answer" });
|
|
346
|
+
logCall("perplexity_research", { task: task ?? "", replay: true }, { ok: false, error: from ? "replay: no saved answer" : "replay: the marker names no answers file" });
|
|
334
347
|
return "ERROR: replay — the run asked no question worded exactly like this one, so there is no saved answer to give; nothing was sent to the research service.";
|
|
335
348
|
}
|
|
336
349
|
logCall("perplexity_research", { task, replay: true }, { ok: true, bytes: saved.text.length, replayed: true });
|
|
@@ -40,25 +40,28 @@ const codexBin = () => { const r = resolveEngineProgram("openai-agent"); return
|
|
|
40
40
|
|
|
41
41
|
// tier/alias → codex `-m` model id. opus/sonnet/haiku are the driver's abstract tiers (CONTRACT §3). The
|
|
42
42
|
// GPT ids are ENV-OVERRIDABLE and default to the vendor's current models — `gpt-6.1-sol` for the judgment
|
|
43
|
-
//
|
|
44
|
-
// uses REAL, current ids rather than invented guesses, and a stage's declared tier
|
|
45
|
-
// on both engines. Until 2026-10-09 they were `gpt-5.6-sol`, `gpt-5.6-terra` and
|
|
46
|
-
// was what codex itself reported as its own default (live-probed 2026-07-27:
|
|
47
|
-
// provider: openai`), which is why the judgment tier kept it.
|
|
43
|
+
// tier, `gpt-6.1-terra` for the sweep tier and `gpt-6-luna` for the cheap tier (the GPT-6 ladder, below) —
|
|
44
|
+
// so an out-of-the-box run uses REAL, current ids rather than invented guesses, and a stage's declared tier
|
|
45
|
+
// means the same thing on both engines. Until 2026-10-09 they were `gpt-5.6-sol`, `gpt-5.6-terra` and
|
|
46
|
+
// `gpt-5.6-luna`; `sol` was what codex itself reported as its own default (live-probed 2026-07-27:
|
|
47
|
+
// `model: gpt-5.6-sol, provider: openai`), which is why the judgment tier kept it.
|
|
48
48
|
//
|
|
49
|
-
// ── THE GPT-6 LADDER (
|
|
49
|
+
// ── THE GPT-6 LADDER (2026-10-09; the sweep tier moved 2026-10-28) ──────────────────────────────────────
|
|
50
50
|
//
|
|
51
51
|
// The owner asked for the latest models on both engines, cheaper and faster. The vendor's price list,
|
|
52
52
|
// read 2026-10-09 (standard tier, per million tokens in / out): `gpt-6.1-sol` $2 / $10, `gpt-6-luna`
|
|
53
|
-
// $0.10 / $0.50, `gpt-6-astra` $10 / $50. The 5.6 ladder it
|
|
54
|
-
// luna $0.20 / $1.20.
|
|
55
|
-
//
|
|
53
|
+
// $0.10 / $0.50, `gpt-6-astra` $10 / $50. The 5.6 ladder it replaced was sol $4 / $20, terra $2 / $12,
|
|
54
|
+
// luna $0.20 / $1.20. With no GPT-6 middle model then, the sweep tier shared Sol. `gpt-6.1-terra`
|
|
55
|
+
// ($1 / $5, released 2026-10-26) is that middle model, and the owner asked again for the latest models,
|
|
56
|
+
// so the sweep tier takes it. Astra is the premium model, neither cheaper nor faster, and is not a rung.
|
|
56
57
|
//
|
|
57
58
|
// THE CODEX FLOOR MOVED WITH IT. On ChatGPT-account sign-in (subscription billing) codex 0.156.1, 0.157.0
|
|
58
59
|
// and 0.158.0 refuse `gpt-6.1-sol` with a 400 — "not supported when using Codex with a ChatGPT account" —
|
|
59
60
|
// and 0.159.0 to 0.162.0 accept it; `gpt-6-luna` is accepted from 0.156.1. Measured 2026-10-09 with
|
|
60
61
|
// one-word turns on the test box. Below 0.159.0 the judgment tier stops at its first stage, so the floor
|
|
61
|
-
// in ENGINE_BINARIES (driver.config.mjs) is 0.159.0.
|
|
62
|
+
// in ENGINE_BINARIES (driver.config.mjs) is 0.159.0. `gpt-6.1-terra` needs 0.159.0 or later by the
|
|
63
|
+
// vendor's Codex model page, and 0.162.0 served it on a ChatGPT sign-in (measured 2026-10-28), so the
|
|
64
|
+
// floor did not move for it.
|
|
62
65
|
//
|
|
63
66
|
// The August measurement below still names the symptom to watch for when a tier moves to a cheaper model.
|
|
64
67
|
//
|
|
@@ -108,7 +111,7 @@ const codexBin = () => { const r = resolveEngineProgram("openai-agent"); return
|
|
|
108
111
|
// story. They fall through to the throw below, which is where they always should have been. Concrete
|
|
109
112
|
// gpt-*/o* ids still pass through.
|
|
110
113
|
const JUDGMENT = process.env.CLEAROTRON_OPENAI_MODEL_JUDGMENT || "gpt-6.1-sol"; // opus — was gpt-5.6-sol
|
|
111
|
-
const SWEEP = process.env.CLEAROTRON_OPENAI_MODEL_SWEEP || "gpt-6.1-
|
|
114
|
+
const SWEEP = process.env.CLEAROTRON_OPENAI_MODEL_SWEEP || "gpt-6.1-terra"; // sonnet — was gpt-6.1-sol, and gpt-5.6-terra before 2026-10-09
|
|
112
115
|
const CHEAP = process.env.CLEAROTRON_OPENAI_MODEL_CHEAP || "gpt-6-luna"; // haiku — was gpt-5.6-luna
|
|
113
116
|
const OPENAI_MODEL = { opus: JUDGMENT, sonnet: SWEEP, haiku: CHEAP };
|
|
114
117
|
export function openaiModel(model) {
|
|
@@ -791,8 +791,21 @@ const ACTION_KEYS = ["id", "kind", "text", "ordinals", "deadline", "condition"];
|
|
|
791
791
|
// without them parses clean. The parser is SHAPE-only here (object / key-allowlist / source-is-a-string);
|
|
792
792
|
// the composite>=3 NON-EMPTY-source gate lives in verify.mjs checkFindingsSibling (it carries the ordinal
|
|
793
793
|
// context and is a validator-fail, not a parser-throw, on the offline unit path).
|
|
794
|
-
|
|
794
|
+
// A4 — `quality`, the optional model-attested source class, code-audited. `question` names the research question whose
|
|
795
|
+
// completed answer a no-result rests on; the writer's record joins it to the answers the writer holds (synthesis-record.mjs).
|
|
796
|
+
const USE_CHECK_KEYS = ["source", "quality", "question"];
|
|
795
797
|
export const USE_SOURCE_QUALITY = ["owner-site", "independent", "register-mirror"];
|
|
798
|
+
// D7 — `use_check.source` carries exactly ONE "we searched and found nothing" value, a code-owned sentinel the
|
|
799
|
+
// writer's message dictates. The writer's record refuses it when the run holds no completed search to have returned
|
|
800
|
+
// it, so the record must read it as the report does. MATCHED ON NORMALISED PUNCTUATION, NOT ONE SPELLING: the seat
|
|
801
|
+
// emitted a hyphen where the doctrine writes an em dash, and exact equality let the raw tool name through to a
|
|
802
|
+
// delivered report twice. Dash class and whitespace fold to one form on both sides; anything else is a source string.
|
|
803
|
+
// The frozen renderer (publish/render.mjs) keeps its own copy of this match, and a test holds the two the same.
|
|
804
|
+
export const USE_CHECK_NO_RESULT = "perplexity_research — no result";
|
|
805
|
+
export const isUseCheckNoResult = (s) => {
|
|
806
|
+
const norm = (x) => String(x ?? "").replace(/[‐-―−-]+/g, "-").replace(/\s+/g, " ").trim().toLowerCase();
|
|
807
|
+
return norm(s) === norm(USE_CHECK_NO_RESULT);
|
|
808
|
+
};
|
|
796
809
|
const OWN_RIGHTS_KEYS = ["source"];
|
|
797
810
|
const DEADLINE_KEYS = ["kind", "date"]; // #6 — a CLIENT-facing time-critical date (opposition / SOU / renewal)
|
|
798
811
|
|
package/driver/gateway.mjs
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
13
13
|
import { join, dirname, basename } from "node:path";
|
|
14
14
|
import { driverDir } from "../shared/driver-dir.mjs"; //
|
|
15
|
+
import { NO_SEARCH_RESULT, RESEARCH_CORRECTION } from "./research-answers.mjs"; // a search that did not answer supplies no result
|
|
15
16
|
import { tmpdir } from "node:os";
|
|
16
17
|
import { config, resolveModel, modelFamily, modelSnapshotKind, envOn, envGateOn, preflightEngineBinary, olderThanFloor, ENGINE_BINARIES } from "./driver.config.mjs";
|
|
17
18
|
import { probeCliVersion } from "./engine/cli-version.mjs";
|
|
@@ -2375,8 +2376,18 @@ const CLEARANCE_FINDINGS_TOKEN_RE = /(?<!knockout_|knockoutassess_)findings?_/;
|
|
|
2375
2376
|
// of the written files, and the driver then holds the record that save left: the retry sends only the change, in the
|
|
2376
2377
|
// patch the follow-ups above it ask for. A record nobody saved yet, or one whose version is being replaced, still
|
|
2377
2378
|
// takes the complete save.
|
|
2378
|
-
|
|
2379
|
+
// The patch a held record takes, in the tool's own words; the retry for most findings checks leaves the narrative as it is.
|
|
2380
|
+
const HELD_RECORD_PATCH = "ONE `record_synthesis` PATCH call, sending only the change: `findings_patch` carrying the complete corrected finding object(s), each with the `ordinal` it replaces, and `findings` carrying any register you corrected, complete, and never a `findings[]` list";
|
|
2381
|
+
const HELD_RECORD_RETRY = `Fix exactly that in ${HELD_RECORD_PATCH}; leave the narrative prose unchanged`;
|
|
2379
2382
|
const VERSION_FAILURE_RE = /finding_legacy_scale_forbidden|schema_version/;
|
|
2383
|
+
// A USE CHECK'S CORRECTION corrects what depends on the finding too, so it never says to leave the narrative as it is
|
|
2384
|
+
// (design, 2026-10-28). A held record takes the patch, in the tool's own words, with the narrative sections it changes;
|
|
2385
|
+
// a record not yet accepted is completed by its first call, which the line after every hint names. The caller prints a
|
|
2386
|
+
// full stop after the hint, so the ruled sentences go without their last one.
|
|
2387
|
+
const USE_CHECK_TOKEN_RE = /finding_use_check_(missing|source_missing):/;
|
|
2388
|
+
const useCheckCorrection = (heldRecord) => heldRecord
|
|
2389
|
+
? `${RESEARCH_CORRECTION} Send it in ${HELD_RECORD_PATCH}. In \`narrative\`, send only the sections you changed — the rest are held`
|
|
2390
|
+
: RESEARCH_CORRECTION.replace(/\.$/, "");
|
|
2380
2391
|
/**
|
|
2381
2392
|
* Does the driver hold an accepted synthesis record for the run these files belong to? Read from either of the files
|
|
2382
2393
|
* the writer's call renders: the stage's own expected file is the narrative, and a caller may name the findings.
|
|
@@ -2433,6 +2444,10 @@ export function correctionHint(lastFail, { gridLedgerName = "common-law-grid.jso
|
|
|
2433
2444
|
// The framework's method (framework-method.mjs). Each refusal already names its fix — the value list,
|
|
2434
2445
|
// or the band the table gives — so no addendum: the generic band hint below would point elsewhere.
|
|
2435
2446
|
extra = "";
|
|
2447
|
+
} else if (USE_CHECK_TOKEN_RE.test(lastFail)) {
|
|
2448
|
+
// A use status with no source behind it. Where no search completed, the honest record is an unknown use,
|
|
2449
|
+
// never a no-result, so the repair is handed the ruled sentence for a search that did not answer, unchanged.
|
|
2450
|
+
extra = ` ${NO_SEARCH_RESULT}`;
|
|
2436
2451
|
} else if (/finding_band_|findings_rated_under_|finding_disposition_missing/.test(lastFail)) {
|
|
2437
2452
|
extra = ` SPECIFIC FIX: every finding carries "disposition"; adversarial / coexistence-partner / ` +
|
|
2438
2453
|
`distinguished findings are RATED and carry "band" = EXACTLY one of the framework's band words (as ` +
|
|
@@ -2482,7 +2497,8 @@ export function correctionHint(lastFail, { gridLedgerName = "common-law-grid.jso
|
|
|
2482
2497
|
`registration — never overwrite or transpose another's facts; a finding with no fetched record → []), the four ` +
|
|
2483
2498
|
`meters as {token, basis} (basis = verified-from-record | inferred-from-signal), quadrant {x,y} in [0,1], a ` +
|
|
2484
2499
|
`typed source, a disposition, and a unique 1-based ordinal. The failed check was: ${tok}.${extra} ` +
|
|
2485
|
-
(
|
|
2500
|
+
(USE_CHECK_TOKEN_RE.test(lastFail) ? useCheckCorrection(heldRecord)
|
|
2501
|
+
: heldRecord && !VERSION_FAILURE_RE.test(lastFail) ? HELD_RECORD_RETRY
|
|
2486
2502
|
: `Fix exactly that, re-save the COMPLETE findings.json, and leave the narrative prose unchanged`);
|
|
2487
2503
|
} else if (/coverage_status_offenum/.test(lastFail)) {
|
|
2488
2504
|
// D1 — the off-enum PROSE Status cell (distinct from coverage_status_invalid, which is the JSON
|
package/driver/owner-screen.mjs
CHANGED
|
@@ -146,7 +146,7 @@ function sliceState(entry, block) {
|
|
|
146
146
|
/** Plain-English reason a slice is not an enumerated answer (null when it is). PURE. */
|
|
147
147
|
function sliceReason(entry, block, deferredReason, capabilities) {
|
|
148
148
|
if (entry?.unsupported === true)
|
|
149
|
-
return str(entry.unsupported_reason) || `the active register provider (${str(capabilities?.id) || "unknown"}) has no owner surface — this
|
|
149
|
+
return str(entry.unsupported_reason) || `the active register provider (${str(capabilities?.id) || "unknown"}) has no owner surface — this search was never carried out`;
|
|
150
150
|
if (!block) return "no band block carries this qid — the slice is unaccounted for, never a clean";
|
|
151
151
|
if (block.deferred === true) return str(deferredReason) || str(block.reason).slice(0, 300);
|
|
152
152
|
if (block.error === true) return `provider error — the slice never really ran: ${str(block.reason).slice(0, 200)}`;
|
|
@@ -37,11 +37,19 @@
|
|
|
37
37
|
// cite is enforced in that it is always PRESENT and always the driver's, never in the sense that a
|
|
38
38
|
// missing one can withhold a report. Nothing in this module throws. A provider outage produces rows that
|
|
39
39
|
// say the query did not answer, and the run publishes.
|
|
40
|
+
//
|
|
41
|
+
// ── 2026-10-28: A LOOKUP THAT DID NOT ANSWER IS NOT A "NO RESULT" ──────────────────────────────────────
|
|
42
|
+
//
|
|
43
|
+
// The row's source was that literal for a failed lookup and for a completed answer with no link alike, and the card
|
|
44
|
+
// printed it: neither shows a search that found nothing. A row now keeps the link it has and nothing in its place, and
|
|
45
|
+
// its `ok` says whether the lookup answered. The card's words for the two are the owner's (render-knockout.mjs). A
|
|
46
|
+
// reply the formatter could not read is a lookup that did not answer.
|
|
40
47
|
import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from "node:fs";
|
|
41
48
|
import { join } from "node:path";
|
|
42
49
|
import { createHash } from "node:crypto";
|
|
43
50
|
|
|
44
|
-
import { promotableRecords } from "./publish/render-knockout.mjs";
|
|
51
|
+
import { promotableRecords, ownerCheckOutcome, ownerCheckText, USE_CHECK_UNAVAILABLE } from "./publish/render-knockout.mjs";
|
|
52
|
+
import { isResearchAnswer } from "./research-answers.mjs"; // a reply the formatter could not read is no answer
|
|
45
53
|
|
|
46
54
|
/** The literal the clearance lane uses for an honest non-answer. Copied, not re-worded. */
|
|
47
55
|
export const NO_RESULT = "perplexity_research — no result";
|
|
@@ -124,8 +132,8 @@ export function firstSourceUrl(text) {
|
|
|
124
132
|
* `exec` is the sweep's own executor — injected, so the fixture path that makes this product testable at
|
|
125
133
|
* $0 covers this call too and a test never reaches a provider.
|
|
126
134
|
*
|
|
127
|
-
* Each row carries `source`: a URL when the payload gave one, otherwise
|
|
128
|
-
*
|
|
135
|
+
* Each row carries `source`: a URL when the payload gave one, otherwise null, and `ok`: whether the lookup
|
|
136
|
+
* answered. A row is written for EVERY owner owed a check, including the ones that failed — an owner with
|
|
129
137
|
* no row would be indistinguishable from an owner nobody owed a check to, which is the absence-reads-as-
|
|
130
138
|
* a-pass shape this repository keeps paying for.
|
|
131
139
|
*
|
|
@@ -154,7 +162,7 @@ export async function runOwnerChecks({ owners, exec, runDir, ledgerPath = null,
|
|
|
154
162
|
writeFileSync(join(rawDir, ownerPayloadFile(o).replace(/\.md$/, ".json")), JSON.stringify({ mark: o.mark, owner: o.owner, query, items: r.raw }, null, 1) + "\n");
|
|
155
163
|
} catch { /* the ledger row is the receipt; a raw-results write never fails a lookup */ }
|
|
156
164
|
}
|
|
157
|
-
const ok = Boolean(r?.ok && r?.text);
|
|
165
|
+
const ok = Boolean(r?.ok && isResearchAnswer(r?.text));
|
|
158
166
|
let payloadFile = null;
|
|
159
167
|
if (ok && runDir) {
|
|
160
168
|
// The payload lands beside the mark payloads, under a name derived from the row rather than the
|
|
@@ -162,7 +170,7 @@ export async function runOwnerChecks({ owners, exec, runDir, ledgerPath = null,
|
|
|
162
170
|
payloadFile = ownerPayloadFile(o);
|
|
163
171
|
try { writeFileSync(join(runDir, "research", payloadFile), r.text); } catch { payloadFile = null; }
|
|
164
172
|
}
|
|
165
|
-
const source = ok ?
|
|
173
|
+
const source = ok ? firstSourceUrl(r.text) : null;
|
|
166
174
|
const row = {
|
|
167
175
|
mark: o.mark, owner: o.owner, recordIds: o.recordIds, classes: o.classes,
|
|
168
176
|
query, preset, ok, source, payloadFile,
|
|
@@ -206,7 +214,8 @@ export function useCheckLineFor(ownerChecks, recordId) {
|
|
|
206
214
|
const row = (Array.isArray(ownerChecks) ? ownerChecks : [])
|
|
207
215
|
.find((c) => (Array.isArray(c?.recordIds) ? c.recordIds : []).some((x) => String(x).trim() === id));
|
|
208
216
|
if (!row) return null;
|
|
209
|
-
|
|
217
|
+
const o = ownerCheckOutcome(row);
|
|
218
|
+
return o.kind === "unavailable" ? USE_CHECK_UNAVAILABLE : `${USE_CHECK_LABEL} ${ownerCheckText(o)}`;
|
|
210
219
|
}
|
|
211
220
|
|
|
212
221
|
/** Read the store a run wrote, tolerantly. `[]` when the lane did not run — never a throw. */
|
package/driver/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "clearotron-driver",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.5.0-beta.
|
|
5
|
+
"version": "0.5.0-beta.6",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
7
7
|
"description": "Deterministic driver for the trademark clearance workflow: orchestration in code (fan-out, fan-in barrier, gating, retries); the model does judgment leaves only, through a reasoning CLI spawned per stage.",
|
|
8
8
|
"engines": {
|