clearotron 0.3.2-beta.11 → 0.3.2-beta.13

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 (122) hide show
  1. package/.env.example +1 -1
  2. package/CONTRIBUTING.md +6 -5
  3. package/INSTALL.md +3 -4
  4. package/README.md +2 -1
  5. package/bin/clearotron.mjs +14 -0
  6. package/bin/connect.mjs +68 -3
  7. package/bin/example.mjs +12 -1
  8. package/bin/key.mjs +6 -1
  9. package/bin/onboard.mjs +23 -3
  10. package/bin/passphrase.mjs +4 -2
  11. package/bin/start.mjs +18 -6
  12. package/build-info.json +2 -2
  13. package/docs/CLIENT-MCP.md +6 -6
  14. package/docs/DELIVERY.md +3 -3
  15. package/docs/ONBOARDING.md +1 -1
  16. package/docs/PORTAL.md +3 -3
  17. package/docs/RELEASES.md +1 -1
  18. package/docs/SECURITY.md +1 -1
  19. package/docs/architecture/04-configuration-reference.md +4 -4
  20. package/docs/architecture/05-config-governance.md +10 -10
  21. package/docs/architecture/06-operations-runbook.md +3 -3
  22. package/docs/architecture/07-quality-and-audit.md +1 -1
  23. package/docs/architecture/08-development-guide.md +2 -2
  24. package/docs/architecture/09-security-and-data.md +1 -1
  25. package/docs/decisions/0002-no-dark-functionality.md +1 -1
  26. package/docs/decisions/0006-what-the-public-repository-carries.md +3 -3
  27. package/driver/CHANGELOG.md +29 -0
  28. package/driver/ask-ledger.mjs +1 -1
  29. package/driver/band-shape.mjs +1 -1
  30. package/driver/bundled-demos.mjs +2 -2
  31. package/driver/card-budget.mjs +2 -2
  32. package/driver/case-law-ledger.mjs +2 -2
  33. package/driver/connotation-search.mjs +5 -5
  34. package/driver/contract-e3-backlog.mjs +13 -13
  35. package/driver/contract-vocabulary.mjs +5 -5
  36. package/driver/coverage-form.mjs +2 -2
  37. package/driver/demo-container.mjs +26 -2
  38. package/driver/disposition-tool.mjs +2 -2
  39. package/driver/engine/CONTRACT.md +3 -3
  40. package/driver/engine/mcp/gather-config.mjs +1 -1
  41. package/driver/engine/mcp/recording-server.mjs +1 -1
  42. package/driver/engine/mcp/supplemental.mjs +22 -5
  43. package/driver/engine/openai-agent.mjs +2 -2
  44. package/driver/findings-model.mjs +21 -6
  45. package/driver/gateway.mjs +1 -1
  46. package/driver/knockout-next-step.mjs +72 -0
  47. package/driver/named-band.mjs +1 -1
  48. package/driver/package.json +1 -1
  49. package/driver/pipeline-knockout.mjs +27 -1
  50. package/driver/pipeline.mjs +4 -4
  51. package/driver/placement-union.mjs +1 -1
  52. package/driver/portal-mcp-client.mjs +1 -1
  53. package/driver/portal-report.mjs +25 -3
  54. package/driver/portal-service.mjs +25 -13
  55. package/driver/predelivery-lint.mjs +9 -4
  56. package/driver/progress.mjs +37 -1
  57. package/driver/publish/render-knockout.mjs +14 -9
  58. package/driver/publish/render.mjs +6 -6
  59. package/driver/record-discard.mjs +1 -1
  60. package/driver/register-availability.mjs +1 -1
  61. package/driver/register-count.mjs +1 -1
  62. package/driver/register-digest-record.mjs +1 -1
  63. package/driver/register-plan.mjs +81 -11
  64. package/driver/report-card-record.mjs +2 -2
  65. package/driver/result-noun-fields.mjs +3 -1
  66. package/driver/roster-verdict.mjs +2 -2
  67. package/driver/search-policy.mjs +1 -1
  68. package/driver/skeptic-record.mjs +1 -1
  69. package/driver/stages-knockout.mjs +1 -1
  70. package/driver/stages.mjs +1 -1
  71. package/driver/suite-census.json +72 -24
  72. package/driver/systemd/README.md +1 -1
  73. package/driver/unit-inventory.mjs +2 -2
  74. package/driver/verify-knockout.mjs +0 -27
  75. package/driver/verify.mjs +1 -1
  76. package/mcp-server/CHANGELOG.md +8 -0
  77. package/mcp-server/CONNECT.md +8 -8
  78. package/mcp-server/lib/runs.mjs +1 -1
  79. package/mcp-server/package.json +1 -1
  80. package/mcp-server/serve.mjs +27 -0
  81. package/package.json +1 -1
  82. package/portal-ui/dist/assets/{index-CtvwLCti.css → index-7Lq-dXDV.css} +12 -9
  83. package/portal-ui/dist/assets/{index-DXSRxPV_.js → index-w8GFZftk.js} +110 -69
  84. package/portal-ui/dist/index.html +2 -2
  85. package/portal-ui/package.json +1 -1
  86. package/providers/free-tier/src/capabilities.js +2 -2
  87. package/providers/jx/src/core.js +3 -3
  88. package/providers/jx/src/turn-envelope.mjs +1 -1
  89. package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
  90. package/providers/oauth-mcp-bridge/package.json +1 -1
  91. package/providers/perplexity/README.md +1 -1
  92. package/providers/signa/src/capabilities.js +2 -2
  93. package/providers/signa/src/core.js +2 -2
  94. package/providers/uspto-local/src/core.js +1 -1
  95. package/providers/uspto-local/src/sync.js +1 -1
  96. package/scripts/README.md +2 -6
  97. package/scripts/ask-ai-render-check.mjs +26 -1
  98. package/scripts/citation-anchor-report.mjs +1 -1
  99. package/scripts/citation-line-check.mjs +3 -3
  100. package/scripts/dead-names.mjs +17 -16
  101. package/scripts/e2e.mjs +1 -1
  102. package/scripts/env-audit.mjs +7 -1
  103. package/scripts/env-classify.mjs +1 -1
  104. package/scripts/pack-publishable.mjs +1 -1
  105. package/scripts/release-artifact-seal.mjs +2 -2
  106. package/scripts/repo-writes.mjs +42 -0
  107. package/scripts/report-sections-render-check.mjs +245 -0
  108. package/scripts/report-theme-render-check.mjs +31 -3
  109. package/scripts/settings-render-check.mjs +15 -8
  110. package/scripts/strip-tracker-citations.mjs +4 -4
  111. package/scripts/test-run.mjs +108 -6
  112. package/shared/browser-temp-root.mjs +10 -3
  113. package/shared/client-door.mjs +38 -6
  114. package/shared/connect-clients.mjs +2 -0
  115. package/shared/identifier-scan.mjs +2 -2
  116. package/shared/invocation.mjs +1 -1
  117. package/shared/parent-watch.mjs +33 -0
  118. package/shared/reference-guard-classes.mjs +5 -3
  119. package/shared/running-start.mjs +14 -3
  120. package/shared/scope.mjs +22 -3
  121. package/shared/stdio-connect.mjs +39 -18
  122. package/shared/writing-standard-classes.mjs +2 -3
