clearotron 0.2.2 → 0.2.4

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.
Files changed (161) hide show
  1. package/.env.example +64 -37
  2. package/.nvmrc +1 -1
  3. package/CONTRIBUTING.md +8 -4
  4. package/INSTALL.md +36 -14
  5. package/README.md +18 -22
  6. package/THIRD-PARTY-NOTICES.md +1 -1
  7. package/bin/clearotron.mjs +22 -0
  8. package/bin/example.mjs +10 -5
  9. package/bin/onboard.mjs +370 -28
  10. package/bin/start.mjs +220 -12
  11. package/build-info.json +2 -2
  12. package/demo/README.md +1 -1
  13. package/demo/full-country-search/run/_driver/framework.json +1 -1
  14. package/demo/global-preliminary-search/run/_driver/framework.json +1 -1
  15. package/demo/knockout-search/run/_driver/framework.json +1 -1
  16. package/demo/multi-country-focus-search/run/_driver/framework.json +1 -1
  17. package/docs/E2E.md +5 -5
  18. package/docs/GLOSSARY.md +85 -0
  19. package/docs/PORTAL.md +2 -2
  20. package/docs/README.md +6 -4
  21. package/docs/SECURITY.md +17 -0
  22. package/docs/architecture/01-product-overview.md +21 -9
  23. package/docs/architecture/04-configuration-reference.md +2 -0
  24. package/docs/architecture/05-config-governance.md +19 -0
  25. package/docs/architecture/05-customer-profiles.md +2 -2
  26. package/docs/configuration.md +4 -3
  27. package/docs/decisions/0006-what-the-public-repository-carries.md +30 -5
  28. package/driver/CHANGELOG.md +90 -0
  29. package/driver/README.md +25 -6
  30. package/driver/common-law-receipts.mjs +1 -1
  31. package/driver/config-staleness.mjs +1 -1
  32. package/driver/connotation-search.mjs +1 -1
  33. package/driver/contract-audit.mjs +5 -1
  34. package/driver/contract-e3-baseline.json +11 -11
  35. package/driver/dev-portal.mjs +2 -2
  36. package/driver/doubt-selection.mjs +1 -1
  37. package/driver/drainer-identity.mjs +1 -1
  38. package/driver/driver.config.mjs +1 -1
  39. package/driver/effort-model.mjs +2 -2
  40. package/driver/engine/cli-version.mjs +103 -0
  41. package/driver/engine/probe.mjs +45 -5
  42. package/driver/enqueue-schema.mjs +8 -8
  43. package/driver/feedback-store.mjs +1 -1
  44. package/driver/findings-model.mjs +52 -2
  45. package/driver/flag-snapshot.mjs +20 -0
  46. package/driver/framework.mjs +1 -1
  47. package/driver/gateway.mjs +20 -3
  48. package/driver/outbox-backoff.mjs +1 -1
  49. package/driver/package.json +3 -3
  50. package/driver/party-facts.mjs +2 -2
  51. package/driver/phase0.mjs +1 -1
  52. package/driver/pipeline.mjs +211 -106
  53. package/driver/plain-register.mjs +134 -0
  54. package/driver/portal-access.mjs +1 -1
  55. package/driver/portal-config-view.mjs +70 -2
  56. package/driver/portal-families.mjs +1 -1
  57. package/driver/portal-local-auth.mjs +5 -1
  58. package/driver/portal-report.mjs +1 -1
  59. package/driver/portal-service.mjs +79 -9
  60. package/driver/portal-upstream.mjs +2 -2
  61. package/driver/predelivery-lint.mjs +151 -2
  62. package/driver/profile-page.html +10 -2
  63. package/driver/profile-service.mjs +4 -4
  64. package/driver/profiles/README.md +3 -3
  65. package/driver/profiles.mjs +144 -12
  66. package/driver/publish/index.mjs +2 -2
  67. package/driver/publish/knockout.mjs +7 -1
  68. package/driver/publish/render-knockout.mjs +453 -118
  69. package/driver/publish/render.mjs +111 -16
  70. package/driver/reference-strip-signatures.mjs +20 -0
  71. package/driver/search-policy.mjs +3 -3
  72. package/driver/skills/blind-frame/SKILL.md +1 -1
  73. package/driver/skills/knockout-assess/SKILL.md +84 -0
  74. package/driver/skills/matter-frame/SKILL.md +6 -6
  75. package/driver/skills/matter-frame/watchlist-reference.md +2 -2
  76. package/driver/skills/prelim-common-law/SKILL.md +1 -1
  77. package/driver/skills/prelim-search/SKILL.md +1 -1
  78. package/driver/skills/prelim-search/report-prose.md +82 -0
  79. package/driver/skills/prelim-search/risk-framework-demo.manifest.json +1 -1
  80. package/driver/skills/prelim-search/risk-framework-triage.manifest.json +1 -1
  81. package/driver/skills/prelim-search/risk-framework.manifest.json +1 -1
  82. package/driver/skills/prelim-search/synthesis-rules.md +1 -1
  83. package/driver/skills/prelim-variants/SKILL.md +3 -3
  84. package/driver/stage-context.mjs +13 -0
  85. package/driver/stages.mjs +54 -7
  86. package/driver/suite-census.json +220 -58
  87. package/driver/systemd/clearotron-worker.service +3 -3
  88. package/driver/tokens.mjs +1 -1
  89. package/driver/unit-inventory.mjs +214 -27
  90. package/driver/verify.mjs +1 -1
  91. package/driver/whatif-memo-run.mjs +26 -4
  92. package/driver/whatif-memo.mjs +19 -1
  93. package/examples/README.md +2 -2
  94. package/examples/grants.example.json +6 -7
  95. package/mcp-server/CHANGELOG.md +4 -0
  96. package/mcp-server/lib/scrub.mjs +1 -1
  97. package/mcp-server/lib/whatif.mjs +2 -2
  98. package/mcp-server/package.json +1 -1
  99. package/mcp-server/packs/ops/EXAMPLES.md +2 -2
  100. package/mcp-server/server.mjs +1 -1
  101. package/package.json +31 -19
  102. package/portal-ui/dist/assets/{index-Ct3iQtZ2.css → index-1ziUJX1E.css} +43 -2
  103. package/portal-ui/dist/assets/{index-BUGgiYtF.js → index-KFAHMgdT.js} +289 -73
  104. package/portal-ui/dist/index.html +2 -2
  105. package/portal-ui/package.json +1 -1
  106. package/providers/_shared/term-shape.mjs +1 -1
  107. package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
  108. package/providers/oauth-mcp-bridge/package.json +1 -1
  109. package/providers/uspto-local/src/index-store.js +2 -2
  110. package/scripts/added-reference-check.mjs +45 -89
  111. package/scripts/citation-line-check.mjs +121 -2
  112. package/scripts/e2e.mjs +2 -2
  113. package/scripts/env-classify.mjs +67 -3
  114. package/scripts/generated-files-are-current.mjs +125 -0
  115. package/scripts/live-surface-check.mjs +38 -1
  116. package/scripts/mint-names-in-force.mjs +4 -2
  117. package/scripts/mint-public-residue.mjs +74 -0
  118. package/scripts/mint-reference-strip-backlog.mjs +12 -1
  119. package/scripts/mint-suite-census.mjs +5 -2
  120. package/scripts/nightly-notice.mjs +0 -2
  121. package/scripts/preinstall-node-check.mjs +24 -0
  122. package/scripts/render-check.mjs +13 -2
  123. package/scripts/report-frame-check.mjs +8 -1
  124. package/scripts/report-screenshot.mjs +6 -3
  125. package/scripts/revisit-render-check.mjs +6 -3
  126. package/scripts/score.mjs +1 -1
  127. package/scripts/strip-tracker-citations.mjs +38 -7
  128. package/scripts/test-full.mjs +226 -0
  129. package/scripts/test-run.mjs +16 -0
  130. package/shared/README.md +2 -1
  131. package/shared/anon-overlay.mjs +3 -3
  132. package/shared/browser-temp-root.mjs +142 -0
  133. package/shared/checkout-move.mjs +1 -1
  134. package/shared/client-door.mjs +4 -3
  135. package/shared/customer-store.mjs +1 -1
  136. package/shared/identifier-scan.mjs +1 -1
  137. package/shared/install-auth.mjs +35 -0
  138. package/shared/invocation.mjs +33 -1
  139. package/shared/listen.mjs +55 -3
  140. package/shared/names-in-force.mjs +6 -1
  141. package/shared/node-floor.mjs +97 -0
  142. package/shared/os-advice.mjs +91 -0
  143. package/shared/reap-on-exit.mjs +42 -0
  144. package/shared/reference-guard-classes.mjs +351 -0
  145. package/shared/staff-domain.mjs +154 -0
  146. package/shared/store-in-repo.mjs +23 -2
  147. package/shared/suite-census.mjs +31 -3
  148. package/shared/withheld-paths-access.mjs +36 -18
  149. package/skills/clearotron-ops/SKILL.md +1 -1
  150. package/driver/recipes/README.md +0 -49
  151. package/driver/skills/prelim-search/risk-framework-aurora.manifest.json +0 -19
  152. package/driver/skills/prelim-search/risk-framework-aurora.md +0 -77
  153. package/driver/skills/prelim-search/risk-framework-zephyr.manifest.json +0 -14
  154. package/driver/skills/prelim-search/risk-framework-zephyr.md +0 -33
  155. package/driver/skills/prelim-search/worked-examples-aurora.md +0 -10
  156. package/driver/skills/prelim-search/worked-examples-zephyr.md +0 -10
  157. package/scripts/ai-page-render-check.mjs +0 -532
  158. package/scripts/clearances-render-check.mjs +0 -480
  159. package/scripts/composer-render-check.mjs +0 -1027
  160. package/scripts/home-render-check.mjs +0 -526
  161. package/scripts/portal-lifecycle-check.mjs +0 -639
