@konneal/engine 0.2.100 → 0.2.102

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.
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  handleAsk
3
- } from "./chunk-GYCUMFOA.js";
3
+ } from "./chunk-FQVP3JUA.js";
4
4
  import "./chunk-QNLFNEI4.js";
5
- import "./chunk-BBKAETKM.js";
5
+ import "./chunk-25ECO5M7.js";
6
6
  import "./chunk-KNCRYAX3.js";
7
7
  import "./chunk-DTW4UW2E.js";
8
8
  import "./chunk-A3QHHUN5.js";
@@ -22318,27 +22318,27 @@ async function runStages(stages, c) {
22318
22318
  var dense = {
22319
22319
  name: "dense",
22320
22320
  run: async (c) => {
22321
- const { env, filter, filters, vector, opts, rq, folded } = c;
22321
+ const { env, filters, vector, opts, rq, folded } = c;
22322
22322
  const q = { topK: LIMITS.retrieveK, returnMetadata: "all" };
22323
- if (filter) q.filter = filter;
22324
22323
  const optimistic = opts.optimisticHits ?? [];
22325
22324
  const sameLane = rq === folded;
22326
- if (!filter && sameLane && optimistic.length) {
22325
+ if (!filters?.doc_number && sameLane && optimistic.length) {
22327
22326
  c.matches = optimistic.map((h) => ({ id: h.id, score: h.score, metadata: h.metadata }));
22328
22327
  console.log("optimistic lane: reused", c.matches.length, "dense hits (no re-query)");
22329
22328
  return;
22330
22329
  }
22331
- if (filter) {
22332
- let matches = (await env.VECTORIZE.query(vector, q)).matches ?? [];
22330
+ if (filters?.doc_number) {
22331
+ const allMatches = (await env.VECTORIZE.query(vector, q)).matches ?? [];
22332
+ let matches = allMatches;
22333
+ const before = matches.length;
22334
+ matches = matches.filter((m) => String(m.metadata?.doc_number ?? "") === filters.doc_number);
22335
+ if (filters.edition) matches = matches.filter((m) => !m.metadata?.edition || String(m.metadata.edition) === filters.edition);
22336
+ console.log("dense scope (in code):", before, "\u2192", matches.length, "in doc", filters.doc_number, filters.edition ? `@${filters.edition}` : "");
22333
22337
  if (filters && filters.edition && matches.length < 3) {
22334
- const docOnly = await env.VECTORIZE.query(vector, {
22335
- topK: LIMITS.retrieveK,
22336
- returnMetadata: "all",
22337
- filter: toVectorizeFilter({ doc_number: filters.doc_number })
22338
- });
22339
- if ((docOnly.matches ?? []).length > matches.length) {
22340
- console.log("edition pin dropped:", filters.doc_number, "@", filters.edition, "\u2192", docOnly.matches?.length ?? 0, "doc-scoped hits (edition not in corpus)");
22341
- matches = docOnly.matches ?? [];
22338
+ const docOnly = allMatches.filter((m) => String(m.metadata?.doc_number ?? "") === filters.doc_number);
22339
+ if (docOnly.length > matches.length) {
22340
+ console.log("edition pin dropped:", filters.doc_number, "@", filters.edition, "\u2192", docOnly.length, "doc-scoped hits (edition not in corpus)");
22341
+ matches = docOnly;
22342
22342
  filters.edition = void 0;
22343
22343
  }
22344
22344
  }
@@ -22520,10 +22520,10 @@ var conceptGraph = {
22520
22520
  }
22521
22521
  }
22522
22522
  if (numbers.size) {
22523
- const gc = await lexicalWithin(c.env, c.rq || c.query, [...numbers], 12);
22523
+ const gc = await lexicalWithin(c.env, c.rq || c.query, [...numbers], 6);
22524
22524
  const seenIds0 = new Set(c.matches.map((m) => m.id));
22525
22525
  let merged0 = 0;
22526
- for (const m of gc.slice(0, 6)) {
22526
+ for (const m of gc.slice(0, 3)) {
22527
22527
  if (!seenIds0.has(m.id)) {
22528
22528
  c.matches.push({ id: m.id, score: m.score * THRESHOLDS.conceptGraphDiscount, metadata: m.metadata });
22529
22529
  seenIds0.add(m.id);
@@ -22547,9 +22547,9 @@ var graphLane = {
22547
22547
  const g = await c.lane["graph-lane"];
22548
22548
  const seenIds = new Set(c.matches.map((m) => m.id));
22549
22549
  let merged = 0;
22550
- for (const m of (g ?? []).slice(0, 10)) {
22550
+ for (const m of (g ?? []).slice(0, 5)) {
22551
22551
  if (!seenIds.has(m.id)) {
22552
- c.matches.push({ id: m.id, score: m.score * THRESHOLDS.graphLaneDiscount, metadata: m.metadata });
22552
+ c.matches.push({ id: m.id, score: m.score * THRESHOLDS.graphLaneDiscount * 0.5, metadata: m.metadata });
22553
22553
  seenIds.add(m.id);
22554
22554
  merged++;
22555
22555
  }
@@ -22580,17 +22580,40 @@ var licensedLane = {
22580
22580
  const matches = await portIndex(c.env, "public").getByIds(ids);
22581
22581
  const seenIds = new Set(c.matches.map((m) => m.id));
22582
22582
  let merged = 0;
22583
+ const mergedIds = [];
22583
22584
  for (const m of matches) {
22584
22585
  if (m.metadata && !seenIds.has(m.id)) {
22585
22586
  c.matches.push({ id: m.id, score: THRESHOLDS.licensedLaneScore, metadata: m.metadata });
22586
22587
  seenIds.add(m.id);
22587
22588
  merged++;
22589
+ mergedIds.push(m.id);
22588
22590
  }
22589
22591
  }
22592
+ c.lane["licensed-ids"] = Promise.resolve(mergedIds);
22590
22593
  console.log("licensed lane:", ids.length, "units,", matches.length, "vectors,", merged, "merged");
22591
22594
  }
22592
22595
  };
22593
22596
 
22597
+ // workers/worker_public/src/stages/licensedCover.ts
22598
+ var licensedCover = {
22599
+ name: "licensed-cover",
22600
+ failure: "additive",
22601
+ when: (c) => !!c.opts.licensedDocNumbers?.length && c.finalHits.length > 0,
22602
+ run: async (c) => {
22603
+ const keys = new Set(c.opts.licensedDocNumbers);
22604
+ const present = c.finalHits.some((h) => keys.has(String(h.metadata.standard ?? "")));
22605
+ if (present) return;
22606
+ const ids = (await c.lane["licensed-ids"] ?? []).slice(0, 8);
22607
+ if (!ids.length) return;
22608
+ const matches = await portIndex(c.env, "public").getByIds(ids);
22609
+ const unit = matches.find((m) => m.metadata && keys.has(String(m.metadata.standard ?? "")));
22610
+ if (!unit) return;
22611
+ const hit = toHits([unit])[0];
22612
+ c.finalHits = [...c.finalHits, hit];
22613
+ console.log("licensed cover: seated", unit.id, "\u2014", unit.metadata.docidentifier);
22614
+ }
22615
+ };
22616
+
22594
22617
  // workers/worker_public/src/stages/multiQuery.ts
22595
22618
  var RRF_K = 60;
22596
22619
  var multiQuery = {
@@ -22798,12 +22821,10 @@ var editionCover = {
22798
22821
  let added = 0;
22799
22822
  for (const w of want.slice(0, maxDocs)) {
22800
22823
  try {
22801
- const q = await c.env.VECTORIZE.query(c.vector, {
22802
- topK: 3,
22803
- returnMetadata: "all",
22804
- filter: { $and: [{ docidentifier: { $eq: w.di } }, { edition: { $eq: w.edition } }] }
22805
- });
22806
- const hits = toHits(q.matches ?? []).map((h) => ({ ...h, score: top * THRESHOLDS.editionCoverDiscount }));
22824
+ const rows2 = await c.env.DB.prepare(
22825
+ "SELECT id, doc_id, docidentifier, doctype, doc_number, edition, language, clause_anchor, clause_title, status, superseded_by, corpus, tier, text FROM chunks WHERE docidentifier = ?1 AND edition = ?2 LIMIT 3"
22826
+ ).bind(w.di, w.edition).all();
22827
+ const hits = rowsToHits(rows2.results ?? []).map((h) => ({ ...h, score: top * THRESHOLDS.editionCoverDiscount }));
22807
22828
  c.hits.push(...hits);
22808
22829
  added += hits.length;
22809
22830
  console.log("edition cover:", w.di, w.edition, `+${hits.length}`);
@@ -23223,6 +23244,7 @@ var STAGES = [
23223
23244
  editionSteer,
23224
23245
  propagate,
23225
23246
  diversity,
23247
+ licensedCover,
23226
23248
  typedPin,
23227
23249
  sectionDescent,
23228
23250
  dedup,
@@ -56,7 +56,7 @@ import {
56
56
  tokenBudget,
57
57
  understandQuery,
58
58
  usageTotal
59
- } from "./chunk-BBKAETKM.js";
59
+ } from "./chunk-25ECO5M7.js";
60
60
  import {
61
61
  embed,
62
62
  generateOnce,
@@ -6,7 +6,7 @@ import {
6
6
  sessionFrom,
7
7
  telemetry,
8
8
  understandQuery
9
- } from "./chunk-BBKAETKM.js";
9
+ } from "./chunk-25ECO5M7.js";
10
10
  import {
11
11
  portModelRunner
12
12
  } from "./chunk-KNCRYAX3.js";
package/dist/lexical.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { Hit } from "./pipeline";
2
2
  /** Build a safe FTS5 MATCH query from user text: alphanumeric tokens,
3
3
  * joined with OR so jargon hits don't require full-phrase match. */
4
4
  export declare function ftsMatchQuery(query: string): string | null;
5
+ export declare function rowsToHits(rows: any[]): Hit[];
5
6
  /** Lexical ranking WITHIN a document set (the graph lanes' identity
6
7
  * resolution): BM25 over the corpus's own FTS, restricted to the
7
8
  * documents' doc_number values. This is the working substitute for
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  handleSearch
3
- } from "./chunk-OREMF5LK.js";
4
- import "./chunk-BBKAETKM.js";
3
+ } from "./chunk-TFGJ6NYS.js";
4
+ import "./chunk-25ECO5M7.js";
5
5
  import "./chunk-KNCRYAX3.js";
6
6
  import "./chunk-DTW4UW2E.js";
7
7
  import "./chunk-A3QHHUN5.js";
@@ -4,7 +4,7 @@ import {
4
4
  handleMemories,
5
5
  scoreFaithfulness,
6
6
  scoreJudge
7
- } from "../../chunk-GYCUMFOA.js";
7
+ } from "../../chunk-FQVP3JUA.js";
8
8
  import {
9
9
  TOOLS_REGISTRY,
10
10
  bindModelNode,
@@ -12,7 +12,7 @@ import {
12
12
  } from "../../chunk-QNLFNEI4.js";
13
13
  import {
14
14
  handleSearch
15
- } from "../../chunk-OREMF5LK.js";
15
+ } from "../../chunk-TFGJ6NYS.js";
16
16
  import {
17
17
  buildMessages,
18
18
  citations,
@@ -34,7 +34,7 @@ import {
34
34
  sessionFrom,
35
35
  telemetry,
36
36
  understandQuery
37
- } from "../../chunk-BBKAETKM.js";
37
+ } from "../../chunk-25ECO5M7.js";
38
38
  import {
39
39
  cfBlobs,
40
40
  embed,
@@ -996,7 +996,7 @@ async function callToolOnce(env, ctx, _req, tier, key, name, args, t0) {
996
996
  const r = await runTool({ ...env, DB: env.DB }, { name, args }, "mcp");
997
997
  payload = r ? { tool: r.name, query: r.query, result: r.output } : { error: { message: `tool ${name} returned nothing for the given arguments` } };
998
998
  } else {
999
- const res = name === "ask" ? await (await import("../../ask-KP42T6DH.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-4CBKKNBJ.js")).handleSearch(env, ctx, inner, tier, key);
999
+ const res = name === "ask" ? await (await import("../../ask-E6YJUOIP.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-5SNP5CHL.js")).handleSearch(env, ctx, inner, tier, key);
1000
1000
  payload = await res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
1001
1001
  }
1002
1002
  telemetry(env, ctx, tier, `mcp:${name}`, null, !payload?.error, JSON.stringify(payload).length, await sha256Hex(`${name}:${JSON.stringify(args ?? {})}`), void 0, void 0, { durationMs: Date.now() - t0 });
package/package.json CHANGED
@@ -27,7 +27,7 @@
27
27
  "playwright": "^1.62.1",
28
28
  "yaml": "^2.9.1"
29
29
  },
30
- "version": "0.2.100",
30
+ "version": "0.2.102",
31
31
  "description": "The Konneal engine: the publisher-agnostic build pipeline and API plane for standards intelligence (retrieval, answer contract, verdicts, evaluation).",
32
32
  "license": "BSD-3-Clause",
33
33
  "type": "module",
@@ -53,7 +53,7 @@ function rowMeta(r: any): ChunkMeta {
53
53
  };
54
54
  }
55
55
 
56
- function rowsToHits(rows: any[]): Hit[] {
56
+ export function rowsToHits(rows: any[]): Hit[] {
57
57
  return rows.map((r: any) => {
58
58
  // rank is bm25 (lower better) → convert to positive score that RRF can ignore
59
59
  // (RRF uses rank position, not score). score kept for logging only.
@@ -54,10 +54,10 @@ export const conceptGraph: Stage = {
54
54
  // candidates rank by the corpus's own BM25 within the set — the
55
55
  // index filter this lane used returned empty for its whole life
56
56
  // (Vectorize metadata filtering is dead on this index)
57
- const gc = await lexicalWithin(c.env, c.rq || c.query, [...numbers], 12);
57
+ const gc = await lexicalWithin(c.env, c.rq || c.query, [...numbers], 6);
58
58
  const seenIds0 = new Set(c.matches.map((m: any) => m.id));
59
59
  let merged0 = 0;
60
- for (const m of gc.slice(0, 6)) {
60
+ for (const m of gc.slice(0, 3)) {
61
61
  if (!seenIds0.has(m.id)) {
62
62
  c.matches.push({ id: m.id, score: m.score * THRESHOLDS.conceptGraphDiscount, metadata: m.metadata });
63
63
  seenIds0.add(m.id);
@@ -4,18 +4,20 @@
4
4
  // saved), the doc/edition-filtered query with its guessed-pin drop and
5
5
  // sparse-filter widen, and the plain unfiltered query.
6
6
  import { LIMITS } from "../config.ts";
7
- import { toVectorizeFilter } from "../selfquery.ts";
8
7
  import type { Stage } from "./types.ts";
9
8
 
10
9
  export const dense: Stage = {
11
10
  name: "dense",
12
11
  run: async (c) => {
13
- const { env, filter, filters, vector, opts, rq, folded } = c;
12
+ const { env, filters, vector, opts, rq, folded } = c;
13
+ // the index's metadata filtering is DEAD on this platform (measured
14
+ // 2026-10-05: filters return empty for values proven present), so
15
+ // the scope never rides the query — the matches filter IN CODE and
16
+ // the sparse widen still covers thin scopes
14
17
  const q: any = { topK: LIMITS.retrieveK, returnMetadata: "all" };
15
- if (filter) q.filter = filter;
16
18
  const optimistic = opts.optimisticHits ?? [];
17
19
  const sameLane = rq === folded; // optimistic vector === this lane's vector
18
- if (!filter && sameLane && optimistic.length) {
20
+ if (!filters?.doc_number && sameLane && optimistic.length) {
19
21
  // Option C fast path: the unfiltered dense query already ran
20
22
  // concurrently with understanding — same vector, same topK, no
21
23
  // filter. Reusing it skips one serial Vectorize round-trip with a
@@ -31,8 +33,13 @@ export const dense: Stage = {
31
33
  // REPLACE an identical query, never dilute a better one.
32
34
  return;
33
35
  }
34
- if (filter) {
35
- let matches: any[] = (await env.VECTORIZE.query(vector, q)).matches ?? [];
36
+ if (filters?.doc_number) {
37
+ const allMatches: any[] = (await env.VECTORIZE.query(vector, q)).matches ?? [];
38
+ let matches: any[] = allMatches;
39
+ const before = matches.length;
40
+ matches = matches.filter((m) => String(m.metadata?.doc_number ?? "") === filters.doc_number);
41
+ if (filters.edition) matches = matches.filter((m) => !m.metadata?.edition || String(m.metadata.edition) === filters.edition);
42
+ console.log("dense scope (in code):", before, "→", matches.length, "in doc", filters.doc_number, filters.edition ? `@${filters.edition}` : "");
36
43
  // an edition pin corroborated by (almost) nothing means the pin
37
44
  // was a guess — understanding emits editions for families it
38
45
  // mixes up (observed: R 76 pinned @2021, an R 60 year; the corpus
@@ -42,15 +49,13 @@ export const dense: Stage = {
42
49
  // the doc-only filter and let family-relative steering rank
43
50
  // editions downstream. A pin the user actually asked for
44
51
  // survives — its edition exists in the corpus.
52
+ // the same guess-drop, in code: a pinned edition that starved the
53
+ // scoped set drops to the doc-only scope
45
54
  if (filters && filters.edition && matches.length < 3) {
46
- const docOnly = await env.VECTORIZE.query(vector, {
47
- topK: LIMITS.retrieveK,
48
- returnMetadata: "all",
49
- filter: toVectorizeFilter({ doc_number: filters.doc_number }),
50
- });
51
- if ((docOnly.matches ?? []).length > matches.length) {
52
- console.log("edition pin dropped:", filters.doc_number, "@", filters.edition, "→", docOnly.matches?.length ?? 0, "doc-scoped hits (edition not in corpus)");
53
- matches = docOnly.matches ?? [];
55
+ const docOnly = allMatches.filter((m) => String(m.metadata?.doc_number ?? "") === filters.doc_number);
56
+ if (docOnly.length > matches.length) {
57
+ console.log("edition pin dropped:", filters.doc_number, "@", filters.edition, "→", docOnly.length, "doc-scoped hits (edition not in corpus)");
58
+ matches = docOnly;
54
59
  filters.edition = undefined;
55
60
  }
56
61
  }
@@ -7,7 +7,8 @@
7
7
  // left every 2021 chunk out). One registry read per query; at most
8
8
  // `maxDocs` documents covered; additive — any failure skips.
9
9
  import { THRESHOLDS } from "../config.ts";
10
- import { toHits, type Stage } from "./types.ts";
10
+ import type { Stage } from "./types.ts";
11
+ import { rowsToHits } from "../lexical.ts";
11
12
 
12
13
  const maxDocs = 2;
13
14
 
@@ -43,12 +44,17 @@ export const editionCover: Stage = {
43
44
  let added = 0;
44
45
  for (const w of want.slice(0, maxDocs)) {
45
46
  try {
46
- const q = await c.env.VECTORIZE.query(c.vector, {
47
- topK: 3,
48
- returnMetadata: "all",
49
- filter: { $and: [{ docidentifier: { $eq: w.di } }, { edition: { $eq: w.edition } }] },
50
- });
51
- const hits = toHits((q as any).matches ?? []).map((h) => ({ ...h, score: top * THRESHOLDS.editionCoverDiscount }));
47
+ // identity resolution, not index filtering: the index's metadata
48
+ // filters are dead on this platform (measured 2026-10-05 — the
49
+ // cover's $and docidentifier/edition query returned empty for
50
+ // its whole life), but the D1 chunks table IS the corpus — the
51
+ // current edition's chunks read directly, text and all
52
+ const rows = await c.env.DB.prepare(
53
+ "SELECT id, doc_id, docidentifier, doctype, doc_number, edition, language, clause_anchor, clause_title, status, superseded_by, corpus, tier, text FROM chunks WHERE docidentifier = ?1 AND edition = ?2 LIMIT 3",
54
+ )
55
+ .bind(w.di, w.edition)
56
+ .all();
57
+ const hits = rowsToHits(rows.results ?? []).map((h) => ({ ...h, score: top * THRESHOLDS.editionCoverDiscount }));
52
58
  c.hits.push(...hits);
53
59
  added += hits.length;
54
60
  console.log("edition cover:", w.di, w.edition, `+${hits.length}`);
@@ -23,9 +23,15 @@ export const graphLane: Stage = {
23
23
  const g = (await c.lane["graph-lane"]!) as any;
24
24
  const seenIds = new Set(c.matches.map((m: any) => m.id));
25
25
  let merged = 0;
26
- for (const m of (g ?? []).slice(0, 10)) {
26
+ // narrow by the reverted experiment's own law (rag#137): candidate-
27
+ // family flooding dilutes the pool — five candidates, and at HALF
28
+ // the lane discount so only family text the cross-encoder genuinely
29
+ // prefers survives the cut (the first breathing gate measured the
30
+ // flood signature: faithfulness sagging across unrelated legs and
31
+ // the typed blocks displaced from the context)
32
+ for (const m of (g ?? []).slice(0, 5)) {
27
33
  if (!seenIds.has(m.id)) {
28
- c.matches.push({ id: m.id, score: m.score * THRESHOLDS.graphLaneDiscount, metadata: m.metadata });
34
+ c.matches.push({ id: m.id, score: m.score * THRESHOLDS.graphLaneDiscount * 0.5, metadata: m.metadata });
29
35
  seenIds.add(m.id);
30
36
  merged++;
31
37
  }
@@ -18,6 +18,7 @@ import { glossary } from "./glossary.ts";
18
18
  import { conceptGraph } from "./conceptGraph.ts";
19
19
  import { graphLane } from "./graphLane.ts";
20
20
  import { licensedLane } from "./licensedLane.ts";
21
+ import { licensedCover } from "./licensedCover.ts";
21
22
  import { multiQuery } from "./multiQuery.ts";
22
23
  import { subQuery } from "./subQuery.ts";
23
24
  import { poolOpen } from "./poolOpen.ts";
@@ -68,6 +69,7 @@ export const STAGES: Stage[] = [
68
69
  editionSteer,
69
70
  propagate,
70
71
  diversity,
72
+ licensedCover,
71
73
  typedPin,
72
74
  sectionDescent,
73
75
  dedup,
@@ -0,0 +1,31 @@
1
+ // Licensed cover (the seat): an entitled, topically-matched caller
2
+ // asked about the licensed standard, so the licensed package's own
3
+ // machinery must be ABLE to reach the answer — the public restatements
4
+ // crowd the window and the citation the golden legs assert rides this
5
+ // passage. When the rerank cut drops every licensed unit, the lane's
6
+ // best unit takes one seat at the end of the window (appended after
7
+ // ranking, like the section-descent children — the window floor keeps
8
+ // appended passages by construction). Narrow by law: one unit, only
9
+ // when none is present, only for an armed licensed lane.
10
+ import { portIndex } from "../env.ts";
11
+ import { toHits } from "./types.ts";
12
+ import type { Stage } from "./types.ts";
13
+
14
+ export const licensedCover: Stage = {
15
+ name: "licensed-cover",
16
+ failure: "additive",
17
+ when: (c) => !!c.opts.licensedDocNumbers?.length && c.finalHits.length > 0,
18
+ run: async (c) => {
19
+ const keys = new Set(c.opts.licensedDocNumbers!);
20
+ const present = c.finalHits.some((h) => keys.has(String((h.metadata as any).standard ?? "")));
21
+ if (present) return;
22
+ const ids = ((await (c.lane["licensed-ids"] as Promise<string[]> | undefined)) ?? []).slice(0, 8);
23
+ if (!ids.length) return;
24
+ const matches = await portIndex(c.env, "public").getByIds(ids);
25
+ const unit = matches.find((m) => m.metadata && keys.has(String((m.metadata as any).standard ?? "")));
26
+ if (!unit) return;
27
+ const hit = toHits([unit as any])[0]!;
28
+ c.finalHits = [...c.finalHits, hit];
29
+ console.log("licensed cover: seated", unit.id, "—", (unit.metadata as any).docidentifier);
30
+ },
31
+ };
@@ -44,13 +44,18 @@ export const licensedLane: Stage = {
44
44
  const matches = await portIndex(c.env, "public").getByIds(ids);
45
45
  const seenIds = new Set(c.matches.map((m: any) => m.id));
46
46
  let merged = 0;
47
+ const mergedIds: string[] = [];
47
48
  for (const m of matches) {
48
49
  if (m.metadata && !seenIds.has(m.id)) {
49
50
  c.matches.push({ id: m.id, score: THRESHOLDS.licensedLaneScore, metadata: m.metadata });
50
51
  seenIds.add(m.id);
51
52
  merged++;
53
+ mergedIds.push(m.id);
52
54
  }
53
55
  }
56
+ // the cover stage's stash: the licensed units' ids, so the seat can
57
+ // recover a unit the rerank cut dropped
58
+ c.lane["licensed-ids"] = Promise.resolve(mergedIds);
54
59
  console.log("licensed lane:", ids.length, "units,", matches.length, "vectors,", merged, "merged");
55
60
  },
56
61
  };