@@ -18201,11 +18201,15 @@ function readFrameSections(data, sameSource) {
18201
18201
  const out = [];
18202
18202
  for (const raw of msg.sections) {
18203
18203
  if (typeof raw !== "object" || raw === null) continue;
18204
- const { id, label } = raw;
18204
+ const { id, label, top } = raw;
18205
18205
  if (typeof id !== "string" || typeof label !== "string") continue;
18206
18206
  const trimmed = label.trim().slice(0, 40);
18207
18207
  if (!id.trim() || !trimmed) continue;
18208
- out.push({
18208
+ out.push(typeof top === "number" && Number.isFinite(top) && top >= 0 ? {
18209
+ id,
18210
+ label: trimmed,
18211
+ top: Math.round(top)
18212
+ } : {
18209
18213
  id,
18210
18214
  label: trimmed
18211
18215
  });
@@ -18213,6 +18217,16 @@ function readFrameSections(data, sameSource) {
18213
18217
  }
18214
18218
  return out;
18215
18219
  }
18220
+ function sectionsReached(sections, line, where) {
18221
+ if (where.atTop) return 0;
18222
+ if (where.atFoot) return sections.length;
18223
+ let n = 0;
18224
+ for (const s of sections) {
18225
+ if (typeof s.top !== "number" || s.top > line) break;
18226
+ n += 1;
18227
+ }
18228
+ return n;
18229
+ }
18216
18230
  function readFrameTheme(data, sameSource) {
18217
18231
  if (!sameSource) return null;
18218
18232
  if (typeof data !== "object" || data === null) return null;
@@ -18243,6 +18257,8 @@ function usePortalTheme() {
18243
18257
  }, []);
18244
18258
  return theme;
18245
18259
  }
18260
+ var TOPBAR_PX = 56;
18261
+ var LANDING_PX = 10;
18246
18262
  function useReportFrame() {
18247
18263
  const ref = (0, import_react.useRef)(null);
18248
18264
  const [height, setHeight] = (0, import_react.useState)(FIRST_PAINT);
@@ -18276,7 +18292,7 @@ function useReportFrame() {
18276
18292
  if (jump !== null) {
18277
18293
  if (frame) {
18278
18294
  const head = document.querySelector(".report-head");
18279
- const chrome = 56 + (head instanceof HTMLElement ? head.offsetHeight : 48) + 10;
18295
+ const chrome = TOPBAR_PX + (head instanceof HTMLElement ? head.offsetHeight : 48) + LANDING_PX;
18280
18296
  const y = frame.getBoundingClientRect().top + window.scrollY + jump - chrome;
18281
18297
  window.scrollTo({
18282
18298
  top: Math.max(0, y),
@@ -18478,10 +18494,45 @@ function Result({ ctx, runId, markSlug }) {
18478
18494
  const run = (0, import_react.useMemo)(() => runs.find((r) => r.runId === runId) ?? null, [runs, runId]);
18479
18495
  const reads = (0, import_react.useMemo)(() => run ? readsFor(runs, run) : [], [runs, run]);
18480
18496
  const frame = useReportFrame();
18481
- const [atSection, setAtSection] = (0, import_react.useState)(null);
18497
+ const [reached, setReached] = (0, import_react.useState)(0);
18498
+ const sectionList = frame.sections;
18499
+ const frameRef = frame.ref;
18482
18500
  (0, import_react.useEffect)(() => {
18483
- setAtSection(null);
18484
- }, [runId, markSlug]);
18501
+ if (!sectionList || sectionList.length < 2) {
18502
+ setReached(0);
18503
+ return;
18504
+ }
18505
+ let queued = false;
18506
+ const measure = () => {
18507
+ queued = false;
18508
+ const el = frameRef.current;
18509
+ if (!el) return;
18510
+ const head = document.querySelector(".report-head");
18511
+ const lineOnScreen = (head instanceof HTMLElement ? head.getBoundingClientRect().bottom : TOPBAR_PX) + LANDING_PX + 2;
18512
+ const where = {
18513
+ atTop: document.documentElement.scrollHeight > window.innerHeight + 2 && window.scrollY <= 0,
18514
+ atFoot: window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 2
18515
+ };
18516
+ setReached(sectionsReached(sectionList, lineOnScreen - el.getBoundingClientRect().top, where));
18517
+ };
18518
+ const later = () => {
18519
+ if (!queued) {
18520
+ queued = true;
18521
+ requestAnimationFrame(measure);
18522
+ }
18523
+ };
18524
+ measure();
18525
+ window.addEventListener("scroll", later, { passive: true });
18526
+ window.addEventListener("resize", later);
18527
+ return () => {
18528
+ window.removeEventListener("scroll", later);
18529
+ window.removeEventListener("resize", later);
18530
+ };
18531
+ }, [
18532
+ sectionList,
18533
+ frame.height,
18534
+ frameRef
18535
+ ]);
18485
18536
  const grouped = run !== null && showsAssessment(run, markSlug);
18486
18537
  const { result: summaryResult } = useLoad(() => grouped ? api.runSummary(runId) : Promise.resolve({ kind: "notFound" }), [runId, grouped]);
18487
18538
  const assessment = summaryResult?.kind === "ok" ? summaryResult.value : [];
@@ -18612,70 +18663,60 @@ function Result({ ctx, runId, markSlug }) {
18612
18663
  ]
18613
18664
  }), hasThread(reads) || (frame.sections?.length ?? 0) > 1 ? (0, import_jsx_runtime.jsxs)("div", {
18614
18665
  className: "report-nav",
18615
- children: [
18616
- hasThread(reads) ? (0, import_jsx_runtime.jsxs)("div", {
18617
- style: {
18618
- display: "flex",
18619
- gap: 8,
18620
- flexWrap: "wrap",
18621
- alignItems: "center"
18622
- },
18623
- children: [(0, import_jsx_runtime.jsx)("span", {
18624
- className: "eyebrow",
18625
- style: { alignSelf: "center" },
18626
- children: "Reads"
18627
- }), reads.map((r) => {
18628
- const active = r.runId === run.runId;
18629
- return (0, import_jsx_runtime.jsxs)("button", {
18630
- type: "button",
18631
- className: "pill",
18632
- "aria-current": active ? "true" : void 0,
18633
- onClick: () => ctx.go(resultPath(r.runId)),
18666
+ children: [hasThread(reads) ? (0, import_jsx_runtime.jsxs)("div", {
18667
+ style: {
18668
+ display: "flex",
18669
+ gap: 8,
18670
+ flexWrap: "wrap",
18671
+ alignItems: "center"
18672
+ },
18673
+ children: [(0, import_jsx_runtime.jsx)("span", {
18674
+ className: "eyebrow",
18675
+ style: { alignSelf: "center" },
18676
+ children: "Reads"
18677
+ }), reads.map((r) => {
18678
+ const active = r.runId === run.runId;
18679
+ return (0, import_jsx_runtime.jsxs)("button", {
18680
+ type: "button",
18681
+ className: "pill",
18682
+ "aria-current": active ? "true" : void 0,
18683
+ onClick: () => ctx.go(resultPath(r.runId)),
18684
+ style: {
18685
+ cursor: "pointer",
18686
+ display: "inline-flex",
18687
+ alignItems: "center",
18688
+ gap: 7,
18689
+ background: active ? "var(--accent-wash)" : "var(--surface-sunken)",
18690
+ borderColor: active ? "var(--accent)" : "var(--border-hairline)",
18691
+ color: active ? "var(--text-accent)" : "var(--text-muted)"
18692
+ },
18693
+ children: [r.tone ? (0, import_jsx_runtime.jsx)("span", {
18694
+ className: "dot",
18634
18695
  style: {
18635
- cursor: "pointer",
18636
- display: "inline-flex",
18637
- alignItems: "center",
18638
- gap: 7,
18639
- background: active ? "var(--accent-wash)" : "var(--surface-sunken)",
18640
- borderColor: active ? "var(--accent)" : "var(--border-hairline)",
18641
- color: active ? "var(--text-accent)" : "var(--text-muted)"
18642
- },
18643
- children: [r.tone ? (0, import_jsx_runtime.jsx)("span", {
18644
- className: "dot",
18645
- style: {
18646
- background: `var(--tone-${r.tone})`,
18647
- width: 7,
18648
- height: 7
18649
- }
18650
- }) : null, readLabel(r)]
18651
- }, r.runId);
18652
- })]
18653
- }) : null,
18654
- hasThread(reads) && (frame.sections?.length ?? 0) > 1 ? (0, import_jsx_runtime.jsx)("span", {
18655
- className: "report-nav-sep",
18656
- "aria-hidden": "true"
18657
- }) : null,
18658
- (frame.sections?.length ?? 0) > 1 ? (0, import_jsx_runtime.jsx)("nav", {
18659
- className: "report-sections",
18660
- "aria-label": "Sections of this report",
18661
- children: (frame.sections ?? []).map((sec, i) => {
18662
- const now = atSection === null ? i === 0 : atSection === sec.id;
18663
- return (0, import_jsx_runtime.jsxs)("button", {
18664
- type: "button",
18665
- className: "report-section",
18666
- "aria-current": now ? "true" : void 0,
18667
- onClick: () => {
18668
- setAtSection(sec.id);
18669
- frame.send("section", sec.id);
18670
- },
18671
- children: [(0, import_jsx_runtime.jsx)("span", {
18672
- className: "report-section-dot",
18673
- "aria-hidden": "true"
18674
- }), sec.label]
18675
- }, sec.id);
18676
- })
18677
- }) : null
18678
- ]
18696
+ background: `var(--tone-${r.tone})`,
18697
+ width: 7,
18698
+ height: 7
18699
+ }
18700
+ }) : null, readLabel(r)]
18701
+ }, r.runId);
18702
+ })]
18703
+ }) : null, (frame.sections?.length ?? 0) > 1 ? (0, import_jsx_runtime.jsx)("nav", {
18704
+ className: "report-sections",
18705
+ "aria-label": "Sections of this report",
18706
+ children: (frame.sections ?? []).map((sec, i) => {
18707
+ return (0, import_jsx_runtime.jsxs)("button", {
18708
+ type: "button",
18709
+ className: "report-section",
18710
+ "data-reached": i < reached ? "true" : void 0,
18711
+ "aria-current": i === reached - 1 ? "true" : void 0,
18712
+ onClick: () => frame.send("section", sec.id),
18713
+ children: [(0, import_jsx_runtime.jsx)("span", {
18714
+ className: "report-section-dot",
18715
+ "aria-hidden": "true"
18716
+ }), sec.label]
18717
+ }, sec.id);
18718
+ })
18719
+ }) : null]
18679
18720
  }) : null]
18680
18721
  }),
18681
18722
  frame.failed ? (0, import_jsx_runtime.jsxs)("div", {
@@ -49,8 +49,8 @@
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-DXSRxPV_.js"></script>
53
- <link rel="stylesheet" crossorigin href="/portal/assets/index-CtvwLCti.css">
52
+ <script type="module" crossorigin src="/portal/assets/index-w8GFZftk.js"></script>
53
+ <link rel="stylesheet" crossorigin href="/portal/assets/index-7Lq-dXDV.css">
54
54
  </head>
55
55
  <body>
56
56
  <div id="root"></div>
@@ -2,7 +2,7 @@
2
2
  "name": "portal-ui",
3
3
  "private": true,
4
4
  "type": "module",
5
- "version": "0.3.2-beta.11",
5
+ "version": "0.3.2-beta.13",
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": {
@@ -48,9 +48,9 @@
48
48
  // The question that DOES depend on the box — which offices this deployment can reach right now — is
49
49
  // answered in driver/register-availability.mjs, and the answer rides the plan as a disclosed
50
50
  // `deferred_coverage` row. It is deliberately not answered here, and `covered` is deliberately not
51
- // narrowed there either: 's admission gate reads this field to decide which territories a client may
51
+ // narrowed there either: the admission gate reads this field to decide which territories a client may
52
52
  // ORDER, so narrowing it to the configured half would refuse a US-only matter at the door instead of
53
- // disclosing its US gap ('s ruling is that such a matter must START and disclose).
53
+ // disclosing its US gap (the rule is that such a matter must START and disclose).
54
54
  //
55
55
  // ── THE "NO SHAPE FOR HALF OF THIS RAN" RULE IS UNCHANGED — IT MOVED THE SPLIT, NOT REPEALED IT ──────
56
56
  //
@@ -13,10 +13,10 @@
13
13
  // SEARCH MACHINERY (query terms), never registry facts — the never-invent rule binds registry DATA,
14
14
  // not the queries we choose to run.
15
15
  //
16
- // BILLING RIDES THE RUN, NOT THIS FILE (/ at e49868e3, and the transport deleted at).
16
+ // BILLING RIDES THE RUN, NOT THIS FILE.
17
17
  // These lanes used to POST to the Anthropic Messages API on ANTHROPIC_API_KEY at a hardcoded haiku
18
- // tier while the rest of the run used whatever program the customer chose — the mix the owner's D6
19
- // ruling forbids. They now go through `engine.runTurn()` like every other model call, so whatever
18
+ // tier while the rest of the run used whatever program the customer chose — the mix the product's one-provider-per-run
19
+ // rule forbids. They now go through `engine.runTurn()` like every other model call, so whatever
20
20
  // program and billing mode the run is on carries them, and nothing here selects a vendor.
21
21
  //
22
22
  // `callMessagesAPI` — with its own MESSAGES_API_URL, x-api-key header and retry ladder — is DELETED
@@ -3,7 +3,7 @@
3
3
  // turn-envelope.mjs — render a forced-tool request as a CLI-turn prompt, and read the turn's text back
4
4
  // into the envelope the three lane parsers already understand. PURE. No driver import, no network.
5
5
  //
6
- // ── why this exists ( /) ─────────────────────────────────────────────────────────────────
6
+ // ── why this exists ─────────────────────────────────────────────────────────────────
7
7
  //
8
8
  // The jx lanes called the Anthropic Messages API directly with `tool_choice: {type:"tool"}`, on a key,
9
9
  // at a hardcoded tier — regardless of which AI program the customer configured. The owner's standing
@@ -1,5 +1,13 @@
1
1
  # trademark-oauth-mcp-bridge
2
2
 
3
+ ## 0.3.2-beta.13
4
+
5
+ No changes in this release.
6
+
7
+ ## 0.3.2-beta.12
8
+
9
+ No changes in this release.
10
+
3
11
  ## 0.3.2-beta.11
4
12
 
5
13
  No changes in this release.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trademark-oauth-mcp-bridge",
3
- "version": "0.3.2-beta.11",
3
+ "version": "0.3.2-beta.13",
4
4
  "license": "AGPL-3.0-only",
5
5
  "private": true,
6
6
  "description": "OAuth 2.1 MCP stdio bridge used by the engine's case-law gather stage (courtlistener / legaldatahunter).",
@@ -11,7 +11,7 @@ optional: without `PERPLEXITY_API_KEY` the run door refuses by name
11
11
  (`preflightResearchCredential`, `../../driver/driver.config.mjs`) rather than searching less. A
12
12
  KNOCKOUT is the exception and it is deliberate — its register half is a whole product without the
13
13
  sweep, so `../../driver/pipeline-knockout.mjs` SKIPS this adapter and discloses the half it did not
14
- run ( acceptance 6). Nothing is billed for a sweep that never runs.
14
+ run. Nothing is billed for a sweep that never runs.
15
15
  (`CLEAROTRON_KNOCKOUT_SWEEP_FIXTURES` is the $0 dev route to a knockout WITH a sweep.)
16
16
 
17
17
  The header's `index.js` and `build.js` belong to the plugin packaging this core was extracted from;
@@ -20,7 +20,7 @@
20
20
  // * predicates.wildcardInfix — `contains` would appear to serve it and does not. The kernel hands
21
21
  // the infix case its RAW pattern with the asterisks still in it, so the sweep would search the
22
22
  // punctuation. Declaring it would be declaring a capability the executor cannot serve, which is
23
- // the one thing 's criteria forbid. Stays null; the slice defers, disclosed.
23
+ // the one thing a declaration must never do. Stays null; the slice defers, disclosed.
24
24
  // * resultCeiling — there is no single number to put here, and the reason is worth the paragraph.
25
25
  // An unscoped term pages to exhaustion whatever its size, on every predicate — no ceiling. But add
26
26
  // `filters.owner_name` and the SAME term stops dead at the owner-scoped window (400), which the
@@ -194,7 +194,7 @@ export const CAPABILITIES = Object.freeze({
194
194
 
195
195
  // ── WHICH BINDING LAYERS DOES A SEARCH SCOPED TO THIS OFFICE ACTUALLY RETURN? ──────────
196
196
  //
197
- // 's first task, answered for this provider by driving the three scopings against each other rather
197
+ // The first question for every provider, answered here by driving the three scopings against each other rather
198
198
  // than by reading the documentation. One term, one limit, France:
199
199
  //
200
200
  // filters.offices the national register ALONE
@@ -680,8 +680,8 @@ export function toSignaParams(p = {}) {
680
680
  // and this branch is the only thing standing between that and `strategies: ["exact"]`. With
681
681
  // `predicates.default` declared `"contains"` and no mapping here, every unanchored slice would
682
682
  // have gone to the wire as an EXACT search: narrower than the plan asked for, returning fewer
683
- // rows, and answering as though it were the query requested. That is 's defect exactly, and
684
- // declaring a predicate without wiring it is the one thing this issue's criteria forbid.
683
+ // rows, and answering as though it were the query requested. That is the silent narrowing this branch
684
+ // prevents, and declaring a predicate without wiring it is the one thing a declaration must never do.
685
685
  //
686
686
  // A `*` in the term means this is the infix-wildcard case, which shares the empty {} and which
687
687
  // `contains` cannot serve — the kernel hands the RAW pattern, asterisks included, so a contains
@@ -67,7 +67,7 @@ function resolveDbPath(auth) {
67
67
  // marker added by one consumer is a marker the other consumers do not get. Marking at the throw
68
68
  // means every path out of this provider carries it, including ones written later.
69
69
  //
70
- // 's composite backstop caught a THROW and converted it. That backstop never fired against this
70
+ // The composite backstop caught a THROW and converted it. That backstop never fired against this
71
71
  // provider, because four of its five entry points catch their own throw and return a plain error
72
72
  // first — and the test that claimed it worked drove a stub that throws, which this does not do.
73
73
  throw new Error(
@@ -312,7 +312,7 @@ export async function syncIndex({ dbPath, files, ingest = ingestFile, onFile = n
312
312
  await onPhase?.("ingest");
313
313
  // BLOCKS THE EVENT LOOP FOR THE WHOLE REBUILD — node:sqlite is synchronous, and this is one
314
314
  // transaction over every row in the index. Nothing on this thread runs again until it commits,
315
- // which is why 's disk sampler is a worker thread rather than a timer.
315
+ // which is why the disk sampler is a worker thread rather than a timer.
316
316
  rebuildFts(db);
317
317
  await onPhase?.("fts");
318
318
  const rows = db.prepare("SELECT count(*) AS n FROM mark").get().n;
package/scripts/README.md CHANGED
@@ -52,14 +52,10 @@ than its contents, and the whole reason the browser checks exist is that a fix f
52
52
  scrollbars" shipped a report with two scrollbars past 1,500 passing tests. Those checks run in CI's
53
53
  `build-and-verify` job and nowhere else — so **CI, not your machine, is what covers them.**
54
54
 
55
- `render-check.mjs` is a further step out, running in **neither** — and wiring it up is what found out
56
- why that mattered. CI left it out because it measures a PUBLISHED RUN and had no pool to point at;
57
- `--fixture-pool` removed that reason, so wired it into`build-and-verify` as a blocking step.
58
-
59
- **Its first run anywhere reported 6 of 9 assertions failing with `no-probe` and `null`** — not a layout
55
+ **`render-check.mjs`'s first run anywhere reported 6 of 9 assertions failing with `no-probe` and `null`** — not a layout
60
56
  defect, an inability to *measure*. The three assertions that look inside the report frame (the height
61
57
  bridge, the scrollbar loop, the sideways overflow) depend on a probe script in the sandboxed iframe
62
- posting back to the shell. The pool built fine; the harness cannot see inside its own frame. **.**
58
+ posting back to the shell. The pool built fine; the harness cannot see inside its own frame.
63
59
 
64
60
  **The first explanation written down here was wrong, and how it was wrong is the useful part.** This
65
61
  paragraph used to say the message never arrives under `file://` with production's sandbox. Measured in
@@ -133,6 +133,10 @@ let deck = 'connected'
133
133
  // travels the real injected script to the real listener. Two cards at fixed places: a numbered finding,
134
134
  // and an "Also considered" card, which carries the button and no number.
135
135
  let reportDoc = 'stub'
136
+ // A SLOW RUNNER, ON DEMAND. The finding's document is held back this long before it is served, which is
137
+ // how a CI runner that loaded it late is reproduced: the press below has to wait for the document, not
138
+ // for a fixed time. Zero, the default, serves it at once.
139
+ const FRAME_DELAY_MS = Number(process.env.ASK_AI_CHECK_FRAME_DELAY_MS ?? 0) || 0
136
140
  const FINDING_DOC = () => prepareReportForEmbed(`<!doctype html><html><head><meta charset="utf-8"><title>report</title></head>
137
141
  <body style="margin:0;font-family:sans-serif;background:#fff;height:400px">
138
142
  <div class="card" id="c3" style="position:absolute;top:40px;left:20px"><div class="cardhead"><span class="fnum">3</span>
@@ -159,7 +163,8 @@ const server = createServer((req, res) => {
159
163
  // the height a published report's bridge announces, so the header draws the Export menu a reader meets.
160
164
  if (p.startsWith('/portal/report/') && reportDoc === 'finding') {
161
165
  res.writeHead(200, { 'content-type': 'text/html' })
162
- return res.end(FINDING_DOC())
166
+ const doc = FINDING_DOC()
167
+ return FRAME_DELAY_MS ? setTimeout(() => res.end(doc), FRAME_DELAY_MS) : res.end(doc)
163
168
  }
164
169
  if (p.startsWith('/portal/report/')) {
165
170
  res.writeHead(200, { 'content-type': 'text/html' })
@@ -433,9 +438,29 @@ const arrived = await value(PROBE)
433
438
  // null origin and the point is the whole path — the injected script hears the press, posts it, and the
434
439
  // report screen opens its control. The cards sit at fixed places in the document, so the frame's own
435
440
  // position on the page is all that has to be measured.
441
+ // READY, NOT A FIXED WAIT. The press lands on a finding's button, and it does something only once the
442
+ // document in the frame has loaded and its bridge is listening. A fixed pause after navigating was not
443
+ // always that on a CI runner: the press arrived first and opened nothing (measured on beta-12, 2026-09-19,
444
+ // with no change to the portal or the bridge since a green run). The bridge posts the document's height
445
+ // from the same script that listens for the press, so a frame that has left its first-paint height has a
446
+ // listener. The Ask AI control must be on the header too, since the press opens it. Both are waited for,
447
+ // and the frame's place must hold still across two reads, so the press is aimed where the button is.
448
+ const FIRST_PAINT = Number(/export const FIRST_PAINT = (\d+)/.exec(readFileSync(join(DIST, '..', 'src', 'contract', 'reportFrame.ts'), 'utf8'))?.[1] ?? NaN)
449
+ const frameReady = async () => {
450
+ let last = null
451
+ for (let i = 0; i < 200; i++) {
452
+ const now = await value(`(() => { const f = document.querySelector('iframe'); if (!f || !document.querySelector('[data-ask-ai] > button')) return null;
453
+ const r = f.getBoundingClientRect(); return Math.round(r.top) + ':' + Math.round(r.height) })()`)
454
+ if (now && now === last && !now.endsWith(`:${FIRST_PAINT}`)) return true
455
+ last = now
456
+ await new Promise((r) => setTimeout(r, 100))
457
+ }
458
+ return false
459
+ }
436
460
  const pressInFrame = async (top) => {
437
461
  await value('(() => { window.scrollTo(0, 0); return true })()')
438
462
  await new Promise((r) => setTimeout(r, 200))
463
+ if (!(await frameReady())) return false
439
464
  const at = await value(`(() => { const f = document.querySelector('iframe'); if (!f) return null; const r = f.getBoundingClientRect(); return { x: r.left, y: r.top } })()`)
440
465
  if (!at) return false
441
466
  const x = Math.round(at.x + 140), y = Math.round(at.y + top + 16)
@@ -113,7 +113,7 @@ export function anchorRows(citations, linesOf) {
113
113
  if (!others.length) { rows.push({ ...c, verdict: "silent", anchor }); continue; }
114
114
  let elsewhere = null;
115
115
  for (const n of others) {
116
- const decl = new RegExp(`^\\s*(?:export\\s+)?(?:default\\s+)?(?:async\\s+)?(?:function|const|let|var|class)\\s+${n.replace(/\$/g, "\\$")}\\b`);
116
+ const decl = new RegExp(`^\\s*(?:export\\s+)?(?:default\\s+)?(?:async\\s+)?(?:function|const|let|var|class)\\s+${n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
117
117
  for (let i = 0; i < target.length; i++) {
118
118
  if (!decl.test(target[i])) continue;
119
119
  // THE CITATION POINTING INSIDE WHAT IT NAMES IS CORRECT, not drifted — see the header.
@@ -528,7 +528,7 @@ function main() {
528
528
  // WATCHLIST_OWNERS_MAX declared in variant-manifest-model.mjs anything else — `declared` marks it
529
529
  //
530
530
  // A BARE WORD BEFORE `in <file>` IS NOT A SYMBOL CITATION, deliberately, and this is the difference
531
- // between this arm and 's. reads the token beside a number SPECULATIVELY and drops it when it
531
+ // between this arm and the line arm, which reads the token beside a number SPECULATIVELY and drops it when it
532
532
  // is not declared, because "ALREADY computes (coverage-ledger.mjs:180)" would otherwise manufacture a
533
533
  // defect out of emphatic prose. This arm cannot do that: an undeclared symbol has to FAIL or the check
534
534
  // has no teeth at all. So the form is opted into. `the field in scope-ledger.mjs` and `checked in
@@ -923,8 +923,8 @@ export function symbolMisses(citations, readLines) {
923
923
  // WHAT IT CANNOT SEE, and why this stays a slice. A citation that lands on the WRONG NON-BLANK LINE is
924
924
  // invisible to it — that looks identical to a correct one. Three of the eleven citations repointed in
925
925
  // `stages.mjs` under this issue were exactly that: `stages.mjs:1474` pointed at a transliteration `why:`
926
- // row, `pipeline.mjs:2908` at a different function entirely, and neither line was blank. Only 's
927
- // arm can decide those, and only where the citation names a symbol. A clean run here is evidence about
926
+ // row, line 2908 of `pipeline.mjs` at a different function entirely, and neither line was blank. Only the
927
+ // symbol arm can decide those, and only where the citation names a symbol. A clean run here is evidence about
928
928
  // punctuation, not about correctness.
929
929
  //
930
930
  // AN OVERRUN IS NOT THIS FINDING. A span running past the file's last line is already reported as
@@ -6,8 +6,8 @@
6
6
  // node scripts/dead-names.mjs report; exit 1 if a dead name is back
7
7
  // node scripts/dead-names.mjs --json the same, as JSON
8
8
  //
9
- // ruled Lore and Aughra dead names that may appear nowhere, and the purge is done: the issue's
10
- // own pattern returned 58 files when it was written and returns ONE now, which is a dictionary snippet
9
+ // The owner ruled Lore and Aughra dead names that may appear nowhere, and the purge is done: the issue's
10
+ // own pattern returned 58 files when it was written and ONE when this guard landed, a dictionary snippet
11
11
  // about hunting inside captured search results. What does not exist is anything to keep it that way. A
12
12
  // name published once stays published, so the cost of a re-introduction is not a revert — it is a name
13
13
  // in the history of a repository that is about to be cut in public.
@@ -42,37 +42,38 @@ export const GUARD = "dead-names (#853)";
42
42
  // the issue names as deployed surfaces, and `_` is a word character, so a trailing `\b` would miss the
43
43
  // environment variable while catching the prose. No English word begins with those six letters, so the
44
44
  // leading boundary is all the protection the arm needs — measured: zero hits in the tree either way.
45
- // `clawdi-lisa` and `clawdi-lidia` are 's, not 's, and they are here rather than in a second
46
- // scanner because the mechanism is identical: a name that must never reappear. They were per-person
47
- // agent identities for individuals at this firm, shipped in an operator script's default roster, in a
48
- // systemd unit watching their queues, and in two test fixtures. That out-of-scope note ruled `alex`
49
- // and `sam` INVENTED, which is why those stay; these two were never covered by that ruling.
50
- export const DEAD_NAME_RE = /lorestar|lore_(pool|url|flags)|clearance_lore|lorectl|loreurl|lorecontrols|trademark-lore|\blore\b|\baughra|clawdi-(lisa|lidia)/i;
45
+ // A PER-PERSON IDENTITY IS THE SAME KIND OF NAME, AND IT IS NOT WRITTEN HERE. An agent identity named
46
+ // after a real person must never reappear either, and the mechanism is identical. But a public guard
47
+ // that spells out a person's name publishes the name it exists to keep out, so no such arm is written
48
+ // into this file: `deadNameHits` takes its pattern as an argument, and the test drives the mechanism
49
+ // with an invented identity. The names the tests give agents and requesters are invented too.
50
+ export const DEAD_NAME_RE = /lorestar|lore_(pool|url|flags)|clearance_lore|lorectl|loreurl|lorecontrols|trademark-lore|\blore\b|\baughra/i;
51
51
 
52
52
  /**
53
53
  * Where a true match is somebody else's word rather than our dead name. NAMED, with the reason, and
54
54
  * scoped to the file — never a blanket pattern, because a blanket would re-admit the name everywhere.
55
+ * The first exemption was a dictionary snippet in the demo's captured common-law results; the demo was
56
+ * re-captured without it, so that exemption was deleted rather than carried.
55
57
  */
56
58
  export const EXEMPTIONS = [
57
59
  {
58
- file: "demo/multi-country-focus-search/run/common-law-grid.json",
59
- why: "captured third-party search results. The hit is a Collins English Dictionary definition of "
60
- + "hunting — \"the art, sport, lore, or practice of hunting\" — inside a snippet the common-law "
61
- + "sweep fetched and the driver saved VERBATIM. Editing it would corrupt a machine receipt to "
62
- + "satisfy a lint, which is the one thing a receipt may never be.",
60
+ file: "driver/wordlists/en.txt",
61
+ why: "the engine's list of ordinary English words, the dictionary of the one-letter neighbourhood "
62
+ + "filter. `lore` is an ordinary English word and sits on it. The list only ever removes queries, "
63
+ + "so taking the word out would change which searches a run makes, to satisfy a lint.",
63
64
  },
64
65
  ];
65
66
 
66
67
  const exemptionFor = (file) => EXEMPTIONS.find((e) => e.file === file) ?? null;
67
68
 
68
- /** Every dead-name hit in a corpus of {file, text}. PURE. */
69
- export function deadNameHits(corpus) {
69
+ /** Every dead-name hit in a corpus of {file, text}, against DEAD_NAME_RE unless a pattern is given. PURE. */
70
+ export function deadNameHits(corpus, pattern = DEAD_NAME_RE) {
70
71
  const out = [];
71
72
  for (const { file, text } of corpus ?? []) {
72
73
  if (exemptionFor(file)) continue;
73
74
  const lines = String(text ?? "").split("\n");
74
75
  for (let i = 0; i < lines.length; i++) {
75
- if (DEAD_NAME_RE.test(lines[i])) out.push({ file, line: i + 1, text: lines[i].trim().slice(0, 160) });
76
+ if (pattern.test(lines[i])) out.push({ file, line: i + 1, text: lines[i].trim().slice(0, 160) });
76
77
  }
77
78
  }
78
79
  return out;
package/scripts/e2e.mjs CHANGED
@@ -2370,7 +2370,7 @@ const TERMINAL_BY_SUFFIX_RAN = { failed: "failed", done: "delivered", cancelled:
2370
2370
 
2371
2371
  // REPAIR — the live-state vocabulary is IMPORTED, not retyped. This file's first draft spelled it
2372
2372
  // out twice more (an in-flight suffix set and a claim-lock regex), which is exactly the failure
2373
- // queue-markers.mjs was created for: "written down three times and the copies disagreed ". 's
2373
+ // queue-markers.mjs was created for: "written down three times and the copies disagreed". The
2374
2374
  // claim lock in particular is a NORMAL in-flight state — a live token is a claim in progress, a dead one
2375
2375
  // is restored by sweepAbandonedTakeovers — and one more private copy of that rule is one more chance to
2376
2376
  // report a claim race as a stranded job.
@@ -310,6 +310,12 @@ export function isProductFile(rel) {
310
310
  if (/\.test\.mjs$/.test(rel)) return false;
311
311
  if (/^e2e\//.test(rel)) return false;
312
312
  if (/^scripts\/(e2e|test-run|ci-)/.test(rel)) return false;
313
+ // THE BROWSER CHECKS. CI and a developer run them against a fixture portal; no install runs one. A switch
314
+ // one of them reads, such as the delay that reproduces a slow runner, belongs to the check, and a row
315
+ // for it in `.env.example` would hand an operator a setting that changes nothing they run. Measured
316
+ // when this was added: HOME, PATH, TMPDIR and that delay were all the checks read, and the first three
317
+ // stay product, because shipping code reads them too.
318
+ if (/^scripts\/[a-z0-9-]+-render-check\.mjs$/.test(rel)) return false;
313
319
  return true;
314
320
  }
315
321
 
@@ -618,7 +624,7 @@ const EXTERNAL_RE = /^#\s*external:\s*(\S.*)$/;
618
624
  //
619
625
  // THE TWO MARKERS DO NOT CLEAR EACH OTHER. `pending` was a single slot, and any comment line that was
620
626
  // not an `# external:` reset it. Adding a second marker to that shape would mean an `# effect:` line
621
- // silently DELETED the `# external:` declaration above it — the row would go orphaned and 's
627
+ // silently DELETED the `# external:` declaration above it — the row would go orphaned and the orphan
622
628
  // guard would red, with the cause three lines away and invisible. Each marker carries its own slot.
623
629
  const EFFECT_RE = /^#\s*effect:\s*(\S.*)$/;
624
630
  // A COMMENTED-OUT ROW STILL COUNTS, the same rule `assigned` uses above and for the same reason: `# X=`
@@ -248,7 +248,7 @@ function namesMergedIntoCandidate(src) {
248
248
  const helpers = new Set([...src.matchAll(/Object\.assign\(\s*candidate\s*,\s*(?:await\s+)?([A-Za-z_$][\w$]*)\s*\(/g)]
249
249
  .map((m) => m[1]));
250
250
  for (const fn of helpers) {
251
- const at = src.search(new RegExp(`\\bfunction\\s+${fn.replace(/\$/g, "\\$")}\\s*\\(`));
251
+ const at = src.search(new RegExp(`\\bfunction\\s+${fn.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\(`));
252
252
  const refuse = (why) => new Error(`env-classify: bin/onboard.mjs writes what ${fn}() returns, and ${why}, so `
253
253
  + "the names it writes cannot be read. Read as none, they would land in `tuning` and on the deletion "
254
254
  + "population; that is this script failing to look, not a finding about the wizard.");
@@ -123,7 +123,7 @@ async function main() {
123
123
  try {
124
124
  ({ reconcileAndScan: gate } = await import("../cut/packed-artifact.mjs"));
125
125
  } catch (e) {
126
- console.error(" REFUSING (exit 2, could-not-look): cut/packed-artifact.mjs did not load —"
126
+ console.error(" REFUSING (exit 2, could not look): cut/packed-artifact.mjs did not load —"
127
127
  + ` ${String(e?.message ?? e)}.\n`
128
128
  + " That module carries the only rule that says which files may leave this repository, and a\n"
129
129
  + " pack that cannot ask it produces an artifact nobody has checked. An exported tree does not\n"
@@ -106,10 +106,10 @@ function main() {
106
106
  console.error("usage: node scripts/release-artifact-seal.mjs --tarball <path>");
107
107
  process.exit(2);
108
108
  }
109
- // A path that is not there is a could-not-look, never a seal that found nothing to do. Exit 2 is
109
+ // A path that is not there is a check that could not look, never a seal that found nothing to do. Exit 2 is
110
110
  // the house meaning and it keeps this distinguishable from a tarball that was sealed and was clean.
111
111
  if (!existsSync(tarball)) {
112
- console.error(` REFUSING (exit 2, could-not-look): ${tarball} does not exist, so nothing was sealed.`);
112
+ console.error(` REFUSING (exit 2, could not look): ${tarball} does not exist, so nothing was sealed.`);
113
113
  process.exit(2);
114
114
  }
115
115