@@ -10415,6 +10415,8 @@ var api = {
10415
10415
  accountNames: Object.fromEntries(Object.entries(asRecord(b["accountNames"])).filter(([, v]) => typeof v === "string" && v)),
10416
10416
  concurrentRuns: asNumber(b["concurrentRuns"]),
10417
10417
  engineMode: b["engineMode"] === "demo" ? "demo" : b["engineMode"] === "engine-unproven" ? "engine-unproven" : null,
10418
+ engineProgramDisputed: b["engineProgramDisputed"] === true ? true : b["engineProgramDisputed"] === false ? false : null,
10419
+ setupRoute: b["setupRoute"] === "packaged" ? "packaged" : b["setupRoute"] === "checkout" ? "checkout" : null,
10418
10420
  brand: typeof b["brand"] === "string" ? b["brand"] : "",
10419
10421
  stopControl: (() => {
10420
10422
  const c = asRecord(asRecord(b["controls"])["stop"]);
@@ -10715,7 +10717,9 @@ var api = {
10715
10717
  apiBilled: bill["apiBilled"] === true,
10716
10718
  missing: asStrings(bill["missing"])
10717
10719
  },
10718
- binaryPresent: e["binaryPresent"] === true
10720
+ binaryPresent: e["binaryPresent"] === true,
10721
+ program: asString(e["program"]),
10722
+ install: asString(e["install"])
10719
10723
  };
10720
10724
  })() : null,
10721
10725
  providers: Array.isArray(b["providers"]) ? asArray(b["providers"]).map((p) => {
@@ -10765,6 +10769,15 @@ var api = {
10765
10769
  name,
10766
10770
  modifiedAt
10767
10771
  } : null;
10772
+ })(),
10773
+ staffRule: (() => {
10774
+ const s = b["staffRule"];
10775
+ const name = asString(s?.["name"]);
10776
+ const where = asString(s?.["where"]);
10777
+ return name && where ? {
10778
+ name,
10779
+ where
10780
+ } : null;
10768
10781
  })()
10769
10782
  })),
