osolmaz commited on
Commit
542f736
·
verified ·
1 Parent(s): 7400d99

deploy: c2c7937

Browse files
space/src/consumer-coverage-update.ts CHANGED
@@ -1,9 +1,14 @@
1
  import type Database from "better-sqlite3";
2
  import { canonicalJson } from "@xtap-pool/shared";
3
  import type { ResolvedConsumerContext } from "./consumer-context.js";
 
4
  import { historicalSelection } from "./consumer-changes.js";
5
  import { selectedCompleteThrough } from "./enrich-store.js";
6
- import { selectedObservationThrough } from "./consumer-coverage.js";
 
 
 
 
7
  import { ConsumerCoverageEffects } from "./consumer-coverage-effects.js";
8
 
9
  export type ConsumerCoverage = {
@@ -63,6 +68,27 @@ function metricCoverage(
63
  function recalculate(
64
  database: Database.Database,
65
  target: ResolvedConsumerContext,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  ): ConsumerCoverage {
67
  return {
68
  kind: "coverage",
 
1
  import type Database from "better-sqlite3";
2
  import { canonicalJson } from "@xtap-pool/shared";
3
  import type { ResolvedConsumerContext } from "./consumer-context.js";
4
+ import { ConsumerBootstrapRequired, ConsumerIndexState } from "./consumer-index-state.js";
5
  import { historicalSelection } from "./consumer-changes.js";
6
  import { selectedCompleteThrough } from "./enrich-store.js";
7
+ import {
8
+ selectedCurrentCoverageUnitIds,
9
+ selectedCurrentObservationThrough,
10
+ selectedObservationThrough,
11
+ } from "./consumer-coverage.js";
12
  import { ConsumerCoverageEffects } from "./consumer-coverage-effects.js";
13
 
14
  export type ConsumerCoverage = {
 
68
  function recalculate(
69
  database: Database.Database,
70
  target: ResolvedConsumerContext,
71
+ ): ConsumerCoverage {
72
+ try {
73
+ new ConsumerIndexState(database, target.context.contract, "read").require(
74
+ target.context.source,
75
+ );
76
+ } catch (error) {
77
+ if (error instanceof ConsumerBootstrapRequired) return historicalCoverage(database, target);
78
+ throw error;
79
+ }
80
+ const unitIds = selectedCurrentCoverageUnitIds(database, target);
81
+ return {
82
+ kind: "coverage",
83
+ mode: "semantic",
84
+ completeThrough: selectedCompleteThrough(database, { unitIds }) ?? null,
85
+ observationsThrough: selectedCurrentObservationThrough(database, target, unitIds),
86
+ };
87
+ }
88
+
89
+ function historicalCoverage(
90
+ database: Database.Database,
91
+ target: ResolvedConsumerContext,
92
  ): ConsumerCoverage {
93
  return {
94
  kind: "coverage",
space/src/consumer-coverage.ts CHANGED
@@ -4,6 +4,36 @@ import { eligibleUnits } from "./enrich-store.js";
4
  import { historicalSelection } from "./consumer-changes.js";
5
  import type { ResolvedConsumerContext } from "./consumer-context.js";
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  /** Current selected, completed public content only. Counters have their own clock;
8
  * pending, unselected, subscriber-only and retweet samples cannot advance it. */
9
  export function selectedObservationThrough(
@@ -11,8 +41,6 @@ export function selectedObservationThrough(
11
  target: ResolvedConsumerContext,
12
  postIds?: readonly string[],
13
  ): string | null {
14
- const context = target.context;
15
- const selection = historicalSelection(target);
16
  const unitIds =
17
  postIds === undefined
18
  ? undefined
@@ -21,13 +49,40 @@ export function selectedObservationThrough(
21
  "SELECT DISTINCT unit_id FROM unit_members WHERE tweet_id IN (SELECT value FROM json_each(?))",
22
  )
23
  .all(JSON.stringify(postIds))
24
- .map((row) => z.object({ unit_id: z.string() }).parse(row).unit_id);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  const eligible = eligibleUnits({
26
- ...selection,
27
- ...(unitIds === undefined ? {} : { unitIds }),
28
  taxonomyVersion: context.taxonomy.version,
29
  contractHash: context.contract,
30
  });
 
 
 
 
 
31
  // Read each selected post once and stop at its newest permitted observation.
32
  // Scanning every historical counter sample repeats source and visibility checks.
33
  const row = database
@@ -51,8 +106,7 @@ export function selectedObservationThrough(
51
  AND (json_type(c.payload_json, '$.is_subscriber_only') IS NULL OR json_type(c.payload_json, '$.is_subscriber_only') = 'false')
52
  AND (json_type(c.payload_json, '$.is_retweet') IS NULL OR json_type(c.payload_json, '$.is_retweet') = 'false')
53
  AND json_extract(c.payload_json, '$.author.id') IN (SELECT value FROM json_each(?))
54
- AND EXISTS (SELECT 1 FROM observation_sources s INDEXED BY idx_observation_source_id WHERE s.observation_id = o.observation_id
55
- AND s.segment_key IN (SELECT json_extract(value, '$.key') FROM json_each(?)))
56
  ORDER BY o.observed_at DESC, o.observation_id DESC LIMIT 1
57
  )) AS latest FROM selected_posts p
58
  `,
@@ -65,7 +119,7 @@ export function selectedObservationThrough(
65
  postIds === undefined ? null : JSON.stringify(postIds),
66
  postIds === undefined ? null : JSON.stringify(postIds),
67
  JSON.stringify(selection.authorIds),
68
- JSON.stringify(target.snapshot.files),
69
  );
70
  return z.object({ latest: z.string().nullable() }).parse(row).latest;
71
  }
 
4
  import { historicalSelection } from "./consumer-changes.js";
5
  import type { ResolvedConsumerContext } from "./consumer-context.js";
6
 
7
+ const unitRow = z.object({ unit_id: z.string() });
8
+
9
+ /** Historical membership is an indexed superset of current selected units.
10
+ * Start there, then apply the exact current author and publication rules once
11
+ * for both coverage clocks. */
12
+ export function selectedCurrentCoverageUnitIds(
13
+ database: Database.Database,
14
+ target: ResolvedConsumerContext,
15
+ ): string[] {
16
+ const context = target.context;
17
+ const authors = JSON.stringify(context.selection.author_ids);
18
+ return database
19
+ .prepare(
20
+ `WITH candidates AS MATERIALIZED (
21
+ SELECT DISTINCT unit_id FROM consumer_post_units
22
+ WHERE author_id IN (SELECT value FROM json_each(@authors))
23
+ ) SELECT c.unit_id FROM candidates c WHERE
24
+ NOT EXISTS (SELECT 1 FROM unit_members m JOIN tweets t ON t.id = m.tweet_id
25
+ WHERE m.unit_id = c.unit_id AND (
26
+ json_extract(t.json, '$.author.id') IS NULL
27
+ OR json_extract(t.json, '$.author.id') NOT IN (SELECT value FROM json_each(@authors))
28
+ OR json_extract(t.json, '$.is_subscriber_only') = 1))
29
+ AND EXISTS (SELECT 1 FROM unit_members m JOIN tweets t ON t.id = m.tweet_id
30
+ WHERE m.unit_id = c.unit_id AND COALESCE(json_extract(t.json, '$.is_retweet'), 0) != 1)
31
+ ORDER BY c.unit_id`,
32
+ )
33
+ .all({ authors })
34
+ .map((row) => unitRow.parse(row).unit_id);
35
+ }
36
+
37
  /** Current selected, completed public content only. Counters have their own clock;
38
  * pending, unselected, subscriber-only and retweet samples cannot advance it. */
39
  export function selectedObservationThrough(
 
41
  target: ResolvedConsumerContext,
42
  postIds?: readonly string[],
43
  ): string | null {
 
 
44
  const unitIds =
45
  postIds === undefined
46
  ? undefined
 
49
  "SELECT DISTINCT unit_id FROM unit_members WHERE tweet_id IN (SELECT value FROM json_each(?))",
50
  )
51
  .all(JSON.stringify(postIds))
52
+ .map((row) => unitRow.parse(row).unit_id);
53
+ return observationThrough(database, target, unitIds, postIds, false);
54
+ }
55
+
56
+ /** The caller proved that the database is exactly the target source and applied
57
+ * current author and publication rules to unitIds. Do not repeat either scan. */
58
+ export function selectedCurrentObservationThrough(
59
+ database: Database.Database,
60
+ target: ResolvedConsumerContext,
61
+ unitIds: readonly string[],
62
+ ): string | null {
63
+ return observationThrough(database, target, unitIds, undefined, true);
64
+ }
65
+
66
+ function observationThrough(
67
+ database: Database.Database,
68
+ target: ResolvedConsumerContext,
69
+ unitIds: readonly string[] | undefined,
70
+ postIds: readonly string[] | undefined,
71
+ currentSource: boolean,
72
+ ): string | null {
73
+ const context = target.context;
74
+ const selection = historicalSelection(target);
75
  const eligible = eligibleUnits({
76
+ ...(currentSource ? { labels: selection.labels, labelMode: selection.labelMode } : selection),
77
+ unitIds,
78
  taxonomyVersion: context.taxonomy.version,
79
  contractHash: context.contract,
80
  });
81
+ const sourceSql = currentSource
82
+ ? ""
83
+ : `AND EXISTS (SELECT 1 FROM observation_sources s INDEXED BY idx_observation_source_id
84
+ WHERE s.observation_id = o.observation_id
85
+ AND s.segment_key IN (SELECT json_extract(value, '$.key') FROM json_each(?)))`;
86
  // Read each selected post once and stop at its newest permitted observation.
87
  // Scanning every historical counter sample repeats source and visibility checks.
88
  const row = database
 
106
  AND (json_type(c.payload_json, '$.is_subscriber_only') IS NULL OR json_type(c.payload_json, '$.is_subscriber_only') = 'false')
107
  AND (json_type(c.payload_json, '$.is_retweet') IS NULL OR json_type(c.payload_json, '$.is_retweet') = 'false')
108
  AND json_extract(c.payload_json, '$.author.id') IN (SELECT value FROM json_each(?))
109
+ ${sourceSql}
 
110
  ORDER BY o.observed_at DESC, o.observation_id DESC LIMIT 1
111
  )) AS latest FROM selected_posts p
112
  `,
 
119
  postIds === undefined ? null : JSON.stringify(postIds),
120
  postIds === undefined ? null : JSON.stringify(postIds),
121
  JSON.stringify(selection.authorIds),
122
+ ...(currentSource ? [] : [JSON.stringify(target.snapshot.files)]),
123
  );
124
  return z.object({ latest: z.string().nullable() }).parse(row).latest;
125
  }
space/src/enrich-store.ts CHANGED
@@ -1536,6 +1536,7 @@ function nullable<T>(value: T | undefined): T | null {
1536
  }
1537
 
1538
  type UnitSelection = {
 
1539
  authorIds?: readonly string[] | undefined;
1540
  labels?: readonly string[] | undefined;
1541
  labelMode?: "any" | "all" | undefined;
@@ -1544,7 +1545,6 @@ type UnitSelection = {
1544
  };
1545
 
1546
  type EligibleUnitOptions = UnitSelection & {
1547
- unitIds?: readonly string[];
1548
  taxonomyVersion: number;
1549
  contractHash: string;
1550
  };
@@ -1626,10 +1626,16 @@ function selectedUnits(options: UnitSelection): { sql: string; params: unknown[]
1626
  const cutoffParams = options.cutoff === undefined ? [] : [options.cutoff];
1627
  const authorParams = options.authorIds ?? [];
1628
  return {
1629
- sql: `WITH unit_ids AS MATERIALIZED (SELECT DISTINCT unit_id FROM unit_members)
 
 
1630
  SELECT um.unit_id FROM unit_ids um
1631
  WHERE 1 = 1${cutoffSql}${authorSql}${publicationSql}`,
1632
- params: [...cutoffParams, ...authorParams],
 
 
 
 
1633
  };
1634
  }
1635
 
 
1536
  }
1537
 
1538
  type UnitSelection = {
1539
+ unitIds?: readonly string[] | undefined;
1540
  authorIds?: readonly string[] | undefined;
1541
  labels?: readonly string[] | undefined;
1542
  labelMode?: "any" | "all" | undefined;
 
1545
  };
1546
 
1547
  type EligibleUnitOptions = UnitSelection & {
 
1548
  taxonomyVersion: number;
1549
  contractHash: string;
1550
  };
 
1626
  const cutoffParams = options.cutoff === undefined ? [] : [options.cutoff];
1627
  const authorParams = options.authorIds ?? [];
1628
  return {
1629
+ sql: `WITH unit_ids AS MATERIALIZED (SELECT DISTINCT unit_id FROM unit_members${
1630
+ options.unitIds === undefined ? "" : " WHERE unit_id IN (SELECT value FROM json_each(?))"
1631
+ })
1632
  SELECT um.unit_id FROM unit_ids um
1633
  WHERE 1 = 1${cutoffSql}${authorSql}${publicationSql}`,
1634
+ params: [
1635
+ ...(options.unitIds === undefined ? [] : [JSON.stringify(options.unitIds)]),
1636
+ ...cutoffParams,
1637
+ ...authorParams,
1638
+ ],
1639
  };
1640
  }
1641
 
space/tests/consumer-coverage-update.test.ts CHANGED
@@ -156,6 +156,36 @@ describe("source coverage maintenance", () => {
156
  expect(plan.some((line) => line.includes("segment_key=? AND observation_id=?"))).toBe(false);
157
  });
158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  it("does zero body reads for twenty no-op polls and twenty harmless revisions, including restart and replay", async () => {
160
  await f.postMany(Array.from({ length: 250 }, (_, n) => consumerTweet(String(100 + n))));
161
  let source = await finish(BOOTSTRAP);
 
156
  expect(plan.some((line) => line.includes("segment_key=? AND observation_id=?"))).toBe(false);
157
  });
158
 
159
+ it("uses the indexed current selection for both semantic coverage clocks", async () => {
160
+ await f.postMany([
161
+ consumerTweet("100"),
162
+ consumerTweet("200", {
163
+ conversation_id: "100",
164
+ captured_at: "2026-09-06T05:00:00.000Z",
165
+ is_subscriber_only: "yes",
166
+ }),
167
+ consumerTweet("300", {
168
+ conversation_id: "100",
169
+ captured_at: "2026-09-06T04:00:00.000Z",
170
+ }),
171
+ consumerTweet("400", { captured_at: "2026-09-06T01:00:00.000Z" }),
172
+ consumerTweet("500", { author: { id: "22", username: "other" } }),
173
+ ]);
174
+ const source = await finish(BOOTSTRAP);
175
+ expect(source.observations_through).toBe("2026-09-06T01:00:00.000Z");
176
+ const pinned = await context(source.cursor);
177
+ const database = f.index.store.database;
178
+ const plan = consumerQueryPlan(
179
+ database,
180
+ /WITH candidates AS MATERIALIZED|AS latest FROM selected_posts/u,
181
+ );
182
+ const coverage = updateConsumerCoverage(database, pinned);
183
+ expect(coverage.completeThrough).toBe(source.complete_through);
184
+ expect(coverage.observationsThrough).toBe(source.observations_through);
185
+ expect(plan.some((line) => line.includes("idx_consumer_post_units_author"))).toBe(true);
186
+ expect(plan.some((line) => line.includes("observation_sources"))).toBe(false);
187
+ });
188
+
189
  it("does zero body reads for twenty no-op polls and twenty harmless revisions, including restart and replay", async () => {
190
  await f.postMany(Array.from({ length: 250 }, (_, n) => consumerTweet(String(100 + n))));
191
  let source = await finish(BOOTSTRAP);