expand ci quality gates
All checks were successful
CI / Validate (push) Successful in 1m13s

This commit is contained in:
dirtydishes 2026-05-30 02:34:28 -04:00
parent 65139bf8d0
commit 44431c4e66
71 changed files with 2262 additions and 1173 deletions

View file

@ -465,8 +465,7 @@ const parseCandleParams = (
const endTs = params.end_ts ?? Date.now();
const limit = params.limit ?? env.REST_DEFAULT_LIMIT;
const startTs =
params.start_ts ?? Math.max(0, Math.floor(endTs - params.interval_ms * limit));
const startTs = params.start_ts ?? Math.max(0, Math.floor(endTs - params.interval_ms * limit));
const rangeStart = Math.min(startTs, endTs);
const rangeEnd = Math.max(startTs, endTs);
@ -482,7 +481,13 @@ const parseCandleParams = (
const parseCandleReplayParams = (
url: URL
): { underlyingId: string; intervalMs: number; afterTs: number; afterSeq: number; limit: number } => {
): {
underlyingId: string;
intervalMs: number;
afterTs: number;
afterSeq: number;
limit: number;
} => {
const params = candleReplaySchema.parse({
underlying_id: url.searchParams.get("underlying_id") ?? undefined,
interval_ms: url.searchParams.get("interval_ms") ?? undefined,
@ -601,7 +606,10 @@ const matchesScopedOptionSubscription = (
print: { underlying_id?: string; option_contract_id: string },
subscription: Extract<LiveSubscription, { channel: "options" }>
): boolean => {
if (subscription.option_contract_id && subscription.option_contract_id !== print.option_contract_id) {
if (
subscription.option_contract_id &&
subscription.option_contract_id !== print.option_contract_id
) {
return false;
}
if (subscription.underlying_ids?.length) {
@ -693,8 +701,7 @@ const run = async () => {
env.OPTIONS_INGEST_ADAPTER,
env.EQUITIES_INGEST_ADAPTER
);
const syntheticBackendDisabledReason =
getSyntheticBackendDisabledReason(syntheticBackendMode);
const syntheticBackendDisabledReason = getSyntheticBackendDisabledReason(syntheticBackendMode);
const syntheticControlKv = await openSyntheticControlKv(js);
let syntheticControl = await ensureSyntheticControlState(syntheticControlKv);
const syntheticProfileHits = createRollingSyntheticProfileHits();
@ -899,11 +906,7 @@ const run = async () => {
}
}
const subscribeWithReset = async <T>(
subject: string,
stream: string,
durableName: string
) => {
const subscribeWithReset = async <T>(subject: string, stream: string, durableName: string) => {
const opts = buildDurableConsumer(durableName);
applyDeliverPolicy(opts, env.API_DELIVER_POLICY);
try {
@ -924,7 +927,8 @@ const run = async () => {
try {
await jsm.consumers.delete(stream, durableName);
} catch (deleteError) {
const deleteMessage = deleteError instanceof Error ? deleteError.message : String(deleteError);
const deleteMessage =
deleteError instanceof Error ? deleteError.message : String(deleteError);
if (!deleteMessage.includes("not found")) {
logger.warn("failed to delete jetstream consumer", {
durable: durableName,
@ -1023,8 +1027,12 @@ const run = async () => {
}
const matchingSubscriptions =
subscription.channel === "options" || subscription.channel === "flow" || subscription.channel === "equities"
? [...subscriptionDefinitions.entries()].filter(([, candidate]) => candidate.channel === subscription.channel)
subscription.channel === "options" ||
subscription.channel === "flow" ||
subscription.channel === "equities"
? [...subscriptionDefinitions.entries()].filter(
([, candidate]) => candidate.channel === subscription.channel
)
: [[getSubscriptionKey(subscription), subscription] as const];
if (matchingSubscriptions.length === 0) {
@ -1032,8 +1040,12 @@ const run = async () => {
}
const optionItem = ingestChannel === "options" ? (item as OptionPrint) : null;
const equityItem = ingestChannel === "equities" ? (item as Parameters<typeof matchesScopedEquitySubscription>[0]) : null;
const flowItem = ingestChannel === "flow" ? (item as Parameters<typeof matchesFlowPacketFilters>[0]) : null;
const equityItem =
ingestChannel === "equities"
? (item as Parameters<typeof matchesScopedEquitySubscription>[0])
: null;
const flowItem =
ingestChannel === "flow" ? (item as Parameters<typeof matchesFlowPacketFilters>[0]) : null;
let matchedSubscriptions = 0;
for (const [key, candidate] of matchingSubscriptions) {
@ -1315,9 +1327,7 @@ const run = async () => {
},
control: syntheticBackendMode === "synthetic" ? syntheticControl : null,
derived,
...(syntheticBackendDisabledReason
? { disabled_reason: syntheticBackendDisabledReason }
: {})
...(syntheticBackendDisabledReason ? { disabled_reason: syntheticBackendDisabledReason } : {})
};
};
@ -1385,11 +1395,7 @@ const run = async () => {
syntheticControl = await writeSyntheticControlState(syntheticControlKv, payload);
return jsonResponse({
control: syntheticControl,
derived: buildSyntheticDerivedStatus(
Date.now(),
syntheticControl,
syntheticProfileHits
)
derived: buildSyntheticDerivedStatus(Date.now(), syntheticControl, syntheticProfileHits)
});
} catch (error) {
return jsonResponse(
@ -1436,7 +1442,13 @@ const run = async () => {
if (req.method === "GET" && url.pathname === "/prints/equities/range") {
try {
const { underlyingId, startTs, endTs, limit } = parseEquityPrintRangeParams(url);
const data = await fetchEquityPrintsRange(clickhouse, underlyingId, startTs, endTs, limit);
const data = await fetchEquityPrintsRange(
clickhouse,
underlyingId,
startTs,
endTs,
limit
);
return jsonResponse({ data });
} catch (error) {
return jsonResponse(
@ -1566,7 +1578,9 @@ const run = async () => {
source,
storageFilters
);
return jsonResponse(buildHistoryResponse(data, (item) => ({ ts: item.ts, seq: item.seq })));
return jsonResponse(
buildHistoryResponse(data, (item) => ({ ts: item.ts, seq: item.seq }))
);
} catch (error) {
return jsonResponse(
{
@ -1986,7 +2000,9 @@ const run = async () => {
const payload =
typeof message === "string"
? message
: new TextDecoder().decode(message instanceof Uint8Array ? message : new Uint8Array(message));
: new TextDecoder().decode(
message instanceof Uint8Array ? message : new Uint8Array(message)
);
const parsed = LiveClientMessageSchema.parse(JSON.parse(payload));
if (parsed.op === "ping") {
sendLiveMessage(socket, {

View file

@ -165,11 +165,21 @@ const parseGenericLimitFallback = (env: NodeJS.ProcessEnv, fallback: number): nu
return Math.max(MIN_GENERIC_LIMIT, Math.min(MAX_GENERIC_LIMIT, Math.floor(parsed)));
};
export const resolveGenericLiveLimits = (env: NodeJS.ProcessEnv = process.env): GenericLiveLimits => {
export const resolveGenericLiveLimits = (
env: NodeJS.ProcessEnv = process.env
): GenericLiveLimits => {
const liveLimitDefault = parseGenericLimitFallback(env, DEFAULT_GENERIC_LIMIT);
return {
options: parseGenericLimit(env, "options", env.LIVE_LIMIT_DEFAULT ? liveLimitDefault : DEFAULT_LIVE_LIMITS.options),
nbbo: parseGenericLimit(env, "nbbo", env.LIVE_LIMIT_DEFAULT ? liveLimitDefault : DEFAULT_LIVE_LIMITS.nbbo),
options: parseGenericLimit(
env,
"options",
env.LIVE_LIMIT_DEFAULT ? liveLimitDefault : DEFAULT_LIVE_LIMITS.options
),
nbbo: parseGenericLimit(
env,
"nbbo",
env.LIVE_LIMIT_DEFAULT ? liveLimitDefault : DEFAULT_LIVE_LIMITS.nbbo
),
equities: parseGenericLimit(
env,
"equities",
@ -185,7 +195,11 @@ export const resolveGenericLiveLimits = (env: NodeJS.ProcessEnv = process.env):
"equity-joins",
env.LIVE_LIMIT_DEFAULT ? liveLimitDefault : DEFAULT_LIVE_LIMITS["equity-joins"]
),
flow: parseGenericLimit(env, "flow", env.LIVE_LIMIT_DEFAULT ? liveLimitDefault : DEFAULT_LIVE_LIMITS.flow),
flow: parseGenericLimit(
env,
"flow",
env.LIVE_LIMIT_DEFAULT ? liveLimitDefault : DEFAULT_LIVE_LIMITS.flow
),
"smart-money": parseGenericLimit(
env,
"smart-money",
@ -196,13 +210,21 @@ export const resolveGenericLiveLimits = (env: NodeJS.ProcessEnv = process.env):
"classifier-hits",
env.LIVE_LIMIT_DEFAULT ? liveLimitDefault : DEFAULT_LIVE_LIMITS["classifier-hits"]
),
alerts: parseGenericLimit(env, "alerts", env.LIVE_LIMIT_DEFAULT ? liveLimitDefault : DEFAULT_LIVE_LIMITS.alerts),
alerts: parseGenericLimit(
env,
"alerts",
env.LIVE_LIMIT_DEFAULT ? liveLimitDefault : DEFAULT_LIVE_LIMITS.alerts
),
"inferred-dark": parseGenericLimit(
env,
"inferred-dark",
env.LIVE_LIMIT_DEFAULT ? liveLimitDefault : DEFAULT_LIVE_LIMITS["inferred-dark"]
),
news: parseGenericLimit(env, "news", env.LIVE_LIMIT_DEFAULT ? liveLimitDefault : DEFAULT_LIVE_LIMITS.news)
news: parseGenericLimit(
env,
"news",
env.LIVE_LIMIT_DEFAULT ? liveLimitDefault : DEFAULT_LIVE_LIMITS.news
)
};
};
@ -227,12 +249,18 @@ const extractFreshnessTs = (channel: LiveGenericChannel, item: any): number | nu
export const resolveLiveStateConfig = (env: NodeJS.ProcessEnv = process.env): LiveStateConfig => ({
limits: resolveGenericLiveLimits(env),
scopedCacheMaxKeys: parsePositiveInt(env.LIVE_SCOPED_CACHE_MAX_KEYS, DEFAULT_SCOPED_CACHE_MAX_KEYS),
scopedCacheMaxKeys: parsePositiveInt(
env.LIVE_SCOPED_CACHE_MAX_KEYS,
DEFAULT_SCOPED_CACHE_MAX_KEYS
),
redisFlushIntervalMs: parsePositiveInt(
env.LIVE_REDIS_FLUSH_INTERVAL_MS,
DEFAULT_REDIS_FLUSH_INTERVAL_MS
),
redisFlushMaxItems: parsePositiveInt(env.LIVE_REDIS_FLUSH_MAX_ITEMS, DEFAULT_REDIS_FLUSH_MAX_ITEMS)
redisFlushMaxItems: parsePositiveInt(
env.LIVE_REDIS_FLUSH_MAX_ITEMS,
DEFAULT_REDIS_FLUSH_MAX_ITEMS
)
});
const parsePositiveInt = (value: string | undefined, fallback: number): number => {
const parsed = Number(value);
@ -242,10 +270,7 @@ const parsePositiveInt = (value: string | undefined, fallback: number): number =
return Math.max(1, Math.floor(parsed));
};
type RedisLike = Pick<
RedisClientType,
"isOpen" | "lRange" | "lPush" | "lTrim" | "hGet" | "hSet"
>;
type RedisLike = Pick<RedisClientType, "isOpen" | "lRange" | "lPush" | "lTrim" | "hGet" | "hSet">;
const parseCursor = (value: string | null): Cursor | null => {
if (!value) {
@ -259,7 +284,9 @@ const parseCursor = (value: string | null): Cursor | null => {
}
};
const getGenericConfig = (limits: GenericLiveLimits): {
const getGenericConfig = (
limits: GenericLiveLimits
): {
[K in LiveGenericChannel]: GenericFeedConfig;
} => ({
options: {
@ -365,7 +392,7 @@ const parseJsonList = <T>(payloads: string[], parse: (value: unknown) => T): T[]
return items;
};
const compareCursors = (a: Cursor, b: Cursor): number => (b.ts - a.ts) || (b.seq - a.seq);
const compareCursors = (a: Cursor, b: Cursor): number => b.ts - a.ts || b.seq - a.seq;
const sortGenericItems = <T>(items: T[], cursorOf: (item: T) => Cursor): T[] =>
[...items].sort((a, b) => compareCursors(cursorOf(a), cursorOf(b)));
@ -480,7 +507,10 @@ const matchesScopedOptionSnapshot = (
return false;
}
if (subscription.option_contract_id && item.option_contract_id !== subscription.option_contract_id) {
if (
subscription.option_contract_id &&
item.option_contract_id !== subscription.option_contract_id
) {
return false;
}
@ -529,11 +559,8 @@ const candleCursorField = (underlyingId: string, intervalMs: number): string =>
const overlayRedisKey = (underlyingId: string): string => `live:equity-overlay:${underlyingId}`;
const overlayCursorField = (underlyingId: string): string => `equities:${underlyingId}`;
const dropMatchingCursor = <T>(
items: T[],
target: Cursor,
cursorOf: (item: T) => Cursor
): T[] => items.filter((item) => compareCursors(cursorOf(item), target) !== 0);
const dropMatchingCursor = <T>(items: T[], target: Cursor, cursorOf: (item: T) => Cursor): T[] =>
items.filter((item) => compareCursors(cursorOf(item), target) !== 0);
const insertNewestFirst = <T>(
items: T[],
@ -676,7 +703,13 @@ export class LiveStateManager {
this.pendingRedisWrites.clear();
for (const write of writes) {
await this.persistList(write.listKey, write.cursorField, write.items, write.limit, write.cursor);
await this.persistList(
write.listKey,
write.cursorField,
write.items,
write.limit,
write.cursor
);
this.stats.redisFlushCount += 1;
this.stats.redisFlushItems += write.items.length;
metrics.count("api.live.redis_flush_count", 1);
@ -726,7 +759,12 @@ export class LiveStateManager {
}
}
private updateFreshnessMetric(listKey: string, channel: LiveChannel, item: unknown, now = Date.now()): void {
private updateFreshnessMetric(
listKey: string,
channel: LiveChannel,
item: unknown,
now = Date.now()
): void {
const ts =
channel === "equity-candles" || channel === "equity-overlay"
? typeof (item as { ts?: unknown })?.ts === "number"
@ -784,12 +822,22 @@ export class LiveStateManager {
config.cursorField,
parseCursor(await this.redis.hGet(CURSOR_HASH_KEY, config.cursorField))
);
await this.persistList(config.redisKey, config.cursorField, cached, config.limit, this.genericCursors.get(config.cursorField) ?? null);
await this.persistList(
config.redisKey,
config.cursorField,
cached,
config.limit,
this.genericCursors.get(config.cursorField) ?? null
);
return;
}
}
const fresh = normalizeGenericItems(channel, await config.fetchRecent(this.clickhouse, config.limit), config);
const fresh = normalizeGenericItems(
channel,
await config.fetchRecent(this.clickhouse, config.limit),
config
);
this.stats.genericHydrateFromClickHouse += 1;
this.stats.cacheDepthByKey.set(config.redisKey, fresh.length);
this.genericItems.set(channel, fresh);
@ -806,7 +854,8 @@ export class LiveStateManager {
case "options": {
const config = this.generic.options;
const limit = snapshotLimitFor(subscription, config.limit);
const scoped = Boolean(subscription.underlying_ids?.length) || Boolean(subscription.option_contract_id);
const scoped =
Boolean(subscription.underlying_ids?.length) || Boolean(subscription.option_contract_id);
if (subscription.filters?.view === "raw" || scoped) {
const cached = (this.genericItems.get("options") ?? [])
.filter((entry) => matchesScopedOptionSnapshot(entry, subscription))
@ -815,8 +864,16 @@ export class LiveStateManager {
if (cached.length < limit) {
this.stats.scopedClickHouseSnapshots += 1;
const storageFilters = buildOptionSnapshotFilters(subscription);
const backfill = await fetchRecentOptionPrints(this.clickhouse, limit, undefined, storageFilters);
items = mergeSnapshotBackfill(cached, backfill, limit, (entry) => ({ ts: entry.ts, seq: entry.seq }));
const backfill = await fetchRecentOptionPrints(
this.clickhouse,
limit,
undefined,
storageFilters
);
items = mergeSnapshotBackfill(cached, backfill, limit, (entry) => ({
ts: entry.ts,
seq: entry.seq
}));
}
return {
subscription,
@ -942,7 +999,11 @@ export class LiveStateManager {
this.candleItems.set(key, nextState.items);
this.candleCursors.set(cursorField, cursor);
this.touchAccess(this.candleAccess, key);
this.evictScopedCachesIfNeeded(this.candleItems as Map<string, unknown[]>, this.candleCursors, this.candleAccess);
this.evictScopedCachesIfNeeded(
this.candleItems as Map<string, unknown[]>,
this.candleCursors,
this.candleAccess
);
if (nextState.outOfOrder) {
this.stats.outOfOrderEvents += 1;
metrics.count("api.live.out_of_order_events", 1);
@ -968,7 +1029,11 @@ export class LiveStateManager {
this.overlayItems.set(key, nextState.items);
this.overlayCursors.set(cursorField, cursor);
this.touchAccess(this.overlayAccess, key);
this.evictScopedCachesIfNeeded(this.overlayItems as Map<string, unknown[]>, this.overlayCursors, this.overlayAccess);
this.evictScopedCachesIfNeeded(
this.overlayItems as Map<string, unknown[]>,
this.overlayCursors,
this.overlayAccess
);
if (nextState.outOfOrder) {
this.stats.outOfOrderEvents += 1;
metrics.count("api.live.out_of_order_events", 1);
@ -991,10 +1056,19 @@ export class LiveStateManager {
const nextState =
channel === "nbbo"
? {
items: normalizeGenericItems(channel, [parsed, ...(this.genericItems.get(channel) ?? [])], config),
items: normalizeGenericItems(
channel,
[parsed, ...(this.genericItems.get(channel) ?? [])],
config
),
outOfOrder: false
}
: insertNewestFirst(this.genericItems.get(channel) ?? [], parsed, config.cursor, config.limit);
: insertNewestFirst(
this.genericItems.get(channel) ?? [],
parsed,
config.cursor,
config.limit
);
if (nextState.outOfOrder) {
this.stats.outOfOrderEvents += 1;
@ -1007,7 +1081,13 @@ export class LiveStateManager {
if (nextState.items.length > 0) {
this.updateFreshnessMetric(config.redisKey, channel, nextState.items[0]);
}
this.queueRedisWrite(config.redisKey, config.cursorField, nextState.items, config.limit, cursor);
this.queueRedisWrite(
config.redisKey,
config.cursorField,
nextState.items,
config.limit,
cursor
);
return cursor;
}
}
@ -1022,18 +1102,34 @@ export class LiveStateManager {
if (cached.length > 0) {
this.candleItems.set(key, cached);
this.touchAccess(this.candleAccess, key);
this.evictScopedCachesIfNeeded(this.candleItems as Map<string, unknown[]>, this.candleCursors, this.candleAccess);
this.evictScopedCachesIfNeeded(
this.candleItems as Map<string, unknown[]>,
this.candleCursors,
this.candleAccess
);
this.stats.cacheDepthByKey.set(key, cached.length);
this.updateFreshnessMetric(key, "equity-candles", cached[0]);
this.candleCursors.set(cursorField, parseCursor(await this.redis.hGet(CURSOR_HASH_KEY, cursorField)));
this.candleCursors.set(
cursorField,
parseCursor(await this.redis.hGet(CURSOR_HASH_KEY, cursorField))
);
return;
}
}
const fresh = await fetchRecentEquityCandles(this.clickhouse, underlyingId, intervalMs, CHART_LIMITS.candles);
const fresh = await fetchRecentEquityCandles(
this.clickhouse,
underlyingId,
intervalMs,
CHART_LIMITS.candles
);
this.candleItems.set(key, fresh);
this.touchAccess(this.candleAccess, key);
this.evictScopedCachesIfNeeded(this.candleItems as Map<string, unknown[]>, this.candleCursors, this.candleAccess);
this.evictScopedCachesIfNeeded(
this.candleItems as Map<string, unknown[]>,
this.candleCursors,
this.candleAccess
);
this.stats.cacheDepthByKey.set(key, fresh.length);
if (fresh.length > 0) {
this.updateFreshnessMetric(key, "equity-candles", fresh[0]);
@ -1052,10 +1148,17 @@ export class LiveStateManager {
if (cached.length > 0) {
this.overlayItems.set(key, cached);
this.touchAccess(this.overlayAccess, key);
this.evictScopedCachesIfNeeded(this.overlayItems as Map<string, unknown[]>, this.overlayCursors, this.overlayAccess);
this.evictScopedCachesIfNeeded(
this.overlayItems as Map<string, unknown[]>,
this.overlayCursors,
this.overlayAccess
);
this.stats.cacheDepthByKey.set(key, cached.length);
this.updateFreshnessMetric(key, "equity-overlay", cached[0]);
this.overlayCursors.set(cursorField, parseCursor(await this.redis.hGet(CURSOR_HASH_KEY, cursorField)));
this.overlayCursors.set(
cursorField,
parseCursor(await this.redis.hGet(CURSOR_HASH_KEY, cursorField))
);
return;
}
}
@ -1065,7 +1168,11 @@ export class LiveStateManager {
);
this.overlayItems.set(key, fresh);
this.touchAccess(this.overlayAccess, key);
this.evictScopedCachesIfNeeded(this.overlayItems as Map<string, unknown[]>, this.overlayCursors, this.overlayAccess);
this.evictScopedCachesIfNeeded(
this.overlayItems as Map<string, unknown[]>,
this.overlayCursors,
this.overlayAccess
);
this.stats.cacheDepthByKey.set(key, fresh.length);
if (fresh.length > 0) {
this.updateFreshnessMetric(key, "equity-overlay", fresh[0]);

View file

@ -83,11 +83,7 @@ export const buildSyntheticDerivedStatus = (
session_phase: session.session_phase,
regime: session.regime,
focus_symbols: session.focus_symbols,
profile_hit_counts: getSyntheticProfileHitCounts(
state,
now,
control.coverage_window_minutes
),
profile_hit_counts: getSyntheticProfileHitCounts(state, now, control.coverage_window_minutes),
coverage_window_minutes: control.coverage_window_minutes
});
};

View file

@ -3,7 +3,9 @@ import { isAlertContextPath, parseAlertContextTraceIdPath } from "../src/alert-c
describe("alert context route helpers", () => {
it("extracts a valid alert trace id from the context endpoint path", () => {
expect(parseAlertContextTraceIdPath("/flow/alerts/alert%3Actx%2Fone/context")).toBe("alert:ctx/one");
expect(parseAlertContextTraceIdPath("/flow/alerts/alert%3Actx%2Fone/context")).toBe(
"alert:ctx/one"
);
});
it("returns null for unrelated alert paths", () => {

View file

@ -9,9 +9,7 @@ import {
shouldFanoutLiveEvent
} from "../src/live";
const makeClickHouse = (
queryResolver?: (query: string) => unknown[]
): ClickHouseClient =>
const makeClickHouse = (queryResolver?: (query: string) => unknown[]): ClickHouseClient =>
({
exec: async () => {},
insert: async () => {},
@ -149,22 +147,18 @@ describe("LiveStateManager", () => {
it("trims generic windows to configured per-channel limits", async () => {
const redis = makeRedis();
const now = Date.now();
const manager = new LiveStateManager(
makeClickHouse(),
redis as never,
{
options: 10000,
nbbo: 10000,
equities: 10000,
"equity-quotes": 10000,
"equity-joins": 10000,
flow: 2,
"smart-money": 10000,
"classifier-hits": 10000,
alerts: 10000,
"inferred-dark": 10000
}
);
const manager = new LiveStateManager(makeClickHouse(), redis as never, {
options: 10000,
nbbo: 10000,
equities: 10000,
"equity-quotes": 10000,
"equity-joins": 10000,
flow: 2,
"smart-money": 10000,
"classifier-hits": 10000,
alerts: 10000,
"inferred-dark": 10000
});
await manager.ingest("flow", {
source_ts: now,
@ -503,18 +497,15 @@ describe("LiveStateManager", () => {
manager.getSnapshot({ channel: "flow" })
]);
expect((optionsSnapshot.items as Array<{ trace_id: string }>).map((item) => item.trace_id)).toEqual([
"opt-fresh",
"opt-stale"
]);
expect((nbboSnapshot.items as Array<{ trace_id: string }>).map((item) => item.trace_id)).toEqual([
"nbbo-fresh",
"nbbo-stale"
]);
expect((equitiesSnapshot.items as Array<{ trace_id: string }>).map((item) => item.trace_id)).toEqual([
"eq-fresh",
"eq-stale"
]);
expect(
(optionsSnapshot.items as Array<{ trace_id: string }>).map((item) => item.trace_id)
).toEqual(["opt-fresh", "opt-stale"]);
expect(
(nbboSnapshot.items as Array<{ trace_id: string }>).map((item) => item.trace_id)
).toEqual(["nbbo-fresh", "nbbo-stale"]);
expect(
(equitiesSnapshot.items as Array<{ trace_id: string }>).map((item) => item.trace_id)
).toEqual(["eq-fresh", "eq-stale"]);
expect((flowSnapshot.items as Array<{ id: string }>).map((item) => item.id)).toEqual([
"flow-fresh",
"flow-stale"
@ -699,10 +690,9 @@ describe("LiveStateManager", () => {
option_contract_id: "AAPL-2025-01-17-200-C"
});
expect((snapshot.items as Array<{ trace_id: string }>).map((item) => item.trace_id).slice(0, 2)).toEqual([
"opt-hot",
"opt-backfill"
]);
expect(
(snapshot.items as Array<{ trace_id: string }>).map((item) => item.trace_id).slice(0, 2)
).toEqual(["opt-hot", "opt-backfill"]);
});
it("seeds scoped equity snapshots from clickhouse rows older than 24h", async () => {
@ -806,12 +796,12 @@ describe("LiveStateManager", () => {
manager.getSnapshot({ channel: "flow" })
]);
expect((optionsSnapshot.items as Array<{ trace_id: string }>).map((item) => item.trace_id)).toEqual([
"opt-retained"
]);
expect((equitiesSnapshot.items as Array<{ trace_id: string }>).map((item) => item.trace_id)).toEqual([
"eq-retained"
]);
expect(
(optionsSnapshot.items as Array<{ trace_id: string }>).map((item) => item.trace_id)
).toEqual(["opt-retained"]);
expect(
(equitiesSnapshot.items as Array<{ trace_id: string }>).map((item) => item.trace_id)
).toEqual(["eq-retained"]);
expect((flowSnapshot.items as Array<{ id: string }>).map((item) => item.id)).toEqual([
"flow-retained"
]);
@ -1047,7 +1037,10 @@ describe("LiveStateManager", () => {
});
it("tracks generic cache and scoped clickhouse snapshot sources separately", async () => {
const manager = new LiveStateManager(makeClickHouse(() => []), null);
const manager = new LiveStateManager(
makeClickHouse(() => []),
null
);
const now = Date.now();
await manager.ingest("options", {
@ -1075,7 +1068,10 @@ describe("LiveStateManager", () => {
});
it("keeps backend channel health healthy when a scoped query is quiet", async () => {
const manager = new LiveStateManager(makeClickHouse(() => []), null);
const manager = new LiveStateManager(
makeClickHouse(() => []),
null
);
const now = Date.now();
await manager.ingest("options", {
@ -1098,7 +1094,9 @@ describe("LiveStateManager", () => {
expect(quietSnapshot.items).toEqual([]);
expect(manager.getHotChannelHealth().options.healthy).toBe(true);
expect(manager.getStatsSnapshot().freshnessAgeMsByKey[HOT_LIVE_REDIS_KEYS.options]).toBeLessThanOrEqual(50);
expect(
manager.getStatsSnapshot().freshnessAgeMsByKey[HOT_LIVE_REDIS_KEYS.options]
).toBeLessThanOrEqual(50);
});
it("exposes freshness helper for feed status", () => {