clearotron 0.3.2-beta.8 → 0.3.2-beta.9
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 +34 -3
- package/CONTRIBUTING.md +8 -8
- package/INSTALL.md +6 -6
- package/SECURITY.md +3 -3
- package/bin/brandowner.mjs +3 -3
- package/bin/framework-preflight.mjs +1 -1
- package/bin/start.mjs +18 -4
- package/build-info.json +2 -2
- package/docs/DELIVERY.md +2 -1
- package/docs/INTAKE.md +1 -1
- package/docs/ONBOARDING.md +1 -1
- package/docs/architecture/03-run-lifecycle.md +6 -6
- package/docs/architecture/04-configuration-reference.md +6 -3
- package/docs/architecture/05-config-governance.md +6 -1
- package/docs/architecture/05-customer-profiles.md +2 -2
- package/docs/architecture/06-operations-runbook.md +3 -3
- package/docs/architecture/08-development-guide.md +6 -6
- package/docs/configuration.md +5 -5
- package/docs/decisions/0003-credential-model.md +1 -1
- package/docs/writing-standard.md +4 -0
- package/driver/CHANGELOG.md +48 -0
- package/driver/README.md +3 -3
- package/driver/binding-layers.mjs +1 -1
- package/driver/citation-census.json +3 -3
- package/driver/{prelim-variants-record.mjs → clearance-variants-record.mjs} +24 -24
- package/driver/common-law-receipts.mjs +2 -2
- package/driver/company-bundle.mjs +3 -3
- package/driver/compose-read.mjs +8 -14
- package/driver/consumption-ledger.mjs +2 -2
- package/driver/contract-arm2-baseline.json +2 -3
- package/driver/contract-dictation-registry.mjs +19 -19
- package/driver/contract-e3-backlog.mjs +19 -19
- package/driver/contract-e3-baseline.json +14 -14
- package/driver/contract-vocabulary.mjs +24 -17
- package/driver/deliver-trigger.sh +16 -16
- package/driver/demo-container.mjs +3 -3
- package/driver/dev-portal.mjs +3 -3
- package/driver/disposition-call.mjs +1 -1
- package/driver/doubt-ledger.mjs +2 -2
- package/driver/drainer-identity.mjs +34 -8
- package/driver/driver.config.mjs +95 -45
- package/driver/engine/mcp/README.md +1 -1
- package/driver/engine/mcp/dispositions-server.mjs +3 -3
- package/driver/engine/mcp/gather-config.mjs +9 -9
- package/driver/engine/mcp/perplexity-server.mjs +2 -2
- package/driver/engine/mcp/recording-server.mjs +5 -5
- package/driver/enqueue-schema.mjs +6 -2
- package/driver/findings-model.mjs +5 -2
- package/driver/flag-snapshot.mjs +6 -3
- package/driver/form-neighbourhood.mjs +54 -7
- package/driver/framework.mjs +4 -4
- package/driver/gateway.mjs +12 -6
- package/driver/jx-lanes.mjs +2 -2
- package/driver/jx-units.mjs +1 -1
- package/driver/jx.mjs +30 -2
- package/driver/knockout-review-record.mjs +56 -4
- package/driver/known-conflicts.mjs +1 -1
- package/driver/named-band.mjs +1 -33
- package/driver/ordinary-words.mjs +51 -0
- package/driver/outbox-backoff.mjs +31 -16
- package/driver/package.json +1 -1
- package/driver/partial-payload-baseline.json +2 -2
- package/driver/phase0.mjs +3 -3
- package/driver/pipeline-knockout.mjs +5 -5
- package/driver/pipeline.mjs +206 -68
- package/driver/placement-form.mjs +77 -1
- package/driver/placement-model.mjs +1 -1
- package/driver/portal-report.mjs +92 -5
- package/driver/portal-service.mjs +34 -8
- package/driver/portal-upstream.mjs +1 -1
- package/driver/preserve-merge.mjs +3 -3
- package/driver/product-rows.mjs +2 -2
- package/driver/products.mjs +1 -1
- package/driver/profiles/README.md +3 -3
- package/driver/profiles/demo-brand-owner.json +2 -2
- package/driver/profiles.mjs +55 -17
- package/driver/progress.mjs +18 -8
- package/driver/provider-usage.mjs +8 -8
- package/driver/publish/index.mjs +110 -5
- package/driver/publish/knockout.mjs +29 -4
- package/driver/publish/pool-admin.mjs +1 -1
- package/driver/publish/publish-inputs.mjs +18 -2
- package/driver/publish/render-knockout.mjs +115 -24
- package/driver/publish/render.mjs +153 -34
- package/driver/publish/search-depth.mjs +133 -4
- package/driver/publish/templates/report.css +60 -3
- package/driver/publish/xlsx.mjs +8 -1
- package/driver/queue-order.mjs +2 -2
- package/driver/recording-agreement.mjs +1 -1
- package/driver/reference-score.mjs +1 -1
- package/driver/register-count.mjs +50 -5
- package/driver/register-coverage.mjs +67 -0
- package/driver/register-grant-vocabulary.mjs +1 -1
- package/driver/register-plan.mjs +19 -2
- package/driver/registry-fidelity.mjs +3 -3
- package/driver/repair-composers.mjs +1 -1
- package/driver/repair-contract.mjs +1 -1
- package/driver/replay-archive.mjs +6 -6
- package/driver/report-overview-record.mjs +2 -2
- package/driver/run-requirements.mjs +3 -3
- package/driver/runner.mjs +2 -2
- package/driver/scope-facts.mjs +20 -5
- package/driver/scope-ledger.mjs +5 -5
- package/driver/search-policy.mjs +22 -12
- package/driver/skills/README.md +15 -15
- package/driver/skills/blind-frame/SKILL.md +2 -2
- package/driver/skills/case-law-citation/SKILL.md +4 -4
- package/driver/skills/case-law-citation/sources/eurlex.md +1 -1
- package/driver/skills/{prelim-common-law → clearance-common-law}/SKILL.md +22 -22
- package/driver/skills/{prelim-common-law → clearance-common-law}/perplexity-prompts.md +1 -1
- package/driver/skills/{prelim-register → clearance-register}/SKILL.md +10 -10
- package/driver/skills/{prelim-register → clearance-register}/digest.md +2 -2
- package/driver/skills/{prelim-register → clearance-register}/providers/README.md +1 -1
- package/driver/skills/{prelim-register → clearance-register}/providers/clarivate.md +37 -35
- package/driver/skills/{prelim-register → clearance-register}/providers/corsearch.md +20 -11
- package/driver/skills/{prelim-register → clearance-register}/providers/signa.md +5 -5
- package/driver/skills/{prelim-register → clearance-register}/register-recipes.md +3 -3
- package/driver/skills/{prelim-register → clearance-register}/status-rules.md +2 -2
- package/driver/skills/{prelim-register → clearance-register}/stealth-filer-indicators.md +1 -1
- package/driver/skills/{prelim-register → clearance-register}/unit.md +2 -2
- package/driver/skills/{prelim-search → clearance-search}/SKILL.md +31 -31
- package/driver/skills/{prelim-search → clearance-search}/delivery-contract.md +1 -1
- package/driver/skills/{prelim-search → clearance-search}/phase2-execution.md +18 -18
- package/driver/skills/{prelim-search → clearance-search}/synthesis-rules.md +7 -7
- package/driver/skills/{prelim-variants → clearance-variants}/SKILL.md +18 -18
- package/driver/skills/{prelim-variants → clearance-variants}/transliteration-scripts.md +5 -5
- package/driver/skills/frame-diff/SKILL.md +1 -1
- package/driver/skills/knockout-assess/SKILL.md +10 -7
- package/driver/skills/matter-frame/SKILL.md +3 -3
- package/driver/skills/narrative-refutation/SKILL.md +9 -9
- package/driver/skills/placement-inquiry/SKILL.md +5 -5
- package/driver/stage-context.mjs +1 -1
- package/driver/stages-knockout.mjs +4 -4
- package/driver/stages.mjs +53 -53
- package/driver/status-snapshot.mjs +2 -2
- package/driver/suite-census.json +218 -92
- package/driver/surface-exit-verdict.mjs +58 -0
- package/driver/systemd/README.md +2 -2
- package/driver/systemd/clearotron-worker.service +1 -1
- package/driver/terminal-clamp.mjs +2 -0
- package/driver/usage-ledger.mjs +1 -1
- package/driver/variant-manifest-model.mjs +4 -4
- package/driver/verify-knockout.mjs +27 -0
- package/driver/verify.mjs +67 -6
- package/driver/whatif-queue.mjs +1 -1
- package/driver/wordlists/en.txt +63906 -0
- package/mcp-server/CHANGELOG.md +4 -0
- package/mcp-server/README.md +1 -1
- package/mcp-server/lib/README.md +1 -1
- package/mcp-server/lib/options.mjs +8 -7
- package/mcp-server/lib/plan.mjs +18 -2
- package/mcp-server/lib/runs.mjs +1 -1
- package/mcp-server/lib/usage.mjs +3 -3
- package/mcp-server/lib/whatif.mjs +1 -1
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +3 -0
- package/package.json +12 -11
- package/portal-ui/dist/assets/{index-CVOIvdhc.css → index-CtvwLCti.css} +207 -3
- package/portal-ui/dist/assets/{index-6jzO9HiX.js → index-EVaSo5-g.js} +1459 -482
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/README.md +1 -1
- package/providers/_shared/enumerate.mjs +6 -6
- package/providers/_shared/execute-plan.mjs +3 -3
- package/providers/_shared/ledger.mjs +119 -5
- package/providers/_shared/provider-text.mjs +2 -2
- package/providers/_shared/screen.mjs +2 -2
- package/providers/_shared/script-form.mjs +3 -3
- package/providers/_shared/territory-codes.mjs +23 -3
- package/providers/clarivate/README.md +1 -1
- package/providers/clarivate/src/capabilities.js +12 -12
- package/providers/clarivate/src/core.js +37 -43
- package/providers/corsearch/README.md +1 -1
- package/providers/corsearch/src/capabilities.js +5 -5
- package/providers/corsearch/src/core.js +3 -3
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/perplexity/src/core.js +1 -1
- package/providers/signa/README.md +1 -1
- package/providers/signa/src/capabilities.js +42 -49
- package/providers/signa/src/core.js +106 -29
- package/providers/uspto-local/src/sync.js +1 -1
- package/scripts/README.md +1 -0
- package/scripts/ask-ai-render-check.mjs +127 -1
- package/scripts/authority-boundary-probe.mjs +4 -4
- package/scripts/backfill-started-at.mjs +2 -2
- package/scripts/census-merge-driver.mjs +33 -2
- package/scripts/citation-anchor-report.mjs +181 -0
- package/scripts/dead-names.mjs +1 -1
- package/scripts/deprecate-below.mjs +66 -8
- package/scripts/drain-preflight.mjs +1 -1
- package/scripts/e2e.mjs +174 -0
- package/scripts/env-audit.mjs +27 -0
- package/scripts/env-classify.mjs +20 -2
- package/scripts/freeze-example-run.mjs +20 -10
- package/scripts/live-surface-check.mjs +124 -41
- package/scripts/markdown-link-check.mjs +1 -1
- package/scripts/merge-shape-check.mjs +242 -0
- package/scripts/mint-names-in-force.mjs +5 -5
- package/scripts/mint-offered-territories.mjs +72 -0
- package/scripts/mint-public-residue.mjs +2 -2
- package/scripts/mint-reference-strip-backlog.mjs +2 -2
- package/scripts/mint-suite-census.mjs +75 -2
- package/scripts/mint-writing-standard-backlog.mjs +2 -2
- package/scripts/purge-runs.mjs +7 -7
- package/scripts/reconcile-runs.mjs +2 -2
- package/scripts/release-approve-parked.mjs +20 -2
- package/scripts/release-await-cut.mjs +120 -1
- package/scripts/release-note-required.mjs +76 -8
- package/scripts/report-header-render-check.mjs +164 -0
- package/shared/brand.mjs +27 -0
- package/shared/connect-clients.mjs +39 -11
- package/shared/env-aliases.mjs +1 -1
- package/shared/identifier-scan.mjs +65 -9
- package/shared/identifier-sentinels.mjs +22 -0
- package/shared/names-in-force.mjs +3 -1
- package/shared/offered-territories.json +738 -0
- package/shared/pre-rename-spellings.mjs +53 -0
- package/shared/reference-guard-classes.mjs +40 -2
- package/shared/stdio-connect.mjs +39 -4
- package/shared/tree-commit.mjs +48 -0
- /package/driver/skills/{prelim-register → clearance-register}/providers/euipo.md +0 -0
- /package/driver/skills/{prelim-register → clearance-register}/providers/free-tier.md +0 -0
- /package/driver/skills/{prelim-register → clearance-register}/providers/uspto-local.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/field-doctrine-pharma.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/firm-wide-reasoning.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/report-prose.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework-demo.manifest.json +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework-demo.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework-triage.manifest.json +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework-triage.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework.manifest.json +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/risk-framework.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/template-formatting.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/templates/email/generic.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/templates/search-request-form.html +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/worked-examples-demo.md +0 -0
- /package/driver/skills/{prelim-search → clearance-search}/worked-examples.md +0 -0
|
@@ -105,14 +105,40 @@ export function drainerVerdict({ stamp, headCommit, isAlive, processes, ppidOf =
|
|
|
105
105
|
const nameThem = (ps) => ps.map((p) => `pid ${p.pid}`).join(", ");
|
|
106
106
|
|
|
107
107
|
if (!stamp) {
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
// THREE SITUATIONS LIVED IN ONE FAIL, AND ONE OF THEM IS NOT A FINDING.
|
|
109
|
+
//
|
|
110
|
+
// All three begin the same way — no stamp, so what the executing process holds was never
|
|
111
|
+
// established. What separates them is the PROCESS TABLE, and the old message ran the answers
|
|
112
|
+
// together: it asserted "nothing is executing runs on this box", a finding, and closed with "This is
|
|
113
|
+
// a failure to look, never a pass", which is the opposite claim. A reader could act on either.
|
|
114
|
+
//
|
|
115
|
+
// The module's ruling about postures says what each exit's posture IS. It does not license reporting
|
|
116
|
+
// two different facts through one verdict, and a deployment check is exactly where that is
|
|
117
|
+
// expensive: redeploying fixes an idle box and does nothing at all for an unreadable process table.
|
|
118
|
+
//
|
|
119
|
+
// — NOTHING WAS ESTABLISHED IN EITHER DIRECTION. No stamp and no process table: this arm did not
|
|
120
|
+
// find an idle box, it failed to look at one. Skipped and marked, never passed — the caller counts
|
|
121
|
+
// it toward a different exit code, and the box could be perfectly healthy or completely wedged.
|
|
122
|
+
if (seen === null) {
|
|
123
|
+
return { state: "skip", blocked: true,
|
|
124
|
+
message: `no drainer identity stamp at ${STAMP_BASENAME} AND the process table could not be read: what the `
|
|
125
|
+
+ "process that executes runs holds was not established, and neither was whether one is running at all. "
|
|
126
|
+
+ `This is a failure to look — redeploying would change nothing, because nothing was compared.${postureNote}` };
|
|
127
|
+
}
|
|
128
|
+
// — THE INCIDENT'S OWN SHAPE. Drainer-shaped processes are running and none of them stamped
|
|
129
|
+
// anything, so what build they hold is unknown. This is the state the orphaned drainer was in and
|
|
130
|
+
// the reason this arm exists; it is a finding and stays one.
|
|
131
|
+
if (seen.length) {
|
|
132
|
+
return { state: "fail", message: `no drainer identity stamp at ${STAMP_BASENAME} while ${seen.length} `
|
|
133
|
+
+ `drainer-shaped process(es) ARE running (${nameThem(seen)}) — unstamped, so what build they hold is `
|
|
134
|
+
+ `unknown. The process table was read; this is what it said.${postureNote}` };
|
|
135
|
+
}
|
|
136
|
+
// — A FINDING, and the plainest one here. The process table WAS read and holds no drainer, so
|
|
137
|
+
// nothing is executing runs on this box. Saying "failure to look" about a table that answered was
|
|
138
|
+
// the contradiction at the centre of this.
|
|
139
|
+
return { state: "fail", message: `no drainer identity stamp at ${STAMP_BASENAME} and no drainer-shaped process `
|
|
140
|
+
+ "is running, so nothing is executing runs on this box. The process table was read, and this is what "
|
|
141
|
+
+ `it said.${postureNote}` };
|
|
116
142
|
}
|
|
117
143
|
|
|
118
144
|
const pid = Number(stamp.pid) || 0;
|
package/driver/driver.config.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
2
|
// Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
|
|
3
|
-
// Central paths + tunables for the
|
|
3
|
+
// Central paths + tunables for the clearance-search deterministic driver.
|
|
4
4
|
//
|
|
5
5
|
// The driver runs as an ordinary UNIX service account (launched by systemd), NOT as an LLM agent.
|
|
6
6
|
// The agent exec-deny is a gateway agent-tool restriction; it does not apply to this OS process.
|
|
@@ -15,7 +15,7 @@ import { isWsl } from "../shared/wsl.mjs"; // — the one answer to "is this L
|
|
|
15
15
|
import { envFrom } from "../shared/env-aliases.mjs"; // — an operator-facing name is the one an operator sets, and it has to work where they set it; — envFrom is the resolver that reads every spelling of it
|
|
16
16
|
import { invoke } from "../shared/invocation.mjs"; // — name a command the reader can actually type
|
|
17
17
|
import { envFileRead } from "../shared/env-local.mjs"; // — WHICH file to set it in, measured; null for a service that read none
|
|
18
|
-
import { numericSetting, resolveNumericSetting } from "./numeric-setting.mjs"; // — a number, or a refusal that names the variable; never NaN
|
|
18
|
+
import { numericSetting, resolveNumericSetting } from "./numeric-setting.mjs"; import { STUDIO_SEGMENTS, STUDIO_SEGMENT_RE, studioSegmentFor } from "../shared/pre-rename-spellings.mjs"; export { STUDIO_SEGMENTS, STUDIO_SEGMENT_RE, studioSegmentFor }; // the studio segment an install keeps its runs under // — a number, or a refusal that names the variable; never NaN
|
|
19
19
|
|
|
20
20
|
const { X_OK } = FS;
|
|
21
21
|
|
|
@@ -111,6 +111,23 @@ export const envGateOn = (name) => {
|
|
|
111
111
|
// so import-time captures silently pinned every test to the FIRST test's env (workspace root, pool,
|
|
112
112
|
// retries) — the root of the intermittent cross-test contamination flake. Getters make "set env, then
|
|
113
113
|
// run" mean what it says, in tests and in prod alike.
|
|
114
|
+
// The four skill folders the identifier rename moved. A store outside the product may still hold them,
|
|
115
|
+
// and a profile may still name them, under the old spelling.
|
|
116
|
+
const RENAMED_SKILL_DIRS = Object.freeze([
|
|
117
|
+
["clearance-search", "prelim-search"], ["clearance-register", "prelim-register"],
|
|
118
|
+
["clearance-common-law", "prelim-common-law"], ["clearance-variants", "prelim-variants"],
|
|
119
|
+
]);
|
|
120
|
+
/** `[rel, the same path under the folder's other spelling | null]`. PURE. */
|
|
121
|
+
export function skillSpellings(rel) {
|
|
122
|
+
const m = /^skills\/([a-z-]+)(\/.*)?$/.exec(String(rel ?? ""));
|
|
123
|
+
if (!m) return [rel, null];
|
|
124
|
+
for (const [now, before] of RENAMED_SKILL_DIRS) {
|
|
125
|
+
if (m[1] === now) return [rel, `skills/${before}${m[2] ?? ""}`];
|
|
126
|
+
if (m[1] === before) return [rel, `skills/${now}${m[2] ?? ""}`];
|
|
127
|
+
}
|
|
128
|
+
return [rel, null];
|
|
129
|
+
}
|
|
130
|
+
|
|
114
131
|
export const config = {
|
|
115
132
|
// / — A BLANK VALUE IS NOT A CONFIGURED VALUE. `process.env.X || default` treats " " as
|
|
116
133
|
// configured, because a whitespace-only string is TRUTHY in JavaScript — so a variable set to spaces
|
|
@@ -212,21 +229,8 @@ export const config = {
|
|
|
212
229
|
* still fails loudly against the canonical location rather than silently against the overlay.
|
|
213
230
|
*/
|
|
214
231
|
resolveSkillPath(relFromSkillsRoot) {
|
|
215
|
-
|
|
216
|
-
const overlay = this.skillsOverlayDir;
|
|
217
|
-
if (overlay) {
|
|
218
|
-
// FAIL LOUD ON AN UNREADABLE OVERLAY. existsSync() answers false for a permission error just as it
|
|
219
|
-
// does for a missing file, so a config store the process cannot read would silently resolve EVERY
|
|
220
|
-
// file to the repo — swapping a customer's own risk framework for the Generic default with nothing
|
|
221
|
-
// in the log to say so. A configured-but-unreadable overlay is a deploy defect, not a fallback.
|
|
222
|
-
if (!existsSync(overlay))
|
|
223
|
-
throw new Error(`skills_overlay_unreadable:${overlay} (CLEAROTRON_INSTRUCTIONS_DIR names it, set by the operator or derived by the portal from PROFILE_REPO_ROOT, but this process cannot see it — customer-specific skills would silently fall back to the repo defaults)`);
|
|
224
|
-
const p = join(dirname(overlay), rel);
|
|
225
|
-
if (existsSync(p)) return p;
|
|
226
|
-
}
|
|
227
|
-
return join(dirname(this.skillsBaseDir), rel);
|
|
232
|
+
return this.resolveSkillPathReport(relFromSkillsRoot).path;
|
|
228
233
|
},
|
|
229
|
-
|
|
230
234
|
/**
|
|
231
235
|
* WHICH LAYER ANSWERED, as a fact rather than a path — `resolveSkillPath` with its reasoning shown.
|
|
232
236
|
*
|
|
@@ -252,13 +256,24 @@ export const config = {
|
|
|
252
256
|
*/
|
|
253
257
|
resolveSkillPathReport(relFromSkillsRoot) {
|
|
254
258
|
const rel = String(relFromSkillsRoot ?? "").replace(/^\/+/, "");
|
|
255
|
-
|
|
259
|
+
// EITHER SPELLING OF A RENAMED SKILL FOLDER, in either direction. A store that has not moved its
|
|
260
|
+
// folders still holds `skills/prelim-search/…`, and a profile written before the rename still NAMES
|
|
261
|
+
// it; the product ships only the new names. Tried in the overlay first — a client's own doctrine must
|
|
262
|
+
// never be silently replaced by ours because a folder name changed — and the base answers only under
|
|
263
|
+
// the name it ships.
|
|
264
|
+
const [asNamed, other] = skillSpellings(rel);
|
|
265
|
+
const shipped = rel === asNamed && other && /^skills\/prelim-/.test(asNamed) ? other : asNamed;
|
|
266
|
+
const basePath = join(dirname(this.skillsBaseDir), shipped);
|
|
256
267
|
const overlay = this.skillsOverlayDir;
|
|
257
268
|
if (!overlay) return { path: basePath, rel, layer: existsSync(basePath) ? "base-only" : "missing", overlayPath: null, basePath };
|
|
258
269
|
if (!existsSync(overlay))
|
|
259
270
|
throw new Error(`skills_overlay_unreadable:${overlay} (CLEAROTRON_INSTRUCTIONS_DIR names it, set by the operator or derived by the portal from PROFILE_REPO_ROOT, but this process cannot see it — customer-specific skills would silently fall back to the repo defaults)`);
|
|
260
|
-
const overlayPath = join(dirname(overlay),
|
|
271
|
+
const overlayPath = join(dirname(overlay), asNamed);
|
|
261
272
|
if (existsSync(overlayPath)) return { path: overlayPath, rel, layer: "overlay", overlayPath, basePath };
|
|
273
|
+
if (other) {
|
|
274
|
+
const otherPath = join(dirname(overlay), other);
|
|
275
|
+
if (existsSync(otherPath)) return { path: otherPath, rel, layer: "overlay", overlayPath: otherPath, basePath };
|
|
276
|
+
}
|
|
262
277
|
return { path: basePath, rel, layer: existsSync(basePath) ? "base" : "missing", overlayPath, basePath };
|
|
263
278
|
},
|
|
264
279
|
|
|
@@ -276,7 +291,7 @@ export const config = {
|
|
|
276
291
|
return roots;
|
|
277
292
|
},
|
|
278
293
|
|
|
279
|
-
// The base that a profile's "skills/
|
|
294
|
+
// The base that a profile's "skills/clearance-search/<file>.md" path is relative to — i.e. the PARENT
|
|
280
295
|
// of skillsDir. Everything the DRIVER reads itself (framework manifests, band-meaning extraction)
|
|
281
296
|
// must join against this, exactly as the agent resolves the same relative paths against the
|
|
282
297
|
// skillsDir it is handed (gateway.mjs engineSkillsDir).
|
|
@@ -305,15 +320,17 @@ export const config = {
|
|
|
305
320
|
},
|
|
306
321
|
// Escaped prefix for the reverse regexes below (a custom prefix may carry regex metachars).
|
|
307
322
|
get workspacePrefixRe() { return this.workspacePrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); },
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
//
|
|
323
|
+
// THE STUDIO SEGMENT IS NOT A PRODUCT NAME and does not follow the product rename. It is a directory
|
|
324
|
+
// segment on disk, and every archived run — its slug dirs, its `archive/`, its queue, its matter ledger —
|
|
325
|
+
// was written under it. Renaming it does not move those runs; it points the reader somewhere empty, and
|
|
326
|
+
// an empty directory reads as "no runs" rather than as an error. The identifier rename rewrote this
|
|
327
|
+
// comment together with the thirteen sites it protected, and an upgraded install lost sight of its whole
|
|
328
|
+
// archive. So an install keeps the segment it has: `prelim-search` wherever that directory exists, and
|
|
329
|
+
// `clearance-search` only for an install that has no other. Every site asks `studioSegmentFor`.
|
|
330
|
+
studioSegment(workspaceDir) { return studioSegmentFor(workspaceDir); },
|
|
315
331
|
studioRootForAgent(agentId) {
|
|
316
|
-
|
|
332
|
+
const ws = join(this.workspaceRoot, this.workspaceDirName(agentId));
|
|
333
|
+
return join(ws, "studio", studioSegmentFor(ws));
|
|
317
334
|
},
|
|
318
335
|
queueDirForAgent(agentId) {
|
|
319
336
|
return join(this.studioRootForAgent(agentId), "queue");
|
|
@@ -321,9 +338,10 @@ export const config = {
|
|
|
321
338
|
archiveRootForAgent(agentId) {
|
|
322
339
|
return join(this.studioRootForAgent(agentId), "archive");
|
|
323
340
|
},
|
|
324
|
-
// …/<prefix><id>/studio
|
|
341
|
+
// …/<prefix><id>/studio/<segment>/queue → "<id>"; null if the path isn't an agent queue dir. Either
|
|
342
|
+
// spelling of the segment, because an install keeps the one it has.
|
|
325
343
|
agentIdFromQueueDir(qdir) {
|
|
326
|
-
const m = new RegExp(`(?:^|/)${this.workspacePrefixRe}([^/]+)/studio/
|
|
344
|
+
const m = new RegExp(`(?:^|/)${this.workspacePrefixRe}([^/]+)/studio/${STUDIO_SEGMENT_RE}/queue/?$`).exec(qdir);
|
|
327
345
|
return m ? m[1] : null;
|
|
328
346
|
},
|
|
329
347
|
// Every agent workspace's clearotron queue. The systemd `.path` watches these and the runner drains ALL of
|
|
@@ -343,14 +361,18 @@ export const config = {
|
|
|
343
361
|
try {
|
|
344
362
|
for (const name of readdirSync(root)) {
|
|
345
363
|
if (this.agentIdFromWorkspaceName(name) == null) continue;
|
|
346
|
-
|
|
347
|
-
|
|
364
|
+
// BOTH spellings are drained where both exist: a job queued under either is a job somebody is
|
|
365
|
+
// waiting on, and an unwatched queue looks exactly like an empty one.
|
|
366
|
+
for (const seg of STUDIO_SEGMENTS) {
|
|
367
|
+
const q = join(root, name, "studio", seg, "queue");
|
|
368
|
+
if (existsSync(q)) dirs.push(q);
|
|
369
|
+
}
|
|
348
370
|
}
|
|
349
371
|
} catch { /* workspaceRoot may not exist in some test envs — fall through to the canonical queue */ }
|
|
350
372
|
// THE ONE AGENT NAME NO CONFIGURATION REMOVES, and it is deliberate. row 5.
|
|
351
373
|
//
|
|
352
374
|
// `this.queueDir` is `queueDirForAgent("clawdi")` — a LITERAL, not `defaultAgent` — so every
|
|
353
|
-
// deployment, however configured, watches `<workspacePrefix>clawdi/studio/
|
|
375
|
+
// deployment, however configured, watches `<workspacePrefix>clawdi/studio/clearance-search/queue`.
|
|
354
376
|
// Setting CLEAROTRON_DEFAULT_AGENT does not remove it (prod runs `ops`, dev runs `dev`, and both still
|
|
355
377
|
// watch this); nor does CLEAROTRON_WORKSPACE_PREFIX. An installer who copies `.env.example` gets a
|
|
356
378
|
// neutral default agent AND this directory.
|
|
@@ -540,7 +562,7 @@ export const config = {
|
|
|
540
562
|
// directory, so renaming the default moves the install to an empty one and nothing migrates. Here
|
|
541
563
|
// the orphaned files are run-slot locks, so a live run's slot goes unseen and the global cap is
|
|
542
564
|
// silently exceeded rather than enforced. Ruling.
|
|
543
|
-
get runLockDir() { return this.envValue("CLEAROTRON_RUN_LOCK_DIR") || join(this.workspaceRoot, "prelim-run-locks"); },
|
|
565
|
+
get runLockDir() { return this.envValue("CLEAROTRON_RUN_LOCK_DIR") || [join(this.workspaceRoot, "prelim-run-locks"), join(this.workspaceRoot, "clearance-run-locks")].find((d, i) => i === 1 || existsSync(d)); }, // the name the install already has wins
|
|
544
566
|
|
|
545
567
|
// Delivery outbox (Workstream B). On a handoff-mode finish the driver drops <runId>.pending here (naming
|
|
546
568
|
// the forwarder agent); the systemd-user prelim-outbox.path unit fires an INSTANT clearotron-deliver wake off
|
|
@@ -552,7 +574,31 @@ export const config = {
|
|
|
552
574
|
// directory, so renaming the default moves the install to an empty one and nothing migrates. Here
|
|
553
575
|
// the orphaned files are requester-facing events — delivered, run-failed, intake-rejected — so the
|
|
554
576
|
// visible failure is a requester never told their run finished. Ruling.
|
|
555
|
-
get outboxDir() { return this.envValue("CLEAROTRON_OUTBOX_DIR") || join(this.workspaceRoot, "
|
|
577
|
+
get outboxDir() { return this.envValue("CLEAROTRON_OUTBOX_DIR") || join(this.workspaceRoot, "clearance-outbox"); },
|
|
578
|
+
|
|
579
|
+
// ── THE DIRECTORY THIS DEFAULT USED TO NAME, AND WHY IT IS STILL READ ──────────────────────────────
|
|
580
|
+
//
|
|
581
|
+
// This default was `clearance-outbox` until the identifier was renamed. A deployment that never pinned
|
|
582
|
+
// `CLEAROTRON_OUTBOX_DIR` has its `<runId>.pending` markers sitting in the old directory, and moving
|
|
583
|
+
// the default without reading the old one orphans every one of them. That failure is silent and it is
|
|
584
|
+
// the expensive kind: a marker is a report a client is OWED, an unread outbox is indistinguishable
|
|
585
|
+
// from nothing to send, and the run that produced it has already recorded itself as delivered-pending.
|
|
586
|
+
//
|
|
587
|
+
// NULL WHEN THE VARIABLE IS SET, because then the operator named the directory and there is no old
|
|
588
|
+
// default in play. Writers use `outboxDir` alone; only readers consult this, which is the same posture
|
|
589
|
+
// the run records take — new work uses the new name, old work is still understood.
|
|
590
|
+
//
|
|
591
|
+
// THE OLD SPELLING IS THE WHOLE POINT OF THIS ACCESSOR, and a sweep took it. The identifier rename
|
|
592
|
+
// moved the directory from `prelim-outbox` to `clearance-outbox`, and the follow-up that renamed the
|
|
593
|
+
// directory's 85 occurrences rewrote this literal along with them — leaving the accessor that exists to
|
|
594
|
+
// name the OLD directory naming the new one. Both getters then answered the same path: the drain read
|
|
595
|
+
// the new directory twice, never read the old one, and every marker under it was listed TWICE. So a box
|
|
596
|
+
// that never pinned the variable got exactly the failure the block above describes — orphaned markers,
|
|
597
|
+
// silently — plus duplicate work on the ones it could see. It is written once, here, and pinned by a
|
|
598
|
+
// test that asserts the two are different.
|
|
599
|
+
get legacyOutboxDir() {
|
|
600
|
+
return this.envValue("CLEAROTRON_OUTBOX_DIR") ? null : join(this.workspaceRoot, "prelim-outbox");
|
|
601
|
+
},
|
|
556
602
|
|
|
557
603
|
// ── Delivery/comms (Phase 2, standalone product) ─────────────────────────────────────────────────
|
|
558
604
|
// THERE IS ONE MODE AND IT IS NOT A SETTING. The driver SENDS NOTHING: every requester-facing event
|
|
@@ -570,7 +616,7 @@ export const config = {
|
|
|
570
616
|
// LOCATION (see studioRootForAgent / agentIdFromQueueDir above).
|
|
571
617
|
//
|
|
572
618
|
// THE DEFAULT IS PART OF A PATH, so changing it moves where an install looks for its own runs:
|
|
573
|
-
// every run dir is `<workspaceRoot>/workspace-<agent>/studio/
|
|
619
|
+
// every run dir is `<workspaceRoot>/workspace-<agent>/studio/clearance-search/…`. An install created
|
|
574
620
|
// before this default changed keeps its runs under the old id and must pin it — both spellings,
|
|
575
621
|
// because the gather servers read their own variable:
|
|
576
622
|
//
|
|
@@ -939,7 +985,7 @@ export const PROVIDERS = {
|
|
|
939
985
|
id: "corsearch",
|
|
940
986
|
label: "Corsearch",
|
|
941
987
|
credEnv: "CORSEARCH_SESSION_KEY",
|
|
942
|
-
skillDoc: "skills/
|
|
988
|
+
skillDoc: "skills/clearance-register/providers/corsearch.md",
|
|
943
989
|
hasPublicRecordUrl: true,
|
|
944
990
|
// WP-receipts W2: the public per-record origin (publicRecordOrigin + /mark/<jur>/<id> is a working
|
|
945
991
|
// link) — replaces the fragile resolved-link-origin inference at render for receipt-carrying runs.
|
|
@@ -1013,7 +1059,7 @@ export const PROVIDERS = {
|
|
|
1013
1059
|
id: "clarivate",
|
|
1014
1060
|
label: "Clarivate Compumark",
|
|
1015
1061
|
credEnv: "CLARIVATE_API_KEY",
|
|
1016
|
-
skillDoc: "skills/
|
|
1062
|
+
skillDoc: "skills/clearance-register/providers/clarivate.md",
|
|
1017
1063
|
hasPublicRecordUrl: false, // Compumark Content has no public record URL — cite the office register
|
|
1018
1064
|
//, ruling 2026-08-20 — WHAT A CARD SHOWS WHERE A LINK CANNOT GO. A UI exists for this
|
|
1019
1065
|
// provider and we do not know its per-record URL, so the card says so and says it is unfinished.
|
|
@@ -1155,7 +1201,7 @@ export const PROVIDERS = {
|
|
|
1155
1201
|
id: "signa",
|
|
1156
1202
|
label: "Signa",
|
|
1157
1203
|
credEnv: "SIGNA_API_KEY",
|
|
1158
|
-
skillDoc: "skills/
|
|
1204
|
+
skillDoc: "skills/clearance-register/providers/signa.md",
|
|
1159
1205
|
hasPublicRecordUrl: false, // Signa exposes no per-record public URL — cite the office register
|
|
1160
1206
|
//, ruling 2026-08-20 — no register UI exists to link to at all, so the card points at
|
|
1161
1207
|
// the artifact that DOES carry the record: the audit workbook. Naming it is the whole of this
|
|
@@ -1210,8 +1256,12 @@ export const PROVIDERS = {
|
|
|
1210
1256
|
// on the search response, so this no longer has to answer `present` and nothing else. It is
|
|
1211
1257
|
// still null whenever the vendor would only approximate it — and null there means UNKNOWN,
|
|
1212
1258
|
// which is the whole reason the field may never be filled in with a figure from anywhere else.
|
|
1259
|
+
// `floor` rides beside `approximate` because the two are one fact: the register answered, and the
|
|
1260
|
+
// answer is "more than this". Without the number the disclosure is not usable — "approximate"
|
|
1261
|
+
// alone says no more than "unknown" does, which is the state this replaces.
|
|
1213
1262
|
return { ok: true, total: Number.isFinite(p.total_hits) ? p.total_hits : null,
|
|
1214
|
-
approximate: p.total_approximate === true,
|
|
1263
|
+
approximate: p.total_approximate === true, floor: Number.isFinite(p.total_floor) ? p.total_floor : null,
|
|
1264
|
+
present: p.present === true, note: p.note };
|
|
1215
1265
|
} catch (e) { return { ok: false, cause: `countHits threw: ${e.message}` }; }
|
|
1216
1266
|
},
|
|
1217
1267
|
// `reason`, not `cause`, on every refusal: the listing reads `reason` (register-records.mjs), so a
|
|
@@ -1301,7 +1351,7 @@ export const PROVIDERS = {
|
|
|
1301
1351
|
// list — without this, an instance holding the id and no secret passes preflight and dies on the
|
|
1302
1352
|
// first token request, after model spend and reported as a provider fault.
|
|
1303
1353
|
credEnvAlso: ["EUIPO_CLIENT_SECRET"],
|
|
1304
|
-
skillDoc: "skills/
|
|
1354
|
+
skillDoc: "skills/clearance-register/providers/euipo.md",
|
|
1305
1355
|
hasPublicRecordUrl: true,
|
|
1306
1356
|
publicRecordOrigin: "https://euipo.europa.eu",
|
|
1307
1357
|
async recordFetch(uri, { agentId, sessionKey, recordLog = null }) {
|
|
@@ -1367,7 +1417,7 @@ export const PROVIDERS = {
|
|
|
1367
1417
|
id: "uspto-local",
|
|
1368
1418
|
label: "USPTO (local index)",
|
|
1369
1419
|
credEnv: "USPTO_LOCAL_DB",
|
|
1370
|
-
skillDoc: "skills/
|
|
1420
|
+
skillDoc: "skills/clearance-register/providers/uspto-local.md",
|
|
1371
1421
|
hasPublicRecordUrl: true,
|
|
1372
1422
|
// TSDR publishes a page per serial, so a finding can cite an address the reader can open. The
|
|
1373
1423
|
// record ref is /mark/us/<serial>, and the core builds the full statusSearch link on the record.
|
|
@@ -1471,7 +1521,7 @@ export const PROVIDERS = {
|
|
|
1471
1521
|
label: "Free tier (EUIPO + USPTO local index)",
|
|
1472
1522
|
credEnv: "EUIPO_CLIENT_ID",
|
|
1473
1523
|
credEnvAlso: ["EUIPO_CLIENT_SECRET"],
|
|
1474
|
-
skillDoc: "skills/
|
|
1524
|
+
skillDoc: "skills/clearance-register/providers/free-tier.md",
|
|
1475
1525
|
hasPublicRecordUrl: true,
|
|
1476
1526
|
// NULL, deliberately: the two members have DIFFERENT public origins (euipo.europa.eu and the USPTO),
|
|
1477
1527
|
// so a single origin string here would stamp one office's host onto the other's citations. The
|
|
@@ -1821,7 +1871,7 @@ export function preflightCredentials(env = process.env) {
|
|
|
1821
1871
|
// paid for. This door is that same failure, moved in front of the spend.
|
|
1822
1872
|
//
|
|
1823
1873
|
// GATED ON THE COMPONENT, NEVER ON THE PIPELINE. `pipeline === "clearance"` is the wrong predicate and
|
|
1824
|
-
// fails in the expensive direction: `
|
|
1874
|
+
// fails in the expensive direction: `clearance-register-only` is a clearance that carries
|
|
1825
1875
|
// `commonLawGrid: false`, searches no unregistered-use half by design, and would be refused for a
|
|
1826
1876
|
// credential its lane never reads. The component IS the question — search-policy.mjs calls it "the
|
|
1827
1877
|
// clearance's unregistered-use half" in as many words.
|
|
@@ -2332,7 +2382,7 @@ export function preflightDeploymentUrls(env = process.env) {
|
|
|
2332
2382
|
const RUN_FREE_BYTES_FLOOR = 500e6;
|
|
2333
2383
|
|
|
2334
2384
|
/** Nearest ancestor of `p` that exists. statfs needs a real path, and on a first run NONE of
|
|
2335
|
-
* …/workspace-<agent>/studio/
|
|
2385
|
+
* …/workspace-<agent>/studio/clearance-search exists yet — measuring the leaf would throw ENOENT and land
|
|
2336
2386
|
* in the unmeasurable branch, which would disable this check on exactly the fresh installs it is for. */
|
|
2337
2387
|
function nearestExistingDir(p) {
|
|
2338
2388
|
let dir = p;
|
|
@@ -2374,7 +2424,7 @@ export function freeSpacePlan({ freeBytes, needBytes, path }) {
|
|
|
2374
2424
|
* shape of run that can proceed without one, so there is no exemption to write.
|
|
2375
2425
|
*
|
|
2376
2426
|
* MEASURES THE FILESYSTEM THAT WILL HOLD THE BYTES, which is the workspace root's, not `/` and not the
|
|
2377
|
-
* repo's. Run directories live under config.studioRoot (…/workspace-<agent>/studio/
|
|
2427
|
+
* repo's. Run directories live under config.studioRoot (…/workspace-<agent>/studio/clearance-search); the
|
|
2378
2428
|
* published report goes to poolRoot and the packet to outboxDir, which on a laptop are different
|
|
2379
2429
|
* filesystems again. A check aimed at the wrong mount passes while the right one is full, which is the
|
|
2380
2430
|
* silent-pass this exists to prevent — so the path is taken from the caller's studioRoot when the runner
|
|
@@ -14,7 +14,7 @@ The active register provider is mounted under the single, neutral server key `re
|
|
|
14
14
|
named `register_*` — so the namespaced ids the prompts and allowlists carry (`mcp__register__register_enumerate`)
|
|
15
15
|
stay stable across a provider swap. What is PINNED is the vendor's TOOL tokens, not its name:
|
|
16
16
|
`../../test/provider-neutral-prose.test.mjs` walks `driver/` (skipping `fixtures/`) and fails any `<vendor>_<tool>`
|
|
17
|
-
outside the six `<provider>-server.mjs` files and `../../skills/
|
|
17
|
+
outside the six `<provider>-server.mjs` files and `../../skills/clearance-register/providers/`, exempting a core's own
|
|
18
18
|
`ERROR: … HTTP` diagnostics. The plain vendor NAME is unrestricted: `REGISTER_SERVERS` is keyed by it, and 134 other
|
|
19
19
|
files under `driver/` carry `corsearch`.
|
|
20
20
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// held by FOUR stages — `common-law`, `common-law-half`, `narrative-refutation`, `synthesis`. Because
|
|
13
13
|
// `allowedToolsFor` enumerates every tool on every entry a group resolves to, all four carried
|
|
14
14
|
// `mcp__perplexity__record_dispositions`, while every doctrinal mention of the tool is common-law's:
|
|
15
|
-
// `driver/skills/
|
|
15
|
+
// `driver/skills/clearance-common-law/SKILL.md`, and the two common-law stage dictations in `stages.mjs`.
|
|
16
16
|
// Zero occurrences in synthesis's dictation block, zero in narrative-refutation's doctrine.
|
|
17
17
|
//
|
|
18
18
|
// That is GRANTED-BUT-NEVER-ORDERED, the defect class, in its mirror form: not a stage ordered to
|
|
@@ -75,8 +75,8 @@ async function record_dispositions(params) {
|
|
|
75
75
|
let spec;
|
|
76
76
|
try { spec = validateGridSpec(JSON.parse(readFileSync(grid_spec_path, "utf8"))); }
|
|
77
77
|
catch (err) { return { isError: true, text: `ERROR: grid_spec_path unreadable/invalid (${err.message}). The driver writes this file; do not hand-author it.` }; }
|
|
78
|
-
if (!/\/studio\/prelim-search\//.test(spec.output_path))
|
|
79
|
-
return { isError: true, text: `ERROR: grid spec.output_path must be within a studio/
|
|
78
|
+
if (!/\/studio\/(?:prelim|clearance)-search\//.test(spec.output_path)) // either spelling: an install keeps the studio segment it has
|
|
79
|
+
return { isError: true, text: `ERROR: grid spec.output_path must be within a studio/clearance-search run dir; got ${spec.output_path}` };
|
|
80
80
|
// NEVER THROWN PAST THIS POINT. An exception surfaces to the seat as a tool error naming no row, which
|
|
81
81
|
// tells it nothing about what to fix — the failure mode this transport exists to end.
|
|
82
82
|
try {
|
|
@@ -23,7 +23,7 @@ const BRIDGE = process.env.CLEAROTRON_OAUTH_BRIDGE || join(MCP_DIR, "..", "..",
|
|
|
23
23
|
// ── The register surface is PROVIDER-NEUTRAL ────────────────────────────────────────────────────
|
|
24
24
|
// The active register provider is mounted under the single server key `register`, and every register
|
|
25
25
|
// tool is named `register_*`. The vendor's name survives ONLY inside its own *-server.mjs file and in
|
|
26
|
-
// skills/
|
|
26
|
+
// skills/clearance-register/providers/<provider>.md (the vocabulary doc the spawns are told to read).
|
|
27
27
|
//
|
|
28
28
|
// Why neutral names rather than interpolating `${PROVIDER}_enumerate` into the prose: the register
|
|
29
29
|
// instructions in stages.mjs / pipeline.mjs / gateway.mjs are then correct BY CONSTRUCTION for every
|
|
@@ -275,7 +275,7 @@ const RECORDING = Object.freeze({
|
|
|
275
275
|
+ "DISCOVERY over the run dir (O3c: 21 calls, 1 write / 15 attempts, `ls`/`find`/`cat`), not the "
|
|
276
276
|
+ "enumerable pair frame-diff's Read grant covers",
|
|
277
277
|
},
|
|
278
|
-
// FIFTH —
|
|
278
|
+
// FIFTH — clearance-variants, conversion 3. Classes 3 + 2, and the CLASS 3 half is what makes it
|
|
279
279
|
// different from every conversion before it: O3c measured 9 Bash calls with 4 WRITES across 15
|
|
280
280
|
// attempts, and the shape is `python3 -c` over `variant-manifest.json` — the seat PRE-CHECKING its own
|
|
281
281
|
// JSON before saving it. That is a constraint the transport can check, so under the design's Class 3
|
|
@@ -287,12 +287,12 @@ const RECORDING = Object.freeze({
|
|
|
287
287
|
// enumerable — it derives the manifest from material already on disk that the dictation names — so the
|
|
288
288
|
// seeded `Read` grant carries them. Granting a search tool to a stage whose reads can be listed is
|
|
289
289
|
// what the sanctioned-equivalents design refuses. Same ruling as frame-diff, opposite to matter-frame.
|
|
290
|
-
"
|
|
290
|
+
"clearance-variants": {
|
|
291
291
|
seatWrites: false,
|
|
292
|
-
tools: Object.freeze(["
|
|
292
|
+
tools: Object.freeze(["record_clearance_variants"]),
|
|
293
293
|
reason: "hands back the variant manifest — mark, dominant element, elements, variants with their "
|
|
294
294
|
+ "romanisations, incumbent classes, watchlist owners and the scope-ledger rows — through "
|
|
295
|
-
+ "
|
|
295
|
+
+ "record_clearance_variants instead of hand-formatting a JSON skeleton the driver already "
|
|
296
296
|
+ "strict-parses and a prose twin that restates it; the driver serialises variant-manifest.json, "
|
|
297
297
|
+ "renders variant-manifest.md, and derives scope-ledger.json from the same values rather than by "
|
|
298
298
|
+ "re-parsing a markdown table the seat typed. Gains NO retrieval server and NO search tool (its "
|
|
@@ -320,7 +320,7 @@ const RECORDING = Object.freeze({
|
|
|
320
320
|
// NO `search_run_artifacts`: the dictation names this stage's ONLY two inputs — the settled narrative
|
|
321
321
|
// and findings.json — and already trimmed its declared reads to exactly those two. A read set that
|
|
322
322
|
// short is enumerable by definition, and the design forbids handing a search tool to a stage whose
|
|
323
|
-
// reads can be listed. Same ruling as frame-diff and
|
|
323
|
+
// reads can be listed. Same ruling as frame-diff and clearance-variants.
|
|
324
324
|
"report-overview": {
|
|
325
325
|
seatWrites: false,
|
|
326
326
|
tools: Object.freeze(["record_report_overview"]),
|
|
@@ -671,7 +671,7 @@ const LOCAL = {
|
|
|
671
671
|
//
|
|
672
672
|
// GRANTED BY EXACTLY ONE LANE'S GROUP LIST — `common-law` and its `common-law-half` variant, through
|
|
673
673
|
// the prefix branch in toolGroupsForStage. Every doctrinal mention of the tool is theirs:
|
|
674
|
-
// driver/skills/
|
|
674
|
+
// driver/skills/clearance-common-law/SKILL.md and the two common-law stage dictations in stages.mjs.
|
|
675
675
|
//
|
|
676
676
|
// NOT an allowlist growing by a token this time: it SHRINKS synthesis's and narrative-refutation's
|
|
677
677
|
// argv and RENAMES the token on common-law's (`mcp__perplexity__record_dispositions` →
|
|
@@ -1222,8 +1222,8 @@ export const RECORDING_TOOLS = Object.freeze({
|
|
|
1222
1222
|
// so deriving it would compare a value with itself. matter-frame carries the search tool for the same
|
|
1223
1223
|
// reason skeptic does, on its OWN key — a shared key would hand skeptic a writer into the frame.
|
|
1224
1224
|
// BY HAND, like every row here — O1 compares the resolved grant against it, so a derived row would
|
|
1225
|
-
// compare a value with itself.
|
|
1226
|
-
"
|
|
1225
|
+
// compare a value with itself. clearance-variants carries NO search tool: enumerable inputs, Read serves.
|
|
1226
|
+
"clearance-variants": Object.freeze(["Read", "mcp__recording-clearance-variants__record_clearance_variants"]),
|
|
1227
1227
|
"matter-frame": Object.freeze(["Read", "mcp__recording-matter-frame__record_matter_frame",
|
|
1228
1228
|
"mcp__recording-matter-frame__search_run_artifacts"]),
|
|
1229
1229
|
skeptic: Object.freeze(["Read", "mcp__recording-skeptic__record_skeptic",
|
|
@@ -154,8 +154,8 @@ async function research(params) {
|
|
|
154
154
|
let spec;
|
|
155
155
|
try { spec = validateGridSpec(JSON.parse(readFileSync(grid_spec_path, "utf8"))); }
|
|
156
156
|
catch (err) { return `ERROR: grid_spec_path unreadable/invalid (${err.message}). The driver writes this file; do not hand-author it.`; }
|
|
157
|
-
if (!/\/studio\/prelim-search\//.test(spec.output_path))
|
|
158
|
-
return requiredLedgerRefusal(`ERROR: grid spec.output_path must be within a studio/
|
|
157
|
+
if (!/\/studio\/(?:prelim|clearance)-search\//.test(spec.output_path)) // either spelling: an install keeps the studio segment it has
|
|
158
|
+
return requiredLedgerRefusal(`ERROR: grid spec.output_path must be within a studio/clearance-search run dir; got ${spec.output_path}`, { spec, gridSpecPath: grid_spec_path });
|
|
159
159
|
// — already recorded and complete? Answer from the ledger; do not buy the grid twice.
|
|
160
160
|
const already = recordedLedgerFor(spec);
|
|
161
161
|
if (already) {
|
|
@@ -44,7 +44,7 @@ import { VARIANT_DIRECTIONS, RANKING_BASES } from "../../blind-frame-model.mjs";
|
|
|
44
44
|
import { recordSkeptic } from "../../skeptic-record.mjs";
|
|
45
45
|
import { recordFrameDiff } from "../../frame-diff-record.mjs";
|
|
46
46
|
import { recordMatterFrame, INTAKE_ASK_OWNERS, SCOPE_BASES } from "../../matter-frame-record.mjs";
|
|
47
|
-
import {
|
|
47
|
+
import { recordClearanceVariants, SCOPE_LAYERS, SCOPE_STATUS } from "../../clearance-variants-record.mjs";
|
|
48
48
|
import { recordReportOverview } from "../../report-overview-record.mjs";
|
|
49
49
|
import { recordReportCard } from "../../report-card-record.mjs"; // conversion 5 — the fan-out transport
|
|
50
50
|
import { recordRefutation, REVIEW_VERDICTS } from "../../narrative-refutation-record.mjs"; // conversion 9
|
|
@@ -125,12 +125,12 @@ async function record_knockout_review(params) {
|
|
|
125
125
|
return recordKnockoutReview(runDir, params);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
async function
|
|
128
|
+
async function record_clearance_variants(params) {
|
|
129
129
|
const runDir = String(process.env.CLEAROTRON_BAND_RUN_DIR ?? "");
|
|
130
130
|
if (!runDir) {
|
|
131
131
|
return { error: "this server was started without a run — the driver wires it per run; there is no parameter for it and this tool never guesses one" };
|
|
132
132
|
}
|
|
133
|
-
return
|
|
133
|
+
return recordClearanceVariants(runDir, params);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
async function record_report_card(params) {
|
|
@@ -444,7 +444,7 @@ serve({
|
|
|
444
444
|
//
|
|
445
445
|
// `scope_ledger` is the one genuinely new field, and it is what deletes a derivation: those rows used
|
|
446
446
|
// to reach the driver only by re-parsing a markdown table out of the prose manifest.
|
|
447
|
-
name: "
|
|
447
|
+
name: "record_clearance_variants",
|
|
448
448
|
description:
|
|
449
449
|
"Hand back the variant manifest as VALUES. The driver serialises variant-manifest.json, renders " +
|
|
450
450
|
"variant-manifest.md and writes scope-ledger.json from what you send, so you never format JSON, " +
|
|
@@ -516,7 +516,7 @@ serve({
|
|
|
516
516
|
},
|
|
517
517
|
},
|
|
518
518
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
519
|
-
handler:
|
|
519
|
+
handler: record_clearance_variants,
|
|
520
520
|
}, {
|
|
521
521
|
// ── CONVERSION 4 — THE REPORT SHELL, AND THE FIRST ARTIFACT A CLIENT READS ────────────────────
|
|
522
522
|
//
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
2
|
// Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
|
|
3
|
-
// Job-file shape for studio/
|
|
3
|
+
// Job-file shape for studio/clearance-search/queue/<id>.json (written by email-loop on a clearance-search request).
|
|
4
4
|
// The job id = sanitized email message-id so a re-delivered webhook overwrites the same file (no duplicate run).
|
|
5
5
|
//
|
|
6
6
|
// Blocking semantics follow change-spec v3 §B2: the ONLY content reason a search may not start is the
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// with a `noref<hash>` slug (see phase0.mjs deriveSlug). The intake confirmation brief (email-loop §6)
|
|
11
11
|
// resolves ambiguity BEFORE enqueue; this validator is the runner-side mechanical backstop.
|
|
12
12
|
|
|
13
|
-
import { loadProfiles, loadProjects, resolveProfile, applicantMatchesProfile, recipeProseGuard, platformEntryErrors } from "./profiles.mjs";
|
|
13
|
+
import { loadProfiles, loadProjects, resolveProfile, applicantMatchesProfile, recipeProseGuard, platformEntryErrors , unreadableProfiles } from "./profiles.mjs";
|
|
14
14
|
import { demoRunShape } from "./demo-run-agreement.mjs";
|
|
15
15
|
import { ORDERABLE_PRODUCTS, policyFor, checkMarkBudget, checkScopeAgainstPolicy, loadRecipes, kebabCollisions, resolveSearchPolicy } from "./search-policy.mjs";
|
|
16
16
|
// — the §B2 gate resolves the subject through the SAME ladder the run uses. See the gate itself.
|
|
@@ -776,6 +776,10 @@ export function validateJob(job, { atClaim = false } = {}) {
|
|
|
776
776
|
profiles = loadProfiles({ force: true });
|
|
777
777
|
known = profiles.has(key);
|
|
778
778
|
}
|
|
779
|
+
// A company whose own file would not load is a known company that could not be READ — the same
|
|
780
|
+
// failure to look as a store that cannot be read, and handled the same way below, never "no such
|
|
781
|
+
// customer". The file's reason travels with the run's own refusal (resolveProfile).
|
|
782
|
+
if (!known) known = new Set(unreadableProfiles(profiles).map((u) => u.key)).has(key);
|
|
779
783
|
roster = [...profiles.keys()].sort();
|
|
780
784
|
} catch { known = true; }
|
|
781
785
|
// NAME THE ROSTER THIS PROCESS CAN SEE, always.
|
|
@@ -590,8 +590,11 @@ export function riskStatement({ tier, verdict, reasons, basis, clauses } = {}) {
|
|
|
590
590
|
const basisNote = registerOnly ? " Register findings only — no common-law or marketplace search was run." : "";
|
|
591
591
|
if (v === "BLOCKING") return `On hold — the reviewing lawyer's open questions must be resolved before any recommendation.${basisNote}`;
|
|
592
592
|
if (v === "CONDITIONAL") {
|
|
593
|
-
|
|
594
|
-
|
|
593
|
+
// A clause stored as explicit null is a condition ruled to the run record alone: it is not the lede,
|
|
594
|
+
// it is not counted, and its reason is never the fallback text. `undefined` (legacy/short) is not null.
|
|
595
|
+
const cs = Array.isArray(clauses) ? clauses : [];
|
|
596
|
+
const conds = (Array.isArray(reasons) ? reasons : []).filter((r, i) => cs[i] !== null).map((r) => String(r ?? "").trim()).filter(Boolean);
|
|
597
|
+
const cls = cs.map((c) => String(c ?? "").trim()).filter(Boolean);
|
|
595
598
|
const lede = cls[0] ?? conds[0] ?? "the open conditions carried in the report";
|
|
596
599
|
const first = clipClause(sentenceCaseLead(lede), STATEMENT_CLAUSE_MAX);
|
|
597
600
|
const n = conds.length || cls.length;
|
package/driver/flag-snapshot.mjs
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
// than being unable to ask. Failing closed here would mean a file-read error takes the whole portal
|
|
42
42
|
// down — trading a rare wrong-greyed-out option for a total outage.
|
|
43
43
|
|
|
44
|
-
import { writeFileSync, readFileSync, mkdirSync } from "node:fs";
|
|
44
|
+
import { writeFileSync, readFileSync, mkdirSync, existsSync } from "node:fs";
|
|
45
45
|
import { dirname, join } from "node:path";
|
|
46
46
|
import { BUILT } from "./search-policy.mjs";
|
|
47
47
|
import { isEntrypoint } from "../shared/is-entrypoint.mjs"; // — one entry-point test, all spellings
|
|
@@ -369,7 +369,7 @@ export function postureDisagreement(snapshot, live) {
|
|
|
369
369
|
|
|
370
370
|
/** Where the snapshot lives. Beside the pool, so it shares the pool's lifecycle and backup. */
|
|
371
371
|
export function snapshotPath(poolRoot) {
|
|
372
|
-
return join(poolRoot, "_state", "
|
|
372
|
+
return join(poolRoot, "_state", "clearance-flag-snapshot.json");
|
|
373
373
|
}
|
|
374
374
|
|
|
375
375
|
/**
|
|
@@ -383,7 +383,10 @@ export function readFlagSnapshot(poolRoot) {
|
|
|
383
383
|
// reads as intentional.
|
|
384
384
|
if (!poolRoot) return null;
|
|
385
385
|
try {
|
|
386
|
-
|
|
386
|
+
// The file's pre-rename name is read when the new one is not there yet, so an upgraded install does
|
|
387
|
+
// not read as "no snapshot" until its first write under the new name.
|
|
388
|
+
const legacy = join(poolRoot, "_state", "prelim-flag-snapshot.json");
|
|
389
|
+
const raw = JSON.parse(readFileSync(existsSync(snapshotPath(poolRoot)) || !existsSync(legacy) ? snapshotPath(poolRoot) : legacy, "utf8"));
|
|
387
390
|
if (!raw || typeof raw !== "object" || typeof raw.flags !== "object") return null;
|
|
388
391
|
return raw;
|
|
389
392
|
} catch {
|