10770
10783
  adminObserved: () => call("/portal/admin/observed", (b) => ({
@@ -11421,7 +11434,8 @@ function AppShell({ render }) {
11421
11434
  ownerName,
11422
11435
  ownerKeys,
11423
11436
  go,
11424
- visit
11437
+ visit,
11438
+ sidebarCollapsed: collapsed
11425
11439
  }) : (0, import_jsx_runtime.jsx)("div", {
11426
11440
  className: "screen",
11427
11441
  children: (0, import_jsx_runtime.jsxs)("div", {
@@ -14537,6 +14551,42 @@ function Missing({ go, reason }) {
14537
14551
  });
14538
14552
  }
14539
14553
  //#endregion
14554
+ //#region src/contract/engineState.ts
14555
+ function engineRowFaults(engine, { programDisputed = false } = {}) {
14556
+ return [
14557
+ ...engine.known ? [] : [`This build does not ship an engine called ${engine.id}.`],
14558
+ ...engine.billing.missing.length ? [`Set to bill an API key, and ${engine.billing.missing.join(" and ")} is not set — a run is refused rather than billed to the subscription.`] : [],
14559
+ ...engine.binaryPresent ? [] : engine.program && engine.install ? [`The engine program \`${engine.program}\` cannot be found or run on this machine. Install it with \`${engine.install}\`, then restart this service so it re-reads its PATH.`] : ["The engine program cannot be found or run on this machine."],
14560
+ ...programDisputed && engine.binaryPresent ? ["The engine program is on this machine, but the engine could not find it when it last started — so a new search will refuse. Restart the engine service, or install the CLI where the service can see it."] : []
14561
+ ];
14562
+ }
14563
+ function engineNotice({ programDisputed }) {
14564
+ if (programDisputed === true) return {
14565
+ state: "disputed",
14566
+ headline: "The engine program is on this machine, but the engine could not find it when it last started.",
14567
+ before: "So a new search will refuse. Restart the engine service, or install the CLI where the service can see it — installing it again where it already is will not change this.",
14568
+ namesSetupCommand: false,
14569
+ after: ""
14570
+ };
14571
+ return {
14572
+ state: "absent",
14573
+ headline: "No search engine is attached to this install.",
14574
+ before: "Everything else works — the example report, its audit trail and the assistant connection are live right now. To start new searches, install a reasoning CLI and sign in, then run the setup wizard again",
14575
+ namesSetupCommand: true,
14576
+ after: "Then restart the service: this reading was taken when it started, and it will not notice a new install until it starts again."
14577
+ };
14578
+ }
14579
+ function engineRow(engine, { programDisputed = false } = {}) {
14580
+ const faults = engineRowFaults(engine, { programDisputed });
14581
+ return {
14582
+ ok: faults.length === 0,
14583
+ name: engine.vendor ?? engine.id,
14584
+ mono: engine.id,
14585
+ state: engine.billing.apiBilled ? "API key" : "Subscription",
14586
+ faults
14587
+ };
14588
+ }
14589
+ //#endregion
14540
14590
  //#region src/contract/compose.ts
14541
14591
  function parseNames(raw) {
14542
14592
  return raw.split(/[\n,]/).map((s) => s.trim()).filter(Boolean);
@@ -14711,6 +14761,15 @@ function nameBudget(product, names) {
14711
14761
  over: names - product.maxNames
14712
14762
  };
14713
14763
  }
14764
+ function missingPieces(names, classes, goods) {
14765
+ const out = [];
14766
+ if (!names.length) {
14767
+ out.push("Add the brand name you want cleared, in Names above.");
14768
+ return out;
14769
+ }
14770
+ if (!classes.length && !goods.trim()) out.push("Say what the name is used for — pick classes, or describe the goods and services. Either one is enough.");
14771
+ return out;
14772
+ }
14714
14773
  function blockers(d, product, names = 0) {
14715
14774
  const out = [];
14716
14775
  if (!product) {
@@ -14737,11 +14796,32 @@ function blockers(d, product, names = 0) {
14737
14796
  if (!product.available) out.push(product.unavailableNote || "That search is not available just now.");
14738
14797
  return out;
14739
14798
  }
14799
+ function readiness(i) {
14800
+ const NOTHING_PICKED = "Pick one of the searches above to begin.";
14801
+ const blocking = [
14802
+ [i.nameStops.length > 0, i.nameStops[0] ?? ""],
14803
+ [i.gaps.length > 0, i.gaps[0] ?? ""],
14804
+ [i.stops.length > 0, i.stops[0] ?? ""],
14805
+ [i.budget != null, i.budget ? `This search reads ${i.budget.allowed} name${i.budget.allowed === 1 ? "" : "s"} at a time, and you have ${i.budget.allowed + i.budget.over}.` : ""],
14806
+ [i.exhausted, "No searches left on today’s allowance."],
14807
+ [!i.hasProduct, NOTHING_PICKED]
14808
+ ].filter(([when]) => when);
14809
+ if (!blocking.length) return {
14810
+ ready: true,
14811
+ blockedBy: null
14812
+ };
14813
+ return {
14814
+ ready: false,
14815
+ blockedBy: blocking.map(([, sentence]) => sentence).find((s) => s.trim().length > 0) ?? NOTHING_PICKED
14816
+ };
14817
+ }
14740
14818
  var ALL_TERRITORIES$1 = [...REGIONS, ...COUNTRIES];
14741
14819
  var isKnownTerritory = (entry) => {
14742
14820
  const e = String(entry).trim().toLowerCase();
14743
14821
  return e.length > 0 && ALL_TERRITORIES$1.some((t) => t.toLowerCase() === e);
14744
14822
  };
14823
+ var isTerritoryCode = (entry) => /^[A-Za-z]{2}$/.test(String(entry).trim());
14824
+ var isTerritoryEntry = (entry) => isKnownTerritory(entry) || isTerritoryCode(entry);
14745
14825
  function machineryFor(d, product) {
14746
14826
  const knockout = product?.pipeline === "knockout";
14747
14827
  return {
@@ -15233,6 +15313,16 @@ function useUnsaved(dirty) {
15233
15313
  ref.current = dirty;
15234
15314
  (0, import_react.useEffect)(() => registerGuard(() => ref.current), []);
15235
15315
  }
15316
+ function unsavedChanges({ current, empty, saved, submitted }) {
15317
+ if (submitted) return false;
15318
+ if (current === empty) return false;
15319
+ return current !== saved;
15320
+ }
15321
+ //#endregion
15322
+ //#region src/shell/ownerPickerHint.ts
15323
+ function ownerPickerHint(sidebarCollapsed) {
15324
+ return sidebarCollapsed ? "Open the menu on the left to pick one." : "Pick one at the top left.";
15325
+ }
15236
15326
  //#endregion
15237
15327
  //#region src/screens/NewClearance.tsx
15238
15328
  function readProblem(r) {
@@ -15288,12 +15378,23 @@ function NewClearance({ ctx }) {
15288
15378
  const [saveName, setSaveName] = (0, import_react.useState)("");
15289
15379
  const [saveText, setSaveText] = (0, import_react.useState)("");
15290
15380
  const [saveNote, setSaveNote] = (0, import_react.useState)(null);
15381
+ const [saveDone, setSaveDone] = (0, import_react.useState)(null);
15291
15382
  const [plan, setPlan] = (0, import_react.useState)(null);
15292
15383
  const [runFailure, setRunFailure] = (0, import_react.useState)(null);
15293
15384
  const [busy, setBusy] = (0, import_react.useState)(false);
15294
15385
  const [problem, setProblem] = (0, import_react.useState)(null);
15295
15386
  const [submitted, setSubmitted] = (0, import_react.useState)(null);
15296
- useUnsaved((0, import_react.useMemo)(() => submitted == null && JSON.stringify(draft) !== JSON.stringify(EMPTY), [draft, submitted]));
15387
+ const [savedDraft, setSavedDraft] = (0, import_react.useState)(null);
15388
+ useUnsaved((0, import_react.useMemo)(() => unsavedChanges({
15389
+ current: JSON.stringify(draft),
15390
+ empty: JSON.stringify(EMPTY),
15391
+ saved: savedDraft,
15392
+ submitted: submitted != null
15393
+ }), [
15394
+ draft,
15395
+ submitted,
15396
+ savedDraft
15397
+ ]));
15297
15398
  const { result: projectRes } = useLoad(() => draft.project ? api.project(account, draft.project) : Promise.resolve({
15298
15399
  kind: "ok",
15299
15400
  value: null
@@ -15314,7 +15415,7 @@ function NewClearance({ ctx }) {
15314
15415
  const scope = editing.recipe["scope"] ?? {};
15315
15416
  const classes = Array.isArray(scope["classes"]) ? scope["classes"].filter((c) => typeof c === "number") : [];
15316
15417
  const platforms = Array.isArray(scope["platforms"]) ? scope["platforms"].filter((p) => typeof p === "string") : [];
15317
- setDraft((d) => ({
15418
+ writeDraft((d) => ({
15318
15419
  ...d,
15319
15420
  pick: editingPick,
15320
15421
  savedSearch: "",
@@ -15358,8 +15459,12 @@ function NewClearance({ ctx }) {
15358
15459
  platforms: marketplacesApply ? own.platforms.length + parseList(draft.platforms).length : 0,
15359
15460
  density: own.density
15360
15461
  };
15462
+ const writeDraft = (next) => {
15463
+ setSaveDone(null);
15464
+ setDraft(next);
15465
+ };
15361
15466
  const edit = (patch) => {
15362
- setDraft((d) => ({
15467
+ writeDraft((d) => ({
15363
15468
  ...d,
15364
15469
  ...patch
15365
15470
  }));
@@ -15382,7 +15487,7 @@ function NewClearance({ ctx }) {
15382
15487
  return;
15383
15488
  }
15384
15489
  const { read, dropped, worldwide } = resolveRead(r.value);
15385
- setDraft((d) => {
15490
+ writeDraft((d) => {
15386
15491
  const before = {
15387
15492
  draft: d.pick,
15388
15493
  names: d.names,
@@ -15443,11 +15548,11 @@ function NewClearance({ ctx }) {
15443
15548
  };
15444
15549
  case "pickAccount": return {
15445
15550
  title: "Choose a brand owner",
15446
- lines: ["Pick who this clearance is for, at the top left."]
15551
+ lines: [`Pick who this clearance is for. ${ownerPickerHint(ctx.sidebarCollapsed)}`]
15447
15552
  };
15448
15553
  case "notFound": return {
15449
15554
  title: "That is not available to you",
15450
- lines: ["Check the brand owner selected at the top left."]
15555
+ lines: ["Check which brand owner is selected."]
15451
15556
  };
15452
15557
  case "noAccess": return {
15453
15558
  title: "This address has no access yet",
@@ -15504,6 +15609,7 @@ function NewClearance({ ctx }) {
15504
15609
  } else setRunFailure(explain(r));
15505
15610
  };
15506
15611
  const doSave = async () => {
15612
+ setSaveDone(null);
15507
15613
  const label = saveName.trim();
15508
15614
  const slug = editingSlug ?? label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 39);
15509
15615
  if (slug.length < 2) {
@@ -15529,6 +15635,12 @@ function NewClearance({ ctx }) {
15529
15635
  });
15530
15636
  setBusy(false);
15531
15637
  if (isOk(r)) {
15638
+ setSavedDraft(JSON.stringify({
15639
+ ...EMPTY,
15640
+ pick: draft.pick,
15641
+ classes: draft.classes,
15642
+ platforms: draft.platforms
15643
+ }));
15532
15644
  if (editingSlug) {
15533
15645
  ctx.go("/portal/brand/searches");
15534
15646
  return;
@@ -15538,6 +15650,7 @@ function NewClearance({ ctx }) {
15538
15650
  setSaveText("");
15539
15651
  const uncommitted = notCommitted(r);
15540
15652
  setSaveNote(uncommitted ? `Saved as “${label}”. ${uncommitted}` : `Saved as “${label}” — it is in your custom searches.`);
15653
+ setSaveDone(label);
15541
15654
  } else if (r.kind === "conflict") setSaveNote("Someone else changed this custom search while you were editing. Reload and re-apply.");
15542
15655
  else setSaveNote("That could not be saved. Try a different name, or check it on Custom searches.");
15543
15656
  };
@@ -15545,7 +15658,7 @@ function NewClearance({ ctx }) {
15545
15658
  go: ctx.go,
15546
15659
  onAnother: () => {
15547
15660
  setSubmitted(null);
15548
- setDraft(EMPTY);
15661
+ writeDraft(EMPTY);
15549
15662
  setEntry(null);
15550
15663
  }
15551
15664
  });
@@ -15553,12 +15666,17 @@ function NewClearance({ ctx }) {
15553
15666
  className: "screen",
15554
15667
  children: (0, import_jsx_runtime.jsxs)("div", {
15555
15668
  className: "notice",
15556
- children: [(0, import_jsx_runtime.jsx)("b", { children: "Choose a brand owner first" }), (0, import_jsx_runtime.jsx)("p", {
15669
+ children: [(0, import_jsx_runtime.jsx)("b", { children: "Choose a brand owner first" }), (0, import_jsx_runtime.jsxs)("p", {
15557
15670
  style: {
15558
15671
  margin: "6px 0 0",
15559
15672
  color: "var(--text-muted)"
15560
15673
  },
15561
- children: "A clearance is filed for one brand owner. Pick one at the top left, then start the search."
15674
+ children: [
15675
+ "A clearance is filed for one brand owner. ",
15676
+ ownerPickerHint(ctx.sidebarCollapsed),
15677
+ " ",
15678
+ "Then start the search."
15679
+ ]
15562
15680
  })]
15563
15681
  })
15564
15682
  });
@@ -15572,9 +15690,16 @@ function NewClearance({ ctx }) {
15572
15690
  const markLimit = searches.value.maxMarkName;
15573
15691
  const nameStops = overlong.map((n) => `“${n.slice(0, 30)}…” is ${n.length} characters. A mark name may be at most ${markLimit} — it becomes this run's name and part of every report link, so it cannot carry a description. Put the goods and the description in their own fields below.`);
15574
15692
  const budget = nameBudget(activeLevel, names.length);
15575
- const overBudget = budget != null;
15576
15693
  const exhausted = usage?.capped === true && usage.dailyRuns != null && usage.today >= usage.dailyRuns;
15577
- const ready = !(!names.length || !classes.length && !draft.goods.trim()) && !stops.length && !nameStops.length && !overBudget && !exhausted && activeLevel != null;
15694
+ const gaps = missingPieces(names, classes, draft.goods);
15695
+ const { ready, blockedBy } = readiness({
15696
+ gaps,
15697
+ stops,
15698
+ nameStops,
15699
+ budget,
15700
+ exhausted,
15701
+ hasProduct: activeLevel != null
15702
+ });
15578
15703
  const startedFrom = draft.savedSearch ? savedRow?.label ?? "a custom search" : activeLevel?.name ?? "no search picked yet";
15579
15704
  return (0, import_jsx_runtime.jsxs)("div", {
15580
15705
  className: "screen",
@@ -16431,6 +16556,24 @@ function NewClearance({ ctx }) {
16431
16556
  })
16432
16557
  })]
16433
16558
  }),
16559
+ gaps.length ? (0, import_jsx_runtime.jsxs)("div", {
16560
+ className: "notice",
16561
+ style: {
16562
+ borderColor: "var(--tone-medium)",
16563
+ margin: 0
16564
+ },
16565
+ children: [(0, import_jsx_runtime.jsx)("b", { children: gaps.length === 1 ? "One thing left to fill in" : "A couple of things left to fill in" }), (0, import_jsx_runtime.jsx)("ul", {
16566
+ style: {
16567
+ margin: "8px 0 0",
16568
+ paddingLeft: 18,
16569
+ color: "var(--text-muted)"
16570
+ },
16571
+ children: gaps.map((g, i) => (0, import_jsx_runtime.jsx)("li", {
16572
+ style: { marginBottom: 3 },
16573
+ children: g
16574
+ }, `g${i}`))
16575
+ })]
16576
+ }) : null,
16434
16577
  stops.length || nameStops.length ? (0, import_jsx_runtime.jsxs)("div", {
16435
16578
  className: "notice",
16436
16579
  style: {
@@ -16487,12 +16630,17 @@ function NewClearance({ ctx }) {
16487
16630
  ready,
16488
16631
  busy,
16489
16632
  demoMode: ctx.me.engineMode === "demo",
16633
+ setupRoute: ctx.me.setupRoute,
16634
+ programDisputed: ctx.me.engineProgramDisputed,
16635
+ onSettings: ctx.me.role === "staff" ? () => ctx.go("/portal/admin/config") : null,
16490
16636
  saveOpen,
16491
16637
  saveName,
16492
16638
  saveText,
16493
16639
  saveNote,
16640
+ saveDone,
16494
16641
  editing: editingSlug != null,
16495
16642
  canSave: !draft.savedSearch && !stops.length,
16643
+ blockedBy,
16496
16644
  onSaveOpen: () => {
16497
16645
  setSaveOpen(true);
16498
16646
  setSaveName(activeLevel ? `${activeLevel.name}` : "Custom search");
@@ -16508,7 +16656,8 @@ function NewClearance({ ctx }) {
16508
16656
  setSaveNote(null);
16509
16657
  },
16510
16658
  onSave: doSave,
16511
- onReview: doPlan
16659
+ onReview: doPlan,
16660
+ onSeeSaved: () => ctx.go("/portal/brand/searches")
16512
16661
  }),
16513
16662
  plan ? (0, import_jsx_runtime.jsx)(ReviewDialog, {
16514
16663
  plan,
@@ -16655,7 +16804,7 @@ function NameWall({ count, allowed, first, canScreen, screenName, onScreenAll })
16655
16804
  }) : null]
16656
16805
  });
16657
16806
  }
16658
- function Footer({ startedFrom, tier, detail, units, cost, duration, runs, ready, busy, demoMode, saveOpen, saveName, saveText, saveNote, editing, canSave, onSaveOpen, onSaveName, onSaveText, onSaveCancel, onSave, onReview }) {
16807
+ function Footer({ startedFrom, tier, detail, units, cost, duration, runs, ready, busy, demoMode, setupRoute, programDisputed, onSettings, saveOpen, saveName, saveText, saveNote, saveDone, editing, canSave, blockedBy, onSaveOpen, onSaveName, onSaveText, onSaveCancel, onSave, onReview, onSeeSaved }) {
16659
16808
  return (0, import_jsx_runtime.jsxs)("div", {
16660
16809
  className: "composer-footer",
16661
16810
  children: [
@@ -16849,29 +16998,67 @@ function Footer({ startedFrom, tier, detail, units, cost, duration, runs, ready,
16849
16998
  })
16850
16999
  ]
16851
17000
  }) : null,
16852
- demoMode ? (0, import_jsx_runtime.jsxs)("div", {
16853
- className: "footer-demo-note",
17001
+ saveDone ? (0, import_jsx_runtime.jsxs)("span", {
17002
+ className: "save-done",
16854
17003
  role: "status",
16855
- style: {
16856
- fontSize: 12.5,
16857
- lineHeight: 1.45
16858
- },
16859
17004
  children: [
16860
- (0, import_jsx_runtime.jsx)("strong", { children: "No search engine is attached to this install." }),
16861
- " ",
16862
- "Everything else works — the example report, its audit trail and the assistant connection are live right now. To start new searches, install a reasoning CLI and sign in, then run",
16863
- " ",
16864
- (0, import_jsx_runtime.jsx)("code", { children: "npm run setup" }),
16865
- " again."
17005
+ (0, import_jsx_runtime.jsx)(Icon, {
17006
+ name: "check",
17007
+ size: 13
17008
+ }),
17009
+ "Saved as ",
17010
+ saveDone,
17011
+ "”",
17012
+ (0, import_jsx_runtime.jsx)("button", {
17013
+ type: "button",
17014
+ className: "link-btn",
17015
+ onClick: onSeeSaved,
17016
+ children: "See it"
17017
+ })
16866
17018
  ]
16867
- }) : (0, import_jsx_runtime.jsxs)("button", {
16868
- type: "button",
16869
- className: "btn-primary",
16870
- disabled: !ready || busy,
16871
- onClick: onReview,
16872
- children: [busy ? "Checking…" : "Review clearance", (0, import_jsx_runtime.jsx)(Icon, {
16873
- name: "arrow-right",
16874
- size: 14
17019
+ }) : null,
17020
+ demoMode ? (() => {
17021
+ const n = engineNotice({ programDisputed });
17022
+ return (0, import_jsx_runtime.jsxs)("div", {
17023
+ className: "footer-demo-note",
17024
+ role: "status",
17025
+ style: {
17026
+ fontSize: 12.5,
17027
+ lineHeight: 1.45
17028
+ },
17029
+ children: [
17030
+ (0, import_jsx_runtime.jsx)("strong", { children: n.headline }),
17031
+ " ",
17032
+ n.before,
17033
+ n.namesSetupCommand ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [" ", (0, import_jsx_runtime.jsx)(SetupCommand, { route: setupRoute })] }) : null,
17034
+ n.after ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [" ", n.after] }) : null,
17035
+ onSettings ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [" ", (0, import_jsx_runtime.jsx)("button", {
17036
+ type: "button",
17037
+ className: "link-btn",
17038
+ onClick: onSettings,
17039
+ children: "See what this install has"
17040
+ })] }) : null
17041
+ ]
17042
+ });
17043
+ })() : (0, import_jsx_runtime.jsxs)("span", {
17044
+ style: {
17045
+ display: "inline-flex",
17046
+ alignItems: "center",
17047
+ gap: 10,
17048
+ flexWrap: "wrap"
17049
+ },
17050
+ children: [(0, import_jsx_runtime.jsxs)("button", {
17051
+ type: "button",
17052
+ className: "btn-primary",
17053
+ disabled: !ready || busy,
17054
+ onClick: onReview,
17055
+ children: [busy ? "Checking…" : "Start a search", (0, import_jsx_runtime.jsx)(Icon, {
17056
+ name: "arrow-right",
17057
+ size: 14
17058
+ })]
17059
+ }), (0, import_jsx_runtime.jsx)("span", {
17060
+ className: "footer-hint",
17061
+ children: blockedBy ?? "You will see the coverage and what it costs before anything runs."
16875
17062
  })]
16876
17063
  })
16877
17064
  ]
@@ -16879,6 +17066,18 @@ function Footer({ startedFrom, tier, detail, units, cost, duration, runs, ready,
16879
17066
  ]
16880
17067
  });
16881
17068
  }
17069
+ function SetupCommand({ route }) {
17070
+ if (route === "packaged") return (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)("code", { children: "npx clearotron install" }), "."] });
17071
+ if (route === "checkout") return (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)("code", { children: "npm run setup" }), "."] });
17072
+ return (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
17073
+ "— ",
17074
+ (0, import_jsx_runtime.jsx)("code", { children: "npx clearotron install" }),
17075
+ " if you installed the package, or ",
17076
+ (0, import_jsx_runtime.jsx)("code", { children: "npm run setup" }),
17077
+ " ",
17078
+ "from a copy of the source."
17079
+ ] });
17080
+ }
16882
17081
  function Lever({ label, hint, on, onToggle, coming }) {
16883
17082
  return (0, import_jsx_runtime.jsxs)("button", {
16884
17083
  type: "button",
@@ -17528,9 +17727,9 @@ function SavedSearches({ ctx }) {
17528
17727
  const [busy, setBusy] = (0, import_react.useState)(null);
17529
17728
  const [confirming, setConfirming] = (0, import_react.useState)(null);
17530
17729
  const [problem, setProblem] = (0, import_react.useState)(null);
17531
- if (needsOwner) return (0, import_jsx_runtime.jsx)(PickOwner, {});
17730
+ if (needsOwner) return (0, import_jsx_runtime.jsx)(PickOwner, { sidebarCollapsed: ctx.sidebarCollapsed });
17532
17731
  if (!result) return (0, import_jsx_runtime.jsx)("div", { className: "screen" });
17533
- if (result.kind === "pickAccount") return (0, import_jsx_runtime.jsx)(PickOwner, {});
17732
+ if (result.kind === "pickAccount") return (0, import_jsx_runtime.jsx)(PickOwner, { sidebarCollapsed: ctx.sidebarCollapsed });
17534
17733
  if (result.kind !== "ok") return (0, import_jsx_runtime.jsxs)("div", {
17535
17734
  className: "screen",
17536
17735
  children: [(0, import_jsx_runtime.jsx)(Heading, {}), (0, import_jsx_runtime.jsxs)("div", {
@@ -17802,17 +18001,17 @@ function Empty({ go }) {
17802
18001
  })]
17803
18002
  });
17804
18003
  }
17805
- function PickOwner() {
18004
+ function PickOwner({ sidebarCollapsed }) {
17806
18005
  return (0, import_jsx_runtime.jsx)("div", {
17807
18006
  className: "screen",
17808
18007
  children: (0, import_jsx_runtime.jsxs)("div", {
17809
18008
  className: "notice",
17810
- children: [(0, import_jsx_runtime.jsx)("b", { children: "Choose a brand owner first" }), (0, import_jsx_runtime.jsx)("p", {
18009
+ children: [(0, import_jsx_runtime.jsx)("b", { children: "Choose a brand owner first" }), (0, import_jsx_runtime.jsxs)("p", {
17811
18010
  style: {
17812
18011
  margin: "6px 0 0",
17813
18012
  color: "var(--text-muted)"
17814
18013
  },
17815
- children: "Custom searches belong to one brand owner. Pick one at the top left."
18014
+ children: ["Custom searches belong to one brand owner. ", ownerPickerHint(sidebarCollapsed)]
17816
18015
  })]
17817
18016
  })
17818
18017
  });
@@ -18490,7 +18689,7 @@ var PROFILE_FIELDS = [
18490
18689
  kind: "numbers",
18491
18690
  group: "defaults",
18492
18691
  picker: "classes",
18493
- hint: "Default Nice classes, 1-45, always changeable at search time. Commas, spaces or new lines all work."
18692
+ hint: "The numbered categories a trademark is registered in — 9 is software, 25 is clothing, 41 is training. These are the Nice classes, 1 to 45. Leave this empty and each search states its own. Commas, spaces or new lines all work."
18494
18693
  },
18495
18694
  {
18496
18695
  key: "defaultJurisdictions",
@@ -18500,10 +18699,10 @@ var PROFILE_FIELDS = [
18500
18699
  commaSeparated: true,
18501
18700
  picker: "territories",
18502
18701
  item: {
18503
- ok: isKnownTerritory,
18504
- expected: "a territory from the picker below"
18702
+ ok: isTerritoryEntry,
18703
+ expected: "a territory from the picker below, or a two-letter code"
18505
18704
  },
18506
- hint: "One per line or comma-separated. Searched by default unless a clearance specifies otherwise."
18705
+ hint: "One per line or comma-separated — pick from the list below, or type a two-letter code like US, EU or GB; both are understood. A region counts as one entry: European Union covers its member states, so there is no need to add them. Anything not in the list is kept and flagged rather than refused, because a search can name a territory this list does not carry."
18507
18706
  },
18508
18707
  {
18509
18708
  key: "platforms",
@@ -18906,12 +19105,12 @@ function Profile({ ctx }) {
18906
19105
  className: "screen",
18907
19106
  children: (0, import_jsx_runtime.jsxs)("div", {
18908
19107
  className: "notice",
18909
- children: [(0, import_jsx_runtime.jsx)("b", { children: "Choose a brand owner first" }), (0, import_jsx_runtime.jsx)("p", {
19108
+ children: [(0, import_jsx_runtime.jsx)("b", { children: "Choose a brand owner first" }), (0, import_jsx_runtime.jsxs)("p", {
18910
19109
  style: {
18911
19110
  margin: "6px 0 0",
18912
19111
  color: "var(--text-muted)"
18913
19112
  },
18914
- children: "A profile belongs to one brand owner. Pick one at the top left."
19113
+ children: ["A profile belongs to one brand owner. ", ownerPickerHint(ctx.sidebarCollapsed)]
18915
19114
  })]
18916
19115
  })
18917
19116
  });
@@ -19091,11 +19290,15 @@ function Profile({ ctx }) {
19091
19290
  children: "Save"
19092
19291
  }),
19093
19292
  (0, import_jsx_runtime.jsx)("span", {
19094
- style: {
19293
+ style: dirty && !checked ? {
19294
+ fontSize: 13,
19295
+ color: "var(--tone-medium)",
19296
+ fontWeight: 600
19297
+ } : {
19095
19298
  fontSize: 12.5,
19096
19299
  color: "var(--text-muted)"
19097
19300
  },
19098
- children: !dirty ? "No changes." : checked ? "Checked — safe to save." : "Check first. A profile the engine cannot read stops this account searching."
19301
+ children: !dirty ? "No changes." : checked ? "Checked — safe to save." : "Press Check before saving. A profile the engine cannot read stops this account searching."
19099
19302
  })
19100
19303
  ]
19101
19304
  })
@@ -19194,7 +19397,10 @@ function FrameworkBlock({ readOnly, framework, staff }) {
19194
19397
  " rated under the Generic default — but its definitions are unavailable, so the bands cannot be shown here. This needs an administrator to look at it."
19195
19398
  ] }) : (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)("b", { children: "Generic default" }), " — no custom framework is on file for this brand owner; their matters are rated under the generic framework."] })
19196
19399
  }),
19197
- bands.length ? (0, import_jsx_runtime.jsx)("div", {
19400
+ bands.length ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)("div", {
19401
+ className: "fw-ladder-label",
19402
+ children: "Its ratings, strongest concern first:"
19403
+ }), (0, import_jsx_runtime.jsx)("div", {
19198
19404
  className: "fw-ladder",
19199
19405
  children: bands.map((b, i) => {
19200
19406
  const band = rec(b);
@@ -19204,7 +19410,7 @@ function FrameworkBlock({ readOnly, framework, staff }) {
19204
19410
  tone: band?.["tone"]
19205
19411
  }, i) : null;
19206
19412
  })
19207
- }) : null,
19413
+ })] }) : null,
19208
19414
  meanings.length ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)("div", {
19209
19415
  className: "fw-bmh",
19210
19416
  children: "What the bands mean"
@@ -19408,7 +19614,7 @@ function explain(r) {
19408
19614
  };
19409
19615
  case "notFound": return {
19410
19616
  title: "That is not available to you",
19411
- lines: ["Check the brand owner selected at the top left."]
19617
+ lines: ["Check which brand owner is selected."]
19412
19618
  };
19413
19619
  case "noAccess": return {
19414
19620
  title: "This address has no access yet",
@@ -19455,12 +19661,12 @@ function Projects({ ctx }) {
19455
19661
  className: "screen",
19456
19662
  children: (0, import_jsx_runtime.jsxs)("div", {
19457
19663
  className: "notice",
19458
- children: [(0, import_jsx_runtime.jsx)("b", { children: "Choose a brand owner first" }), (0, import_jsx_runtime.jsx)("p", {
19664
+ children: [(0, import_jsx_runtime.jsx)("b", { children: "Choose a brand owner first" }), (0, import_jsx_runtime.jsxs)("p", {
19459
19665
  style: {
19460
19666
  margin: "6px 0 0",
19461
19667
  color: "var(--text-muted)"
19462
19668
  },
19463
- children: "Projects belong to one brand owner. Pick one at the top left."
19669
+ children: ["Projects belong to one brand owner. ", ownerPickerHint(ctx.sidebarCollapsed)]
19464
19670
  })]
19465
19671
  })
19466
19672
  });
@@ -20390,7 +20596,10 @@ function GlobalConfig({ ctx }) {
20390
20596
  }),
20391
20597
  (0, import_jsx_runtime.jsx)(Group, {
20392
20598
  title: "Engine",
20393
- children: v.engine ? (0, import_jsx_runtime.jsx)(Engine, { engine: v.engine }) : (0, import_jsx_runtime.jsx)(NotRecorded, {
20599
+ children: v.engine ? (0, import_jsx_runtime.jsx)(Engine, {
20600
+ engine: v.engine,
20601
+ programDisputed: (v.lastRun?.disagrees ?? []).some((d) => d.what === "engine program")
20602
+ }) : (0, import_jsx_runtime.jsx)(NotRecorded, {
20394
20603
  what: "which engine is running",
20395
20604
  source: v.source
20396
20605
  })
@@ -20525,20 +20734,8 @@ function Auth({ auth }) {
20525
20734
  faults: []
20526
20735
  }) : null] });
20527
20736
  }
20528
- function Engine({ engine }) {
20529
- const billed = engine.billing.apiBilled ? "API key" : "Subscription";
20530
- const faults = [
20531
- ...engine.known ? [] : [`This build does not ship an engine called ${engine.id}.`],
20532
- ...engine.billing.missing.length ? [`Set to bill an API key, and ${engine.billing.missing.join(" and ")} is not set — a run is refused rather than billed to the subscription.`] : [],
20533
- ...engine.binaryPresent ? [] : ["The engine program cannot be found or run on this machine."]
20534
- ];
20535
- return (0, import_jsx_runtime.jsx)(Row$1, {
20536
- ok: faults.length === 0,
20537
- name: engine.vendor ?? engine.id,
20538
- mono: engine.id,
20539
- state: billed,
20540
- faults
20541
- });
20737
+ function Engine({ engine, programDisputed = false }) {
20738
+ return (0, import_jsx_runtime.jsx)(Row$1, { ...engineRow(engine, { programDisputed }) });
20542
20739
  }
20543
20740
  function Provider({ p }) {
20544
20741
  const faults = p.remedy ? [p.remedy] : p.configured ? [] : p.provider === null ? [`No register is selected. Set ${p.missing.join(" and ")}.`] : !p.known ? [`This build does not ship a provider called ${p.provider}.`] : [`Set ${p.missing.join(" and ")}.`];
@@ -20748,7 +20945,10 @@ function PeopleAccess({ ctx }) {
20748
20945
  display: "grid",
20749
20946
  gap: 6
20750
20947
  },
20751
- children: (0, import_jsx_runtime.jsx)(StaffRuleRow, { domains: v.staffDomains })
20948
+ children: (0, import_jsx_runtime.jsx)(StaffRuleRow, {
20949
+ domains: v.staffDomains,
20950
+ rule: v.staffRule
20951
+ })
20752
20952
  })] }) : null,
20753
20953
  (0, import_jsx_runtime.jsx)("div", {
20754
20954
  style: {
@@ -20838,15 +21038,15 @@ function Roles({ brand }) {
20838
21038
  ]
20839
21039
  });
20840
21040
  }
20841
- function StaffRuleRow({ domains }) {
20842
- return (0, import_jsx_runtime.jsx)("div", {
21041
+ function StaffRuleRow({ domains, rule }) {
21042
+ return (0, import_jsx_runtime.jsxs)("div", {
20843
21043
  style: {
20844
21044
  padding: "10px 13px",
20845
21045
  borderRadius: 9,
20846
21046
  border: "1px solid var(--border-hairline)",
20847
21047
  background: "var(--surface-raised)"
20848
21048
  },
20849
- children: (0, import_jsx_runtime.jsxs)("div", {
21049
+ children: [(0, import_jsx_runtime.jsxs)("div", {
20850
21050
  style: {
20851
21051
  display: "flex",
20852
21052
  alignItems: "baseline",
@@ -20871,7 +21071,23 @@ function StaffRuleRow({ domains }) {
20871
21071
  },
20872
21072
  children: "a rule, not a person"
20873
21073
  })]
20874
- })
21074
+ }), rule ? (0, import_jsx_runtime.jsxs)("p", {
21075
+ style: {
21076
+ margin: "6px 0 0",
21077
+ color: "var(--text-muted)",
21078
+ fontSize: 12.5
21079
+ },
21080
+ children: [
21081
+ "Granted by ",
21082
+ (0, import_jsx_runtime.jsx)("b", {
21083
+ className: "mono",
21084
+ children: rule.name
21085
+ }),
21086
+ ", ",
21087
+ rule.where,
21088
+ ". Change or remove that setting and restart this instance to undo the rule. Nothing outside this machine created it."
21089
+ ]
21090
+ }) : null]
20875
21091
  });
20876
21092
  }
20877
21093
  function Observed({ result }) {