clearotron 0.3.3 → 0.4.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +9 -0
- package/INSTALL.md +1 -14
- package/bin/brandowner.mjs +5 -5
- package/bin/connect.mjs +4 -4
- package/bin/onboard.mjs +5 -7
- package/bin/start.mjs +20 -5
- package/bin/update.mjs +6 -1
- package/build-info.json +2 -2
- package/docs/architecture/04-configuration-reference.md +2 -6
- package/driver/CHANGELOG.md +28 -0
- package/driver/ask-ledger.mjs +2 -2
- package/driver/band-shape.mjs +8 -8
- package/driver/blind-frame-model.mjs +1 -1
- package/driver/common-law-receipts.mjs +2 -2
- package/driver/commonlaw-carry.mjs +2 -2
- package/driver/company-bundle.mjs +11 -18
- package/driver/connotation-search.mjs +4 -4
- package/driver/contract-e3-backlog.mjs +3 -3
- package/driver/declination-call.mjs +1 -1
- package/driver/declination-tool.mjs +1 -1
- package/driver/dev-portal.mjs +1 -1
- package/driver/door-call-verdict.mjs +27 -0
- package/driver/driver.config.mjs +17 -11
- package/driver/e2e/README.md +1 -1
- package/driver/engine/mcp/clarivate-server.mjs +2 -1
- package/driver/engine/mcp/corsearch-server.mjs +1 -0
- package/driver/engine/mcp/euipo-server.mjs +1 -0
- package/driver/engine/mcp/free-tier-server.mjs +1 -1
- package/driver/engine/mcp/gather-config.mjs +1 -1
- package/driver/engine/mcp/perplexity-server.mjs +1 -1
- package/driver/engine/mcp/recording-server.mjs +1 -1
- package/driver/engine/mcp/signa-server.mjs +1 -0
- package/driver/engine/mcp/supplemental.mjs +1 -1
- package/driver/engine/mcp/uspto-local-server.mjs +1 -0
- package/driver/enqueue-schema.mjs +2 -2
- package/driver/feedback-issues.mjs +1 -1
- package/driver/feedback-store.mjs +1 -1
- package/driver/findings-model.mjs +3 -3
- package/driver/flag-snapshot.mjs +1 -1
- package/driver/floor-duty.mjs +2 -2
- package/driver/form-neighbourhood.mjs +47 -15
- package/driver/frame-diff-model.mjs +3 -3
- package/driver/gateway.mjs +5 -5
- package/driver/jx-lanes.mjs +1 -1
- package/driver/known-conflicts.mjs +18 -0
- package/driver/log.mjs +2 -2
- package/driver/package.json +1 -1
- package/driver/pipeline-knockout.mjs +129 -95
- package/driver/pipeline.mjs +69 -32
- package/driver/placement-carry.mjs +2 -2
- package/driver/placement-form.mjs +1 -1
- package/driver/portal-mcp-client.mjs +1 -1
- package/driver/portal-request-origin.mjs +79 -0
- package/driver/portal-service.mjs +45 -17
- package/driver/predelivery-lint.mjs +10 -10
- package/driver/profile-page.html +9 -13
- package/driver/profile-service.mjs +25 -13
- package/driver/profiles.mjs +17 -4
- package/driver/progress.mjs +1 -1
- package/driver/provider-usage.mjs +24 -1
- package/driver/publish/index.mjs +17 -5
- package/driver/publish/knockout.mjs +3 -2
- package/driver/publish/render-knockout.mjs +1 -1
- package/driver/publish/render.mjs +14 -3
- package/driver/publish/search-depth.mjs +4 -2
- package/driver/recall-reconciliation.mjs +1 -1
- package/driver/record-carry.mjs +6 -6
- package/driver/recording-agreement.mjs +2 -2
- package/driver/reference-score.mjs +27 -27
- package/driver/register-count.mjs +56 -1
- package/driver/register-digest-record.mjs +1 -1
- package/driver/register-plan.mjs +5 -5
- package/driver/register-records.mjs +10 -1
- package/driver/registry-fidelity.mjs +4 -4
- package/driver/repair-composers.mjs +6 -6
- package/driver/run-economics.mjs +8 -19
- package/driver/screen-gate.mjs +1 -1
- package/driver/skills/blind-frame/SKILL.md +2 -2
- package/driver/skills/clearance-common-law/SKILL.md +2 -2
- package/driver/skills/clearance-common-law/perplexity-prompts.md +4 -4
- package/driver/skills/clearance-register/digest.md +1 -1
- package/driver/skills/clearance-register/unit.md +1 -1
- package/driver/skills/clearance-search/report-prose.md +5 -5
- package/driver/skills/clearance-search/synthesis-rules.md +4 -4
- package/driver/skills/clearance-variants/SKILL.md +2 -2
- package/driver/skills/clearance-variants/transliteration-scripts.md +1 -1
- package/driver/skills/frame-diff/SKILL.md +2 -2
- package/driver/skills/knockout-assess/SKILL.md +9 -9
- package/driver/skills/matter-frame/watchlist-reference.md +1 -1
- package/driver/skills/narrative-refutation/SKILL.md +2 -2
- package/driver/skills/placement-inquiry/SKILL.md +1 -1
- package/driver/stage-context.mjs +4 -4
- package/driver/stages.mjs +23 -15
- package/driver/suite-census.json +138 -42
- package/driver/systemd/clearotron-client-mcp.service +24 -0
- package/driver/systemd/clearotron-mcp-face.service +24 -0
- package/driver/systemd/clearotron-portal.service +24 -0
- package/driver/systemd/clearotron-worker.service +24 -0
- package/driver/tokens.mjs +26 -17
- package/driver/turnaround-bands.mjs +1 -1
- package/driver/unit-inventory.mjs +3 -3
- package/driver/variant-manifest-model.mjs +1 -1
- package/driver/verify.mjs +2 -2
- package/driver/whatif-memo-run.mjs +1 -1
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/lib/audit.mjs +9 -2
- package/mcp-server/lib/http-handler.mjs +7 -3
- package/mcp-server/mint-token.mjs +8 -6
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +11 -1
- package/package.json +1 -1
- package/portal-ui/dist/assets/{index-GBbbyQxc.js → index-D_O_55vK.js} +59 -9
- package/portal-ui/dist/index.html +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/_shared/README.md +1 -1
- package/providers/_shared/answer-memory.mjs +199 -0
- package/providers/_shared/ledger-path.mjs +1 -1
- package/providers/_shared/ledger.mjs +47 -5
- package/providers/_shared/script-form.mjs +24 -5
- package/providers/_shared/term-shape.mjs +5 -5
- package/providers/clarivate/src/capabilities.js +11 -0
- package/providers/clarivate/src/core.js +140 -13
- package/providers/jx-subclass/lookup.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/capabilities.js +24 -0
- package/providers/signa/src/core.js +66 -0
- package/scripts/deprecate-below.mjs +114 -2
- package/scripts/freeze-example-run.mjs +1 -1
- package/scripts/live-surface-check.mjs +11 -2
- package/scripts/release-entry-catch-up.mjs +211 -0
- package/scripts/release-note-required.mjs +102 -6
- package/scripts/release-rehearsal-version.mjs +60 -0
- package/scripts/release-sbom.mjs +104 -0
- package/scripts/release-visible-check.mjs +7 -5
- package/scripts/score.mjs +3 -3
- package/shared/brand.mjs +1 -1
- package/shared/client-door.mjs +15 -8
- package/shared/driver-dir.mjs +20 -9
- package/shared/names-in-force.mjs +2 -0
- package/shared/scope.mjs +25 -10
- package/shared/store-in-repo.mjs +38 -17
|
@@ -516,7 +516,7 @@ export const UNIT_INVENTORY = Object.freeze([
|
|
|
516
516
|
// `untrackedReason` above is kept in the PAST tense of its own finding on purpose: it records how
|
|
517
517
|
// this unit was discovered — by enumerating production, not by reading a list — and that is history
|
|
518
518
|
// the stop does not erase.
|
|
519
|
-
orphanReason: "STOPPED AND DISABLED ON PRODUCTION 2026-08-20 on the owner's word
|
|
519
|
+
orphanReason: "STOPPED AND DISABLED ON PRODUCTION 2026-08-20 on the owner's word, so it runs "
|
|
520
520
|
+ "on no box and `runsOn` is empty. It is not deleted: the unit file, scripts/feedback-mint.mjs and "
|
|
521
521
|
+ "the whole capture path are intact, and `systemctl --user enable --now feedback-mint.timer` as "
|
|
522
522
|
+ "the service account puts it back. It drained a store that never existed — /srv/trademark-archive/_feedback "
|
|
@@ -524,8 +524,8 @@ export const UNIT_INVENTORY = Object.freeze([
|
|
|
524
524
|
+ "flag was ever captured, so stopping it dropped nothing. The decision owed, if any, is whether the "
|
|
525
525
|
+ "feedback machinery is removed rather than parked; driver/run-activity.mjs still carries a note "
|
|
526
526
|
+ "addressed to whoever picks that up, and it is still unclaimed.",
|
|
527
|
-
note: "STOPPED. Production ran it every 30 minutes against an input nothing writes to any more
|
|
528
|
-
+ "
|
|
527
|
+
note: "STOPPED. Production ran it every 30 minutes against an input nothing writes to any more"
|
|
528
|
+
+ "; the owner's word came 2026-08-20 and it was disabled the same evening.",
|
|
529
529
|
},
|
|
530
530
|
// ── OTHER REPOS' UNITS, SHARING THE BOX ───────────────────────────────────────────────────────────
|
|
531
531
|
// Declared, not adopted. A unit this repo does not own is still a unit the arm enumerates, and an
|
|
@@ -332,7 +332,7 @@ export function variantRomanizationGaps(model) {
|
|
|
332
332
|
* which is also where the incident string came from.
|
|
333
333
|
*
|
|
334
334
|
* `watchlist_owners` — those compile to `predicate:"owner"`, exempt from every term rule because
|
|
335
|
-
* owner names are prose by nature ("
|
|
335
|
+
* owner names are prose by nature ("Korphi Technologies (BorgWarner Inc.)").
|
|
336
336
|
*/
|
|
337
337
|
// — `termAnnotationIssue` moved to term-shape.mjs, the ONE term vocabulary, so the plan
|
|
338
338
|
// compiler applies the SAME predicate this gate does rather than a second copy of it.
|
package/driver/verify.mjs
CHANGED
|
@@ -848,7 +848,7 @@ export const CORRECTION_KINDS = ["coverage-disposition", "fact", "rating", "narr
|
|
|
848
848
|
const CORRECTION_KIND_RE = /\[kind:\s*([a-z][a-z-]*)\s*\]/i;
|
|
849
849
|
// — THE SECOND CHANNEL: WHICH FINDING A FLAG IS ABOUT.
|
|
850
850
|
//
|
|
851
|
-
// The reviewer already writes it, in prose — a delivered review's flags open "Finding 9 —
|
|
851
|
+
// The reviewer already writes it, in prose — a delivered review's flags open "Finding 9 — KORPHIC…",
|
|
852
852
|
// "Findings 4, 7, 8." — and the driver could not read it: `targetsOf` matched mark/owner NAMES and
|
|
853
853
|
// returned nothing for six of nine flags on that run. So the corrective pass re-emitted the whole
|
|
854
854
|
// narrative and the whole findings.json, for 683 seconds, and exactly ONE finding moved.
|
|
@@ -2574,7 +2574,7 @@ function checkClientSummaryJoin(p, c) {
|
|
|
2574
2574
|
const risk = body.match(/^-\s*risk:\s*([A-Za-z –-]+)$/m)?.[1]?.trim();
|
|
2575
2575
|
if (!risk) continue;
|
|
2576
2576
|
// wp50: deterministic join (ord line → exact mark → unique containment). The old first-match
|
|
2577
|
-
// containment join bound "DEMVENZY —
|
|
2577
|
+
// containment join bound "DEMVENZY — Norvanta" to the VENZY finding and VALIDATED the wrong tier.
|
|
2578
2578
|
const f = joinFindingToBlock({ ord: parseBlockOrd(body), head }, findingsDoc.findings);
|
|
2579
2579
|
if (!f) continue; // unjoinable block — shape checks own it
|
|
2580
2580
|
if (v4) {
|
|
@@ -97,7 +97,7 @@ export function movedArtifacts(before = {}, after = {}) {
|
|
|
97
97
|
*
|
|
98
98
|
* ✕ EVERYTHING ELSE IS THE SEAT WRITING WHERE IT WAS NOT INVITED, and it is refused by name. The seat
|
|
99
99
|
* has a real file-write grant on the run directory, and on a delivered run it used it: an archived
|
|
100
|
-
* client run carries `memo-
|
|
100
|
+
* client run carries `memo-halver-coexistence.json` at its root, which is the seat's reply written to a
|
|
101
101
|
* path of its own choosing instead of to the `expectFile` it was given. That also explains the retry
|
|
102
102
|
* that preceded it — the harness looked for the reply where it had asked for it and did not find it.
|
|
103
103
|
*
|
package/mcp-server/CHANGELOG.md
CHANGED
package/mcp-server/lib/audit.mjs
CHANGED
|
@@ -50,7 +50,7 @@ export function summarize(body) {
|
|
|
50
50
|
*/
|
|
51
51
|
export const UNNAMED_DOOR = "unnamed";
|
|
52
52
|
|
|
53
|
-
export function appendAudit({ email, sub, body, status, transport, door, path = DEFAULT_AUDIT_PATH }) {
|
|
53
|
+
export function appendAudit({ email, sub, kind = null, namesVerbs = null, body, status, transport, door, path = DEFAULT_AUDIT_PATH }) {
|
|
54
54
|
// `sub` = the inner-token PRINCIPAL (ops-token issuance, INSTALL.md §8) — distinguishes
|
|
55
55
|
// two automations sharing a transport identity. null for internal/user sessions without a sub claim.
|
|
56
56
|
//
|
|
@@ -69,7 +69,14 @@ export function appendAudit({ email, sub, body, status, transport, door, path =
|
|
|
69
69
|
// WRITTEN ONLY WHEN GIVEN, the same rule as `transport` and for the same reason: the existing log
|
|
70
70
|
// shape must not move for records that have no answer to this. A door that does not name itself is a
|
|
71
71
|
// record with no `door` key, not a record claiming to be from nowhere.
|
|
72
|
-
|
|
72
|
+
//
|
|
73
|
+
// `kind` is the principal's kind (ops, user, account, internal), and `namesVerbs` says, for an ops token
|
|
74
|
+
// only, whether it names its write verbs. An ops token that names none is being retired: re-issued with
|
|
75
|
+
// verbs, then refused once this log shows none in use. So the log must be able to count them. Written
|
|
76
|
+
// only when given, by the same rule as `transport`: a caller that never knew writes no claim.
|
|
77
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), email: email ?? null, sub: sub ?? null,
|
|
78
|
+
...(kind ? { kind } : {}), ...(typeof namesVerbs === "boolean" ? { namesVerbs } : {}),
|
|
79
|
+
...summarize(body), status: status ?? null, ...(transport ? { transport } : {}), door: door || UNNAMED_DOOR }) + "\n";
|
|
73
80
|
try { mkdirSync(dirname(path), { recursive: true }); appendFileSync(path, line); } catch { /* best-effort */ }
|
|
74
81
|
}
|
|
75
82
|
|
|
@@ -278,7 +278,11 @@ export function makeHttpHandler({ verify, limiter, opsLimiter = null, sessions,
|
|
|
278
278
|
: send(res, 401, { error: `invalid trademark token: ${e.message}` });
|
|
279
279
|
}
|
|
280
280
|
const transport = await createSession(sessions, scope, user.email);
|
|
281
|
-
|
|
281
|
+
// `namesVerbs` rides with the session so EVERY call it makes is audited with it, not only the
|
|
282
|
+
// first: an ops token that names no verbs is being retired (re-issued with verbs, then refused),
|
|
283
|
+
// and the audit is how the live ones are found. Whether it names verbs, never which, never the token.
|
|
284
|
+
const namesVerbs = scope.kind === "ops" ? Array.isArray(scope.verbs) && scope.verbs.length > 0 : null;
|
|
285
|
+
entry = { transport, sub: scope.sub ?? null, kind: scope.kind ?? null, namesVerbs };
|
|
282
286
|
// STAMP THE SCOPE'S OWN FACTS ONTO THE STORED ENTRY, HERE, AND AFTER THE HANDSHAKE.
|
|
283
287
|
//
|
|
284
288
|
// `createSession` is injected, and every caller carries its own copy of the entry shape — two
|
|
@@ -293,7 +297,7 @@ export function makeHttpHandler({ verify, limiter, opsLimiter = null, sessions,
|
|
|
293
297
|
// and left the gate reading a field nobody had set — green, and doing nothing.
|
|
294
298
|
stampScope = () => {
|
|
295
299
|
const stored = transport.sessionId ? sessions.get(transport.sessionId) : null;
|
|
296
|
-
if (stored) { stored.sub = scope.sub ?? null; stored.kind = scope.kind ?? null; }
|
|
300
|
+
if (stored) { stored.sub = scope.sub ?? null; stored.kind = scope.kind ?? null; stored.namesVerbs = namesVerbs; }
|
|
297
301
|
};
|
|
298
302
|
} else {
|
|
299
303
|
if (entry.email && entry.email !== user.email) {
|
|
@@ -328,7 +332,7 @@ export function makeHttpHandler({ verify, limiter, opsLimiter = null, sessions,
|
|
|
328
332
|
// `finish` fires once the response is fully sent, which is when `res.statusCode` is the answer
|
|
329
333
|
// the caller got rather than the default it started as.
|
|
330
334
|
res.once("finish", () => {
|
|
331
|
-
try { appendAudit({ email: user.email, sub: entry.sub ?? null, body, status: res.statusCode, door }); }
|
|
335
|
+
try { appendAudit({ email: user.email, sub: entry.sub ?? null, kind: entry.kind ?? null, namesVerbs: entry.namesVerbs ?? null, body, status: res.statusCode, door }); }
|
|
332
336
|
catch { /* best-effort */ }
|
|
333
337
|
});
|
|
334
338
|
const answered = entry.transport.handleRequest(req, res, body);
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// intake connector never needs stop_run.
|
|
16
16
|
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
|
-
import { mintToken, verifyToken
|
|
18
|
+
import { mintToken, verifyToken } from "../shared/scope.mjs";
|
|
19
19
|
import { homedir } from "node:os";
|
|
20
20
|
import { defaultDenylistPath } from "../shared/client-door.mjs"; // — one owner for the path
|
|
21
21
|
import { isEntrypoint } from "../shared/is-entrypoint.mjs"; // — one entry-point test, all spellings
|
|
@@ -25,7 +25,7 @@ function fail(msg, code = 1) { process.stderr.write(`mint-token: ${msg}\n`); pro
|
|
|
25
25
|
const USAGE = `usage: node mint-token.mjs --scope ops|user|account [options]
|
|
26
26
|
--scope ops an automation/operator principal (write verbs allowed)
|
|
27
27
|
--sub <name> REQUIRED for ops: the principal name (rides into the audit log)
|
|
28
|
-
--verbs a,b
|
|
28
|
+
--verbs a,b REQUIRED for ops: the write tools this token may call
|
|
29
29
|
--accounts a,b GRANTS cap: the account keys (profileKeys) this token may see/start (omit = all)
|
|
30
30
|
--ttl-days <n> default 30
|
|
31
31
|
--scope user a run-bound report-link token (read-only client layer)
|
|
@@ -76,6 +76,12 @@ export function mintFromOptions({ scope, sub = null, runId = null, ttlDays, verb
|
|
|
76
76
|
// They belong here rather than at either caller for exactly the reason the extraction existed: a rule
|
|
77
77
|
// enforced by one of two callers is a rule the other one does not have.
|
|
78
78
|
if (scope === "ops" && !sub) throw new Error("--sub is required for an ops token — the audit log must name the principal");
|
|
79
|
+
// AN OPS TOKEN NAMES ITS VERBS. One that named none held every write verb, and over the network that
|
|
80
|
+
// included starting a what-if; it is the most valuable credential the product issues, so it is no longer
|
|
81
|
+
// issued. Tokens already out keep working: the door records their use (the audit line's `namesVerbs`) so
|
|
82
|
+
// they can be found, re-issued with verbs and then refused, in that order.
|
|
83
|
+
if (scope === "ops" && !(Array.isArray(verbs) && verbs.length))
|
|
84
|
+
throw new Error("--verbs is required for an ops token — name the write tools it may call, for example --verbs start_run,stop_run");
|
|
79
85
|
if (scope === "user" && !runId) throw new Error("--run is required for a user token (it is bound to exactly one run)");
|
|
80
86
|
if (scope === "account" && !sub) throw new Error("--sub is required for an account key — it names the grants-file identity the key reads its accounts from");
|
|
81
87
|
if (!Number.isFinite(ttlDays) || ttlDays <= 0) throw new Error("--ttl-days must be a positive number");
|
|
@@ -110,10 +116,6 @@ export function mintFromOptions({ scope, sub = null, runId = null, ttlDays, verb
|
|
|
110
116
|
notes.push(`revoke this TOKEN specifically: add "${t.jti}" as a line in ${denylistFile()}`);
|
|
111
117
|
if (scope === "account")
|
|
112
118
|
notes.push(`the grants row IS the reach — an account key reads its accounts from the grants file on every request`);
|
|
113
|
-
if (scope === "ops" && !verbs) {
|
|
114
|
-
const writable = Object.keys(TOOL_SCOPES).filter((k) => TOOL_SCOPES[k].write);
|
|
115
|
-
notes.push(`note: FULL ops authority (${writable.join(", ")}) — consider --verbs for automation principals`);
|
|
116
|
-
}
|
|
117
119
|
return { token, claims: t, notes };
|
|
118
120
|
}
|
|
119
121
|
|
package/mcp-server/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trademark-artifacts-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0-beta.1",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"private": true,
|
|
6
6
|
"description": "MCP server to interrogate clearotron trademark-clearance runs — list/read artifacts, trace the full decision flow, telemetry/cost, coverage, single-run search, and a gated single-step what-if. Imports the clearotron-driver read-only; touches no driver/template/deploy files.",
|
package/mcp-server/server.mjs
CHANGED
|
@@ -668,7 +668,7 @@ const TOOL_DEFS = [
|
|
|
668
668
|
{ name: "plan_run", description: "FREE PREVIEW — resolve what a search WOULD do and return it, spending nothing and queueing nothing. Takes the SAME arguments as start_run. Use this BEFORE start_run whenever the requester has not already confirmed the specifics: it reports the PRODUCT that resolves and WHERE it came from (their request, the account default, a saved search, or the territories themselves), the territories and marketplaces that would ACTUALLY be searched, the turnaround, and any blockers as questions to put back. Show the result to the requester; then call start_run with the SAME arguments to actually run it. Nothing here reserves or holds anything.", inputSchema: { type: "object", properties: { markName: { type: "string", description: "The mark (single-mark form). REQUIRED unless marks[] is given." }, marks: { type: "array", items: { type: "object", properties: { name: { type: "string" }, classes: { type: "array", items: { type: "number" } }, ref: { type: "string" } }, required: ["name"] } }, classes: { type: "array", items: { type: "number" } }, goods: { type: "string" }, jurisdictions: { type: "array", items: { type: "string" }, description: JURISDICTIONS_DESC }, platforms: { type: "array", items: { type: "string" } }, customer: { type: "string" }, profileKey: { type: "string", description: "The customer ACCOUNT key (see list_profiles). OMIT it for a new or unknown client and the search runs under the neutral generic profile, which is never a reason to refuse a search — offer that and the portal route (their own company, with its own framework and territories) in the same reply. On a session whose access names accounts, omitting it needs the generic profile to be one of them; when it is not, the refusal says so and names what the session does hold (see describe_options)." }, projectKey: { type: "string" }, forwarder: { type: "string", description: "Requester/reply-routing key — same as start_run, so the preview describes the job that would actually be built. Account sessions may omit it — it is stamped from your verified identity." }, forwarderEmail: { type: "string" }, ref: { type: "string" }, product: { type: "string", enum: ORDERABLE_PRODUCTS, description: PRODUCT_DESC }, nativeLanguage: { type: "boolean", description: "OPTIONAL, and only on a multi-country-focus-search: the native-language investigation. Send TRUE or omit the field — FALSE IS REFUSED, not honoured: the toggle only ever added, so there is nothing for false to switch off, and accepting it would let you believe you had removed a reading that runs anyway. It runs AUTOMATICALLY on a full-country-search and is not part of the other two, so asking for it there is refused rather than accepted and ignored. It routes on territory — the scope must name one it covers." }, worldwide: { type: "boolean", description: "Search EVERYWHERE, and refuse to be narrowed by the account's own default territories. This is not the same as omitting jurisdictions, which means \"whatever the account says\" — the two used to be indistinguishable on this wire, which is how a request that bought everywhere ran an account's seven countries. Never send \"Worldwide\" as a jurisdictions entry." }, recipeKey: { type: "string" }, upfrontInstructions: { type: "string" }, deadline: { type: "string" }, deliveryRoute: { type: "string", enum: ["email", "portal"], description: "OPTIONAL: how the delivered packet leaves — email (default). \"portal\" is reserved for the portal delivery lane and CLARIFIES at admission until it ships (never a silent no-op) — so the preview BLOCKS it here rather than describing a job start_run could not run." }, commercialFlexibility: { type: "string" }, priorUse: { type: "string" }, campaignShape: { type: "string", description: "Campaign-shape FACTS from the request/client — how the mark will be deployed: standalone brand vs flavour/sub-brand under a NAMED house mark; seasonal/limited vs permanent; launch scale. Verbatim facts only — the engine records them on the matter frame instead of inferring a launch shape." }, customerUnknown: { type: "boolean" } }, required: ["forwarder"] } },
|
|
669
669
|
// ---- OPS write-face (requires an ops token; never exposed to a user/internal session) ----
|
|
670
670
|
{ name: "start_run", description: "OPS-ONLY. Enqueue a NEW clearance/search run the driver will pick up. Provide markName (or marks[] for a batch) + forwarder (and ideally classes/goods/customer/profileKey/ref). Returns the queue id + slug; the runId/codename is assigned when the runner claims it — poll list_runs (by markName) or run_changes for progress. Spends real money once it runs.", inputSchema: { type: "object", properties: { agent: { type: "string", description: "Which agent workspace's queue. Omit for the deployment default (CLEAROTRON_QUEUE_DIR or the default agent's queue — docs/INTAKE.md)." }, markName: { type: "string", description: "The mark (single-mark form). Required unless marks[] is provided." }, marks: { type: "array", items: { type: "object", properties: { name: { type: "string" }, classes: { type: "array", items: { type: "number" } }, ref: { type: "string" } }, required: ["name"] }, description: "Batch form: EVERY name of the batch in ONE job. Only a knockout-search reads more than one name at a time; a clearance reads one, and more is refused rather than truncated." }, classes: { type: "array", items: { type: "number" }, description: "Nice classes — whole numbers 1–45 (1–34 goods, 35–45 services). classes OR goods; either suffices." }, goods: { type: "string" }, jurisdictions: { type: "array", items: { type: "string" }, description: JURISDICTIONS_DESC }, platforms: { type: "array", items: { type: "string" }, description: "OPTIONAL per-run SCOPE: extra marketplaces to sweep, as bare store domains e.g. [\"gnc.com\"]. ADDED to the account's own marketplaces — additive only, never a replacement (a client's platform list is a mandate). Max 10; \"web\" is implicit and must not be listed." }, customer: { type: "string" }, profileKey: { type: "string", description: "The customer ACCOUNT key (see list_profiles) whose profile rates this run; omit for the neutral generic profile — that is how a clearance for a NEW client runs, and a client with no account yet is never a reason to refuse one. Offer it alongside the portal route (their own company, with its own framework, territories and marketplaces) so the requester chooses. On a session whose access names accounts, omitting it needs the generic profile to be one of them; when it is not, the refusal says so and names what the session does hold (see describe_options)." }, forwarder: { type: "string", description: "Requester/reply-routing key — rides the delivery packet so the integrator knows who gets the report (docs/DELIVERY.md). Account sessions may omit it — it is stamped from your verified identity." }, forwarderEmail: { type: "string" }, ref: { type: "string" }, provider: { type: "string" }, brief: { type: "string" }, upfrontInstructions: { type: "string" }, projectKey: { type: "string", description: "OPTIONAL (spec 62): the PROJECT/engagement key UNDER profileKey (see that customer's projects[] in list_profiles) whose overlay rates this run; omit to run on the customer profile. An unknown key clarifies at intake." }, product: { type: "string", enum: ORDERABLE_PRODUCTS, description: PRODUCT_DESC }, nativeLanguage: { type: "boolean", description: "OPTIONAL, and only on a multi-country-focus-search: the native-language investigation. Send TRUE or omit the field — FALSE IS REFUSED, not honoured: the toggle only ever added, so there is nothing for false to switch off, and accepting it would let you believe you had removed a reading that runs anyway. It runs AUTOMATICALLY on a full-country-search and is not part of the other two, so asking for it there is refused rather than accepted and ignored. It routes on territory — the scope must name one it covers." }, worldwide: { type: "boolean", description: "Search EVERYWHERE, and refuse to be narrowed by the account's own default territories. This is not the same as omitting jurisdictions, which means \"whatever the account says\" — the two used to be indistinguishable on this wire, which is how a request that bought everywhere ran an account's seven countries. Never send \"Worldwide\" as a jurisdictions entry." }, recipeKey: { type: "string", description: "OPTIONAL alternative selector: a saved search (recipe) slug for this customer. Mutually exclusive with product — a saved search already carries one." }, deliveryRoute: { type: "string", enum: ["email", "portal"], description: "OPTIONAL: how the delivered packet leaves — email (default). \"portal\" is reserved for the portal delivery lane and CLARIFIES at admission until it ships (never a silent no-op)." }, parentRunId: { type: "string", description: "OPTIONAL escalation lineage: the runId this run escalates from (e.g. a knockout HIGH mark → this clearotron)." }, deliverableSpec: { type: "string" }, commercialFlexibility: { type: "string" }, priorUse: { type: "string" }, campaignShape: { type: "string", description: "Campaign-shape FACTS from the request/client — how the mark will be deployed: standalone brand vs flavour/sub-brand under a NAMED house mark; seasonal/limited vs permanent; launch scale. Verbatim facts only — the engine records them on the matter frame instead of inferring a launch shape." }, deadline: { type: "string", description: "ISO-8601 — drives the deadline envelope." }, customerUnknown: { type: "boolean" }, dupOverride: { type: "boolean", description: "Requester-confirmed force-run past matter dedup." }, clientPrincipal: { type: "boolean", description: "Set by the client portal only: this run was started by a CLIENT and consumes that account's runCaps.dailyRuns allowance. Omit for staff, agent and email-door runs — absence means uncapped, and setting it can only restrict the run that carries it." }, rawRequest: { type: "string" }, forwarderDomain: { type: "string" }, msgId: { type: "string" }, conversationId: { type: "string" }, id: { type: "string" } }, required: ["forwarder"] } },
|
|
671
|
-
{ name: "stop_run", description: "OPS-ONLY. Cancel a run. Pass id to remove a not-yet-claimed queued job (a real cancel, no spend). Pass runId to file a cancel request on a started run. By default the cancel is COOPERATIVE — the sentinel is written and a turn already in flight is allowed to finish, which has no deadline: a reasoning turn can run for tens of minutes. Add immediate:true to also end that turn: it is sent a stop, anything of it still running a few seconds later is killed, and the answer says the turn ended only once it has seen it end. If it could not be ended, the run ends at its next step boundary, and the answer says that instead.", inputSchema: { type: "object", properties: { agent: { type: "string" }, id: { type: "string", description: "A queued job id (from start_run) — dequeues before the runner claims it." }, runId: { type: "string", description: "A started run's runId — files a cancel sentinel." }, immediate: { type: "boolean", description: "Stop NOW instead of at the next step boundary. The cancel sentinel is written FIRST and then the run's own recorded engine turn is stopped, in that order — the sentinel is what makes the record clean, so the terminal names the stage, the actor and the request time instead of leaving a run that reads as still running. The turn is sent SIGTERM (to its whole process group when it leads one, as both engines spawn it, so the tools it started are reached too), watched, and sent SIGKILL if anything of it is still running when the grace runs out; the answer waits for that, which takes seconds. A turn that is stopped loses its step's work; everything already recorded is kept. Sending the stop is not the same as the turn ending, and the answer claims the second only when it has seen it (immediate.ended). Falls back to the boundary stop, and SAYS SO in the answer, when there is no live turn to signal — a run between steps, or one whose turn has already exited — or when the turn was still there after the SIGKILL. The answer states which of the two actually happened."}, onBehalfOf: { type: "string", description: "The identifier of a human this caller has itself verified
|
|
671
|
+
{ name: "stop_run", description: "OPS-ONLY. Cancel a run. Pass id to remove a not-yet-claimed queued job (a real cancel, no spend). Pass runId to file a cancel request on a started run. By default the cancel is COOPERATIVE — the sentinel is written and a turn already in flight is allowed to finish, which has no deadline: a reasoning turn can run for tens of minutes. Add immediate:true to also end that turn: it is sent a stop, anything of it still running a few seconds later is killed, and the answer says the turn ended only once it has seen it end. If it could not be ended, the run ends at its next step boundary, and the answer says that instead.", inputSchema: { type: "object", properties: { agent: { type: "string" }, id: { type: "string", description: "A queued job id (from start_run) — dequeues before the runner claims it." }, runId: { type: "string", description: "A started run's runId — files a cancel sentinel." }, immediate: { type: "boolean", description: "Stop NOW instead of at the next step boundary. The cancel sentinel is written FIRST and then the run's own recorded engine turn is stopped, in that order — the sentinel is what makes the record clean, so the terminal names the stage, the actor and the request time instead of leaving a run that reads as still running. The turn is sent SIGTERM (to its whole process group when it leads one, as both engines spawn it, so the tools it started are reached too), watched, and sent SIGKILL if anything of it is still running when the grace runs out; the answer waits for that, which takes seconds. A turn that is stopped loses its step's work; everything already recorded is kept. Sending the stop is not the same as the turn ending, and the answer claims the second only when it has seen it (immediate.ended). Falls back to the boundary stop, and SAYS SO in the answer, when there is no live turn to signal — a run between steps, or one whose turn has already exited — or when the turn was still there after the SIGKILL. The answer states which of the two actually happened."}, onBehalfOf: { type: "string", description: "The identifier of a human this caller has itself verified. RECORDED BESIDE the token's own principal, never instead of it — a stop by the portal for alice@x is written `portal:alice@x`, so an asserted name is never mistaken for a proved one. Bare identifiers only; anything else is dropped." } } } },
|
|
672
672
|
{ name: "server_info", description: "The AGPL §13 source offer: this server's name, version, licence, source repository and the COMMIT IT IS RUNNING. Callable by every session kind — the offer is owed to whoever interacts with the service. Reads no run and no account data.", inputSchema: { type: "object", properties: {} } },
|
|
673
673
|
{ name: "list_outbox_events", description: "Integrator discovery read (docs/DELIVERY.md): every pending outbox event — delivered markers plus the self-contained run-failed / intake-rejected / duplicate-skipped / late-bind-ack packets. Route each, then mark_sent (delivered) or ack_event (the rest).", inputSchema: { type: "object", properties: {} } },
|
|
674
674
|
{ name: "get_delivery_packet", description: "The run's send payload: _driver/delivery.json (subject, recipient routing, verbatim emailBodyHtml, whatsapp line) and/or failure.json, plus sendPending/.sent state. NOT exposed to client tokens.", inputSchema: { type: "object", properties: { runId: { type: "string" } }, required: ["runId"] } },
|
|
@@ -851,6 +851,16 @@ export function attachHandlers(server, { scope = { kind: "ops", runId: null }, l
|
|
|
851
851
|
let authedArgs;
|
|
852
852
|
try { authedArgs = authorize(scope, name, args); }
|
|
853
853
|
catch (e) { log(`authz deny ${name} [${scope.kind}]: ${e.message}`); return { isError: true, content: [{ type: "text", text: `FORBIDDEN (${name}): ${e.message}` }] }; }
|
|
854
|
+
// THE LISTING IS THE WALL. A tool this session's audience is not shown is one it cannot call: this
|
|
855
|
+
// used to filter only the listing, and a hidden tool called by name ran anyway once authorize()
|
|
856
|
+
// passed it. That is how an ops token reached what_if_run over HTTP, where the listing hides it and
|
|
857
|
+
// the engine starts. After authorize(), so a caller it already refuses keeps the refusal it names;
|
|
858
|
+
// this catches only what it lets through. Same answer as a name that does not exist, because to this
|
|
859
|
+
// session it does not.
|
|
860
|
+
if (!allow(name)) {
|
|
861
|
+
log(`listing deny ${name} [${scope.kind}${local ? "" : ", http"}]: not offered to this session`);
|
|
862
|
+
return { isError: true, content: [{ type: "text", text: `unknown tool "${name}"` }] };
|
|
863
|
+
}
|
|
854
864
|
// ACCOUNT GATE (GRANTS, INSTALL.md §8): a run-addressed call from an account-scoped session must
|
|
855
865
|
// target a run inside the grant. Enforced BEFORE dispatch so a denied run leaks nothing — not even
|
|
856
866
|
// its existence (the deny message names the account, never the run's contents).
|
package/package.json
CHANGED
|
@@ -11865,6 +11865,7 @@ var api = {
|
|
|
11865
11865
|
accountNames: Object.fromEntries(Object.entries(asRecord(b["accountNames"])).filter(([, v]) => typeof v === "string" && v)),
|
|
11866
11866
|
accountFacts: Object.fromEntries(Object.entries(asRecord(b["accountFacts"])).map(([k, v]) => [k, asCompanyFacts(v)])),
|
|
11867
11867
|
concurrentRuns: asNumber(b["concurrentRuns"]),
|
|
11868
|
+
houseMarketplaces: asStrings(b["houseMarketplaces"]),
|
|
11868
11869
|
engineMode: b["engineMode"] === "demo" ? "demo" : b["engineMode"] === "engine-unproven" ? "engine-unproven" : null,
|
|
11869
11870
|
engineProgramDisputed: b["engineProgramDisputed"] === true ? true : b["engineProgramDisputed"] === false ? false : null,
|
|
11870
11871
|
setupRoute: b["setupRoute"] === "packaged" ? "packaged" : b["setupRoute"] === "checkout" ? "checkout" : null,
|
|
@@ -21701,11 +21702,14 @@ var PROFILE_FIELDS = [
|
|
|
21701
21702
|
label: "Marketplaces",
|
|
21702
21703
|
kind: "lines",
|
|
21703
21704
|
group: "defaults",
|
|
21705
|
+
picker: "marketplaces",
|
|
21706
|
+
clearToEmptyList: true,
|
|
21704
21707
|
item: {
|
|
21705
21708
|
ok: (e) => {
|
|
21706
21709
|
const d = e.trim().toLowerCase();
|
|
21707
21710
|
return d !== "web" && !/\s/.test(d) && /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(d);
|
|
21708
21711
|
},
|
|
21712
|
+
strict: true,
|
|
21709
21713
|
expected: "a bare store domain like amazon.com"
|
|
21710
21714
|
},
|
|
21711
21715
|
hint: "One per line. A project can add to this list; it can never remove one."
|
|
@@ -21752,7 +21756,10 @@ var PROJECT_EDITABLE = new Set([
|
|
|
21752
21756
|
"industry",
|
|
21753
21757
|
"defaultProduct"
|
|
21754
21758
|
]);
|
|
21755
|
-
var projectFields = () => PROFILE_FIELDS.filter((f) => PROJECT_EDITABLE.has(rootKey(f)) && !f.customerOnly)
|
|
21759
|
+
var projectFields = () => PROFILE_FIELDS.filter((f) => PROJECT_EDITABLE.has(rootKey(f)) && !f.customerOnly).map((f) => f.clearToEmptyList ? {
|
|
21760
|
+
...f,
|
|
21761
|
+
clearToEmptyList: false
|
|
21762
|
+
} : f);
|
|
21756
21763
|
var choiceLabel = (spec, value) => spec.choices?.find((c) => c.value === value)?.label ?? null;
|
|
21757
21764
|
function depthChoices(products) {
|
|
21758
21765
|
return products.map((l) => {
|
|
@@ -21800,6 +21807,10 @@ function applyField(draft, spec, raw) {
|
|
|
21800
21807
|
const next = { ...draft };
|
|
21801
21808
|
const trimmed = raw.trim();
|
|
21802
21809
|
if (!trimmed) {
|
|
21810
|
+
if (spec.clearToEmptyList) {
|
|
21811
|
+
next[spec.key] = [];
|
|
21812
|
+
return next;
|
|
21813
|
+
}
|
|
21803
21814
|
if (spec.clearWith !== void 0) {
|
|
21804
21815
|
next[spec.key] = spec.clearWith;
|
|
21805
21816
|
return next;
|
|
@@ -21947,7 +21958,7 @@ var suggestionStyle = {
|
|
|
21947
21958
|
background: "var(--surface-raised)",
|
|
21948
21959
|
color: "var(--text-muted)"
|
|
21949
21960
|
};
|
|
21950
|
-
function FieldPicker({ spec, value, onChange }) {
|
|
21961
|
+
function FieldPicker({ spec, value, onChange, suggestions = [] }) {
|
|
21951
21962
|
const [query, setQuery] = (0, import_react.useState)("");
|
|
21952
21963
|
const addId = (0, import_react.useId)();
|
|
21953
21964
|
const addBox = (0, import_react.useRef)(null);
|
|
@@ -22026,7 +22037,43 @@ function FieldPicker({ spec, value, onChange }) {
|
|
|
22026
22037
|
})]
|
|
22027
22038
|
});
|
|
22028
22039
|
}
|
|
22029
|
-
|
|
22040
|
+
if (spec.picker === "marketplaces") {
|
|
22041
|
+
const have = new Set(chosen.map((c) => c.trim().toLowerCase()));
|
|
22042
|
+
const offered = suggestions.filter((d) => !have.has(d.trim().toLowerCase()));
|
|
22043
|
+
return (0, import_jsx_runtime.jsxs)("div", {
|
|
22044
|
+
style: { marginTop: 6 },
|
|
22045
|
+
children: [offered.length ? (0, import_jsx_runtime.jsx)("div", {
|
|
22046
|
+
style: {
|
|
22047
|
+
display: "flex",
|
|
22048
|
+
flexWrap: "wrap",
|
|
22049
|
+
gap: 4
|
|
22050
|
+
},
|
|
22051
|
+
children: offered.map((d) => (0, import_jsx_runtime.jsxs)("button", {
|
|
22052
|
+
type: "button",
|
|
22053
|
+
onClick: () => toggle(d),
|
|
22054
|
+
style: suggestionStyle,
|
|
22055
|
+
children: ["+ ", d]
|
|
22056
|
+
}, d))
|
|
22057
|
+
}) : null, chosen.length ? (0, import_jsx_runtime.jsx)("div", {
|
|
22058
|
+
className: "chip-row",
|
|
22059
|
+
style: { marginTop: 8 },
|
|
22060
|
+
children: chosen.map((d) => (0, import_jsx_runtime.jsxs)("span", {
|
|
22061
|
+
className: "chip chip-own",
|
|
22062
|
+
children: [d, (0, import_jsx_runtime.jsx)("button", {
|
|
22063
|
+
type: "button",
|
|
22064
|
+
className: "chip-x",
|
|
22065
|
+
onClick: () => toggle(d),
|
|
22066
|
+
"aria-label": `Remove ${d}`,
|
|
22067
|
+
children: (0, import_jsx_runtime.jsx)(Icon, {
|
|
22068
|
+
name: "x",
|
|
22069
|
+
size: 13
|
|
22070
|
+
})
|
|
22071
|
+
})]
|
|
22072
|
+
}, d))
|
|
22073
|
+
}) : null]
|
|
22074
|
+
});
|
|
22075
|
+
}
|
|
22076
|
+
const found = matchTerritoriesIn(TERRITORIES, query, chosen, 8);
|
|
22030
22077
|
return (0, import_jsx_runtime.jsxs)("div", {
|
|
22031
22078
|
style: { marginTop: 6 },
|
|
22032
22079
|
children: [
|
|
@@ -22046,14 +22093,14 @@ function FieldPicker({ spec, value, onChange }) {
|
|
|
22046
22093
|
fontFamily: "inherit"
|
|
22047
22094
|
}
|
|
22048
22095
|
}),
|
|
22049
|
-
|
|
22096
|
+
found.length ? (0, import_jsx_runtime.jsx)("div", {
|
|
22050
22097
|
style: {
|
|
22051
22098
|
display: "flex",
|
|
22052
22099
|
flexWrap: "wrap",
|
|
22053
22100
|
gap: 4,
|
|
22054
22101
|
marginTop: 5
|
|
22055
22102
|
},
|
|
22056
|
-
children:
|
|
22103
|
+
children: found.map((t) => (0, import_jsx_runtime.jsxs)("button", {
|
|
22057
22104
|
type: "button",
|
|
22058
22105
|
onClick: () => {
|
|
22059
22106
|
toggle(t);
|
|
@@ -22092,7 +22139,7 @@ function FieldTag({ tag }) {
|
|
|
22092
22139
|
children: tag
|
|
22093
22140
|
});
|
|
22094
22141
|
}
|
|
22095
|
-
function Field({ spec, value, choices, onChange }) {
|
|
22142
|
+
function Field({ spec, value, choices, onChange, suggestions }) {
|
|
22096
22143
|
const picker = spec.kind === "choice" || spec.kind === "boolean";
|
|
22097
22144
|
const multi = spec.kind === "lines" || spec.kind === "prose";
|
|
22098
22145
|
const classes = spec.picker === "classes";
|
|
@@ -22138,7 +22185,8 @@ function Field({ spec, value, choices, onChange }) {
|
|
|
22138
22185
|
(0, import_jsx_runtime.jsx)(FieldPicker, {
|
|
22139
22186
|
spec,
|
|
22140
22187
|
value,
|
|
22141
|
-
onChange
|
|
22188
|
+
onChange,
|
|
22189
|
+
...suggestions ? { suggestions } : {}
|
|
22142
22190
|
})
|
|
22143
22191
|
]
|
|
22144
22192
|
});
|
|
@@ -22188,7 +22236,7 @@ function NewCompany({ ctx }) {
|
|
|
22188
22236
|
const key = derivedKey || (keyTyped ?? "");
|
|
22189
22237
|
useUnsaved((Boolean(typedName) || Object.keys(state.edits).length > 0) && !result);
|
|
22190
22238
|
const refusedEntries = specs().flatMap((spec) => {
|
|
22191
|
-
if (!spec.item?.strict) return [];
|
|
22239
|
+
if (!spec.item?.strict || spec.key === "platforms") return [];
|
|
22192
22240
|
const raw = boxValue(state, spec);
|
|
22193
22241
|
if (!raw.trim()) return [];
|
|
22194
22242
|
return parseLines(raw, spec.commaSeparated ?? false).filter((e) => !spec.item.ok(e));
|
|
@@ -22319,6 +22367,7 @@ function NewCompany({ ctx }) {
|
|
|
22319
22367
|
spec,
|
|
22320
22368
|
value: spec.key === "selfExclusionOwners" && !namesTouched ? typedName : boxValue(state, spec),
|
|
22321
22369
|
choices: null,
|
|
22370
|
+
suggestions: ctx.me.houseMarketplaces,
|
|
22322
22371
|
onChange: (v) => {
|
|
22323
22372
|
if (spec.key === "selfExclusionOwners") setNamesTouched(true);
|
|
22324
22373
|
setState((st) => typeField(st, spec, v));
|
|
@@ -23629,7 +23678,8 @@ function Profile({ ctx }) {
|
|
|
23629
23678
|
edits
|
|
23630
23679
|
}, spec),
|
|
23631
23680
|
choices: spec.key === "defaultProduct" ? productChoices : spec.choices ?? null,
|
|
23632
|
-
onChange: (v) => edit(spec, v)
|
|
23681
|
+
onChange: (v) => edit(spec, v),
|
|
23682
|
+
suggestions: ctx.me.houseMarketplaces
|
|
23633
23683
|
}),
|
|
23634
23684
|
spec.key === "defaultJurisdictions" ? (0, import_jsx_runtime.jsx)(UnsearchableTerritories, { derived: loaded.derived }) : null,
|
|
23635
23685
|
spec.key === "defaultProduct" && coverageOf(loaded.derived) ? (0, import_jsx_runtime.jsxs)("details", {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
-->
|
|
50
50
|
<link rel="preconnect" href="https://api.fontshare.com" crossorigin />
|
|
51
51
|
<link href="https://api.fontshare.com/v2/css?f[]=satoshi@400,500,700,900&display=swap" rel="stylesheet" />
|
|
52
|
-
<script type="module" crossorigin src="/portal/assets/index-
|
|
52
|
+
<script type="module" crossorigin src="/portal/assets/index-D_O_55vK.js"></script>
|
|
53
53
|
<link rel="stylesheet" crossorigin href="/portal/assets/index-5CCwiJG7.css">
|
|
54
54
|
</head>
|
|
55
55
|
<body>
|
package/portal-ui/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "portal-ui",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.4.0-beta.1",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
7
7
|
"description": "The unified trademark portal UI. One address, one login: who you are decides what you see. Built as a static bundle, served by driver/portal-service.mjs — the browser never reaches profile-service or recipe-service.",
|
|
8
8
|
"engines": {
|
|
@@ -21,7 +21,7 @@ documents both: `countProbe` (`"endpoint"` / `"cheap"` / `"none"`) says where a
|
|
|
21
21
|
| `transport-guard.mjs` | Converts a network REJECTION into the tool-error shape at the dependency seam, so one timeout degrades one query rather than killing the stage. |
|
|
22
22
|
| `result-shape.mjs` | Declares the neutral tools' result vocabulary. Imported by its gate test and deliberately by no core — a tool result is the model's prompt surface. |
|
|
23
23
|
| `script-form.mjs` | Which script a term is written in, and whether the active provider's index holds that form of it. |
|
|
24
|
-
| `term-shape.mjs` | Whether a string is a searchable mark term and agrees with its predicate (`{ predicate: "exact", term: "
|
|
24
|
+
| `term-shape.mjs` | Whether a string is a searchable mark term and agrees with its predicate (`{ predicate: "exact", term: "WAVO*" }` is a silent clean). |
|
|
25
25
|
| `territory-codes.mjs` | Display name → ISO 3166 / WIPO ST.3 code. `""` means worldwide, `null` means unknown — and never goes to the wire. |
|
|
26
26
|
| `provider-text.mjs` | Clips a vendor message from the MIDDLE, because the word that classifies a refusal sits at the end of the sentence. |
|
|
27
27
|
| `test/` | Twelve files, provider-agnostic by design — several drive every adapter's real code rather than fixtures of what we believe it returns. |
|