@phosphorco/bb-thread-metrics 0.1.0 → 0.2.1

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.
package/dist/index.js CHANGED
@@ -1,28 +1,562 @@
1
- // src/index.ts
1
+ // src/recent.ts
2
2
  import { z } from "zod";
3
3
  import { UsageVectorSchema } from "@phosphorco/bb-usage-algebra/vector";
4
+ var RECENT_LIMITS = Object.freeze({ rows: 1000, bytes: 4 * 1024 * 1024, rowBytes: 8 * 1024, inputBytes: 1024, providers: 32, windowMs: 3 * 24 * 60 * 60 * 1000 });
5
+ var id = z.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/);
6
+ var revision = z.string().min(1).max(256);
7
+ var time = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
8
+ var coverage = z.enum(["complete", "partial", "unknown"]);
9
+ var rank = z.number().finite().min(0).max(1).nullable();
10
+ var count = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
11
+ var windowShape = (value) => z.object({ m20: value, h24: value, d7: value, lifetime: value }).strip();
12
+ var reason = z.string().min(1).max(64);
13
+ var reasons = z.array(reason).max(16);
14
+ var recentDollarSchema = z.discriminatedUnion("kind", [
15
+ z.object({ kind: z.literal("estimate"), amount: z.number().finite().nonnegative() }).strip(),
16
+ z.object({ kind: z.literal("floor"), amount: z.number().finite().nonnegative() }).strip(),
17
+ z.object({ kind: z.literal("unavailable"), amount: z.null() }).strip()
18
+ ]);
19
+ var recentWindowsSchema = windowShape(recentDollarSchema);
20
+ var flags = windowShape(z.boolean());
21
+ var recentAttributionSchema = z.object({ unknownProvider: recentWindowsSchema, attributionComplete: flags, unpriced: flags }).strip();
22
+ var recentPresentationSchema = z.object({ title: z.string().trim().min(1).max(512).nullable(), navigation: z.discriminatedUnion("kind", [
23
+ z.object({ kind: z.literal("personal") }).strip(),
24
+ z.object({ kind: z.literal("project"), projectId: id }).strip()
25
+ ]).nullable() }).strip();
26
+ var recentApproximationSchema = z.object({ approximate: z.boolean(), reasons: z.array(z.enum(["closest-model", "provider-default", "model-attribution", "bucket-boundary", "missing-price", "source-gap"])).max(6) }).strip().superRefine((v, c) => {
27
+ if (!v.approximate && v.reasons.length)
28
+ c.addIssue({ code: "custom", message: "Approximation reasons require approximate flag" });
29
+ });
30
+ var provider = z.object({ providerId: id, windows: recentWindowsSchema, approximation: recentApproximationSchema }).strip();
31
+ var recentTokenValueSchema = z.discriminatedUnion("kind", [
32
+ z.object({ kind: z.literal("estimate"), value: UsageVectorSchema }).strip(),
33
+ z.object({ kind: z.literal("floor"), value: UsageVectorSchema }).strip(),
34
+ z.object({ kind: z.literal("unavailable"), value: z.null() }).strip()
35
+ ]);
36
+ var recentTokenWindowsSchema = windowShape(recentTokenValueSchema);
37
+ var recentUsageSchema = z.object({
38
+ windows: recentTokenWindowsSchema,
39
+ providers: z.array(z.object({ providerId: id, windows: recentTokenWindowsSchema }).strip()).max(RECENT_LIMITS.providers),
40
+ omittedProviders: count,
41
+ descendants: z.object({ count, exact: z.boolean() }).strip()
42
+ }).strip();
43
+ var recentScopeSchema = z.object({
44
+ memberCount: count,
45
+ coverage,
46
+ coverageReasons: reasons,
47
+ priceVersion: revision,
48
+ approximation: recentApproximationSchema,
49
+ usage: recentUsageSchema.optional(),
50
+ attribution: recentAttributionSchema.optional(),
51
+ windows: recentWindowsSchema,
52
+ scores: recentWindowsSchema,
53
+ providers: z.array(provider).max(RECENT_LIMITS.providers),
54
+ omittedProviders: count,
55
+ dollarRanks: z.object({ m20: rank, h24: rank, d7: rank }).strip(),
56
+ scoreRanks: z.object({ m20: rank, h24: rank, d7: rank }).strip()
57
+ }).strip().superRefine((v, c) => {
58
+ if (new Set(v.providers.map((x) => x.providerId)).size !== v.providers.length)
59
+ c.addIssue({ code: "custom", message: "Duplicate providers" });
60
+ if (v.omittedProviders && v.coverage === "complete")
61
+ c.addIssue({ code: "custom", message: "Omitted providers require partial coverage" });
62
+ if (v.usage) {
63
+ const u = v.usage, fail = (message) => c.addIssue({ code: "custom", path: ["usage"], message });
64
+ const providers = new Set(v.providers.map((p) => p.providerId));
65
+ if (u.providers.length !== providers.size || new Set(u.providers.map((p) => p.providerId)).size !== u.providers.length || u.providers.some((p) => !providers.has(p.providerId)) || u.omittedProviders !== v.omittedProviders)
66
+ fail("Token provider attribution mismatch");
67
+ if (u.descendants.count !== Math.max(0, v.memberCount - 1))
68
+ fail("Descendant/member count mismatch");
69
+ if (u.descendants.exact && v.coverageReasons.some((reason2) => ["member-cap", "depth-cap", "edge-cap"].includes(reason2)))
70
+ fail("Membership caps cannot carry an exact descendant count");
71
+ const near = (a, b) => Math.abs(a - b) <= 4 * Number.EPSILON * Math.max(a, b);
72
+ for (const w of ["m20", "h24", "d7", "lifetime"]) {
73
+ const total = u.windows[w], known = u.providers.filter((p) => p.windows[w].value !== null), all = !u.omittedProviders && known.length === u.providers.length;
74
+ if (total.kind === "estimate" && (u.omittedProviders > 0 || u.providers.some((p) => p.windows[w].kind !== "estimate")))
75
+ fail("Incomplete provider tokens cannot carry an aggregate estimate");
76
+ for (const k of ["inputUncached", "cacheRead", "cacheWrite", "output"]) {
77
+ const sum = known.reduce((n, p) => n + p.windows[w].value[k], 0), value = total.value?.[k];
78
+ if (!Number.isFinite(sum) || value === undefined && sum > 0 || value !== undefined && (sum > value && !near(sum, value) || all && !near(sum, value)))
79
+ fail("Token provider conservation mismatch");
80
+ }
81
+ }
82
+ }
83
+ });
84
+ var recentSnapshotInputSchema = z.object({ ifRevision: revision.optional() }).strict();
85
+ var thresholds = windowShape(z.number().finite().min(0).max(1e6));
86
+ var weights = z.record(id, z.number().finite().min(0).max(3)).refine((v) => Object.keys(v).length <= RECENT_LIMITS.providers, "Too many provider weights");
87
+ var configBase = { schemaVersion: z.literal(2), providerWeights: weights };
88
+ var scoringConfigV2Schema = z.discriminatedUnion("mode", [
89
+ z.object({ ...configBase, mode: z.literal("percentile"), gates: z.object({ m20: z.number().finite().min(0).max(1), h24: z.number().finite().min(0).max(1), d7: z.number().finite().min(0).max(1) }).strip() }).strip(),
90
+ z.object({ ...configBase, mode: z.literal("absolute"), thresholds, lifetimeEnabled: z.boolean().optional() }).strip()
91
+ ]);
92
+ var DEFAULT_SCORING_CONFIG_V2 = Object.freeze({ schemaVersion: 2, mode: "percentile", providerWeights: Object.freeze({}), gates: Object.freeze({ m20: 0.9, h24: 0.9, d7: 0.9 }) });
93
+ var ROOT_FAMILY_HEAT_DOMAIN_V1 = Object.freeze({ version: 1, kind: "root-family" });
94
+ var rootFamilyHeatDomainV1Schema = z.object({ version: z.literal(1), kind: z.literal("root-family") }).strip();
95
+ var configV3Base = { schemaVersion: z.literal(3), domain: rootFamilyHeatDomainV1Schema, providerWeights: weights };
96
+ var scoringConfigV3Schema = z.discriminatedUnion("mode", [
97
+ z.object({ ...configV3Base, mode: z.literal("percentile"), gates: z.object({ m20: z.number().finite().min(0).max(1), h24: z.number().finite().min(0).max(1), d7: z.number().finite().min(0).max(1) }).strip() }).strip(),
98
+ z.object({ ...configV3Base, mode: z.literal("absolute"), thresholds, lifetimeEnabled: z.literal(false).optional() }).strip()
99
+ ]);
100
+ var storedScoringConfigSchema = z.union([scoringConfigV2Schema, scoringConfigV3Schema]);
101
+ var DEFAULT_SCORING_CONFIG_V3 = Object.freeze({ schemaVersion: 3, domain: ROOT_FAMILY_HEAT_DOMAIN_V1, mode: "percentile", providerWeights: Object.freeze({}), gates: Object.freeze({ m20: 0.9, h24: 0.8, d7: 0.8 }) });
102
+ var HEAT_WINDOWS = ["m20", "h24", "d7"];
103
+ var rankWindows = z.object({ m20: rank, h24: rank, d7: rank }).strip();
104
+ var rootTopologyV1Schema = z.discriminatedUnion("kind", [
105
+ z.object({ kind: z.literal("attested"), parentThreadId: id.nullable(), lifecycleOwnerThreadId: id.nullable(), deleted: z.boolean() }).strip(),
106
+ z.object({ kind: z.literal("unknown"), reasons }).strip()
107
+ ]);
108
+ function rootFamilyEligibilityV1(input) {
109
+ const topology = rootTopologyV1Schema.parse(input);
110
+ if (topology.kind === "unknown")
111
+ return "unknown";
112
+ if (topology.deleted)
113
+ return "deleted";
114
+ return topology.parentThreadId === null && topology.lifecycleOwnerThreadId === null ? "eligible-root" : "ineligible-child";
115
+ }
116
+ var coveredPopulation = z.object({ count, coverage, reasons }).strip();
117
+ var rootPopulations = z.object({ m20: coveredPopulation, h24: coveredPopulation, d7: coveredPopulation }).strip();
118
+ var interval = z.object({ start: time, end: time }).strip();
119
+ var rootFamilyHeatContextV1Schema = z.object({
120
+ version: z.literal(1),
121
+ domain: rootFamilyHeatDomainV1Schema,
122
+ contextRevision: revision,
123
+ configRevision: revision,
124
+ topologyRevision: revision,
125
+ rankRevision: revision,
126
+ rankAsOf: time,
127
+ windows: z.object({ m20: interval, h24: interval, d7: interval }).strip(),
128
+ population: z.object({ dollars: rootPopulations, scores: rootPopulations }).strip()
129
+ }).strip().superRefine((v, c) => {
130
+ const duration = { m20: 20 * 60 * 1000, h24: 24 * 60 * 60 * 1000, d7: 7 * 24 * 60 * 60 * 1000 };
131
+ for (const w of HEAT_WINDOWS)
132
+ if (v.windows[w].end !== v.rankAsOf || v.windows[w].start !== Math.max(0, v.rankAsOf - duration[w]))
133
+ c.addIssue({ code: "custom", message: "Invalid root heat window provenance", path: ["windows", w] });
134
+ });
135
+ var decision = z.object({ decision: z.enum(["qualifies", "below", "suppressed", "unavailable"]), reason: z.enum(["threshold-met", "below-threshold", "gate-met", "below-gate", "not-root", "deleted", "topology-unknown", "unsupported-value", "zero", "insufficient-population", "rank-unavailable", "context-unavailable"]) }).strip();
136
+ var rootFamilyDecisionSetV1Schema = z.object({ contextRevision: revision, signals: z.object({ m20: decision, h24: decision, d7: decision }).strip(), winner: z.enum(["on-fire", "hot", "heavy"]).nullable() }).strip();
137
+ var recentRootHeatSchema = z.object({ topology: rootTopologyV1Schema, rootDollarRanks: rankWindows, rootScoreRanks: rankWindows, decisions: rootFamilyDecisionSetV1Schema }).strip();
138
+ function rootFamilyHeatV1(input) {
139
+ const context = rootFamilyHeatContextV1Schema.parse(input.context), config = scoringConfigV3Schema.parse(input.config), family = input.family;
140
+ if (family.configRevision !== context.configRevision || family.contextRevision !== context.contextRevision)
141
+ throw new Error("Root heat configuration/context mismatch");
142
+ const eligibility = rootFamilyEligibilityV1(input.topology), signals = {};
143
+ for (const w of HEAT_WINDOWS) {
144
+ const score = recentDollarSchema.parse(family.scores[w]), r = rank.parse(family.rootScoreRanks[w]), pop = context.population.scores[w];
145
+ if (r !== null && (pop.count === 0 || pop.count === 1 && r !== 0 || pop.coverage === "unknown"))
146
+ throw new Error("Root rank population mismatch");
147
+ if (eligibility !== "eligible-root") {
148
+ signals[w] = { decision: eligibility === "unknown" ? "unavailable" : "suppressed", reason: eligibility === "unknown" ? "topology-unknown" : eligibility === "deleted" ? "deleted" : "not-root" };
149
+ continue;
150
+ }
151
+ if (score.amount === null) {
152
+ signals[w] = { decision: "unavailable", reason: "unsupported-value" };
153
+ continue;
154
+ }
155
+ if (score.amount === 0) {
156
+ signals[w] = score.kind === "floor" ? { decision: "unavailable", reason: "unsupported-value" } : { decision: "suppressed", reason: "zero" };
157
+ continue;
158
+ }
159
+ if (config.mode === "absolute") {
160
+ const hit = score.amount >= config.thresholds[w];
161
+ signals[w] = !hit && score.kind === "floor" ? { decision: "unavailable", reason: "unsupported-value" } : { decision: hit ? "qualifies" : "below", reason: hit ? "threshold-met" : "below-threshold" };
162
+ } else if (pop.coverage === "unknown")
163
+ signals[w] = { decision: "unavailable", reason: "rank-unavailable" };
164
+ else if (pop.count < 2)
165
+ signals[w] = { decision: "suppressed", reason: "insufficient-population" };
166
+ else if (r === null)
167
+ signals[w] = { decision: "unavailable", reason: "rank-unavailable" };
168
+ else {
169
+ const hit = r >= config.gates[w];
170
+ signals[w] = { decision: hit ? "qualifies" : "below", reason: hit ? "gate-met" : "below-gate" };
171
+ }
172
+ }
173
+ const winner = signals.m20.decision === "qualifies" ? "on-fire" : signals.h24.decision === "qualifies" ? "hot" : signals.d7.decision === "qualifies" ? "heavy" : null;
174
+ return { contextRevision: context.contextRevision, signals, winner };
175
+ }
176
+ var recentRowSchema = z.object({ threadId: id, revision, configRevision: revision, asOf: time, presentation: recentPresentationSchema.optional(), ownLastFoldAt: time.nullable(), familyLastFoldAt: time.nullable(), own: recentScopeSchema, family: recentScopeSchema, heat: recentRootHeatSchema.optional() }).strip();
177
+ var recentRootInputSchema = z.object({ threadId: id, asOf: time, configRevision: revision, presentation: recentPresentationSchema.optional(), family: recentScopeSchema, heat: recentRootHeatSchema }).strip();
178
+ var recentHeatInputsSchema = z.object({ contextRevision: revision, topologyRevision: revision, cohortComplete: z.boolean(), pending: count, omitted: z.object({ count, exact: z.boolean() }).strip(), rows: z.array(recentRootInputSchema).max(RECENT_LIMITS.rows) }).strip();
179
+ function weightedScoreV2(values, providerWeights) {
180
+ if (values.length > RECENT_LIMITS.providers)
181
+ throw new Error("Too many score providers");
182
+ weights.parse(providerWeights);
183
+ let sum = 0, unknown = false;
184
+ const seen = new Set;
185
+ for (const v of values) {
186
+ id.parse(v.providerId);
187
+ if (seen.has(v.providerId))
188
+ throw new Error("Duplicate score provider");
189
+ seen.add(v.providerId);
190
+ if (v.amount === null) {
191
+ unknown = true;
192
+ continue;
193
+ }
194
+ if (!Number.isFinite(v.amount) || v.amount < 0)
195
+ throw new Error("Invalid provider dollars");
196
+ const w = Object.hasOwn(providerWeights, v.providerId) ? providerWeights[v.providerId] : 1;
197
+ sum += w * v.amount;
198
+ if (!Number.isFinite(sum))
199
+ throw new Error("Weighted dollar overflow");
200
+ }
201
+ return unknown ? null : sum;
202
+ }
203
+ var population = z.object({ count, coverage }).strip();
204
+ var populations = z.object({ m20: population, h24: population, d7: population }).strip();
205
+ var scopedPopulations = z.object({ dollars: populations, scores: populations }).strip();
206
+ var recentPublicationIdentitySchema = z.object({ instance: z.string().min(1).max(128), sequence: z.number().int().positive().max(Number.MAX_SAFE_INTEGER) }).strip();
207
+ var recentSnapshotFullSchema = z.object({
208
+ version: z.literal(1),
209
+ revision,
210
+ publication: recentPublicationIdentitySchema.optional(),
211
+ unchanged: z.literal(false),
212
+ currency: z.literal("USD"),
213
+ estimated: z.literal(true),
214
+ cutoffLastFoldAt: time.nullable(),
215
+ asOf: time,
216
+ rankAsOf: time,
217
+ recentWindowMs: z.literal(RECENT_LIMITS.windowMs),
218
+ config: z.object({ revision, value: storedScoringConfigSchema }).strip(),
219
+ heat: rootFamilyHeatContextV1Schema.optional(),
220
+ heatInputs: recentHeatInputsSchema.optional(),
221
+ coverage,
222
+ coverageReasons: reasons,
223
+ materializationPending: count,
224
+ omittedRows: z.object({ count, exact: z.boolean() }).strip(),
225
+ population: z.object({ own: scopedPopulations, family: scopedPopulations }).strip(),
226
+ rows: z.array(recentRowSchema).max(RECENT_LIMITS.rows)
227
+ }).strip();
228
+ var recentSnapshotResultSchema = z.union([z.object({ version: z.literal(1), revision, unchanged: z.literal(true) }).strip(), recentSnapshotFullSchema]);
229
+ function validateRecentSnapshot(input, value) {
230
+ recentSnapshotInputSchema.parse(input);
231
+ const bytes = (v) => new TextEncoder().encode(JSON.stringify(v)).byteLength;
232
+ if (bytes(value) > RECENT_LIMITS.bytes)
233
+ throw new Error("Snapshot byte limit");
234
+ const raw = value;
235
+ let entries = 0;
236
+ for (const rows of [raw?.rows, raw?.heatInputs?.rows])
237
+ if (rows !== undefined) {
238
+ if (!Array.isArray(rows) || (entries += rows.length) > RECENT_LIMITS.rows)
239
+ throw new Error("Snapshot aggregate row limit");
240
+ for (const row of rows)
241
+ if (bytes(row) > RECENT_LIMITS.rowBytes)
242
+ throw new Error("Snapshot row byte limit");
243
+ }
244
+ if (value && typeof value === "object" && value.unchanged === true) {
245
+ for (const field of Object.keys(recentSnapshotFullSchema.shape))
246
+ if (!["version", "revision", "unchanged"].includes(field) && Object.hasOwn(value, field))
247
+ throw new Error("Unchanged snapshot carries recognized full content");
248
+ }
249
+ const out = recentSnapshotResultSchema.parse(value);
250
+ if (out.unchanged) {
251
+ if (input.ifRevision !== out.revision)
252
+ throw new Error("Unmatched snapshot revision");
253
+ return out;
254
+ }
255
+ if (out.config.value.schemaVersion === 3 && !out.heat)
256
+ throw new Error("Root-family configuration requires heat context");
257
+ if (out.heat && (out.config.value.schemaVersion !== 3 || out.heat.configRevision !== out.config.revision || out.heat.rankAsOf !== out.rankAsOf))
258
+ throw new Error("Root heat configuration/rank epoch mismatch");
259
+ if (out.rankAsOf > out.asOf)
260
+ throw new Error("Future rank time");
261
+ if ((out.materializationPending || out.omittedRows.count || !out.omittedRows.exact) && out.coverage === "complete")
262
+ throw new Error("Pending/capped snapshot must disclose coverage");
263
+ let previousActivity = Infinity, previousId = "";
264
+ const ids = new Set;
265
+ for (const row of out.rows) {
266
+ if (ids.has(row.threadId))
267
+ throw new Error("Duplicate snapshot thread");
268
+ ids.add(row.threadId);
269
+ if (row.asOf > out.asOf)
270
+ throw new Error("Future row time");
271
+ if (row.configRevision !== out.config.revision)
272
+ throw new Error("Row score configuration mismatch");
273
+ if (row.ownLastFoldAt === null && row.familyLastFoldAt === null)
274
+ throw new Error("Missing fold activity timestamp");
275
+ const activity = Math.max(row.ownLastFoldAt ?? 0, row.familyLastFoldAt ?? 0);
276
+ if (activity > out.asOf || activity < Math.max(0, out.asOf - out.recentWindowMs))
277
+ throw new Error("Row outside recent activity window");
278
+ if (activity > previousActivity || activity === previousActivity && row.threadId < previousId)
279
+ throw new Error("Rows must be newest first with ID tie-break");
280
+ previousActivity = activity;
281
+ previousId = row.threadId;
282
+ for (const scope of ["own", "family"]) {
283
+ validateProjection(row[scope], out.config.value.providerWeights);
284
+ validateRanks(row[scope], out.population[scope], out.rankAsOf >= row.asOf);
285
+ }
286
+ validateRootHeatRow(row, out.heat, out.config.value);
287
+ }
288
+ if (out.heat) {
289
+ for (const [axis, field] of [["dollars", "windows"], ["scores", "scores"]]) {
290
+ for (const w of HEAT_WINDOWS) {
291
+ const pop = out.heat.population[axis][w], supported = out.rows.filter((row) => row.heat && rootFamilyEligibilityV1(row.heat.topology) === "eligible-root" && row.asOf <= out.heat.rankAsOf && (row.family[field][w].amount ?? 0) > 0).length;
292
+ if (pop.coverage === "complete" && pop.count < supported)
293
+ throw new Error("Root population below supported displayed members");
294
+ }
295
+ }
296
+ }
297
+ if (out.heatInputs)
298
+ validateHeatInputs(out);
299
+ if (out.omittedRows.count && out.rows.length && out.cutoffLastFoldAt !== previousActivity)
300
+ throw new Error("Missing eviction cutoff");
301
+ return out;
302
+ }
303
+ function validateProjection(projection, providerWeights) {
304
+ if (projection.coverageReasons.includes("unpriced") && !projection.approximation.reasons.includes("missing-price"))
305
+ throw new Error("Unpriced coverage requires missing-price provenance");
306
+ const observationGap = projection.coverage !== "complete" && !projection.coverageReasons.includes("unpriced") && projection.approximation.reasons.includes("source-gap") && projection.coverageReasons.some((r) => ["source-gap", "root-unattested", "history-unavailable", "missing-thread"].includes(r));
307
+ const unavailableProvenance = (approximation) => approximation.reasons.includes("missing-price") || observationGap && approximation.reasons.includes("source-gap");
308
+ for (const p of projection.providers) {
309
+ if (p.approximation.approximate && (!projection.approximation.approximate || p.approximation.reasons.some((reason2) => !projection.approximation.reasons.includes(reason2))))
310
+ throw new Error("Aggregate must retain provider approximation provenance");
311
+ if (Object.values(p.windows).some((v) => v.kind === "unavailable") && !unavailableProvenance(p.approximation))
312
+ throw new Error("Unavailable provider requires missing-price provenance");
313
+ }
314
+ if ([...Object.values(projection.windows), ...Object.values(projection.scores)].some((v) => v.kind === "unavailable") && !unavailableProvenance(projection.approximation))
315
+ throw new Error("Unavailable total requires missing-price provenance");
316
+ for (const window of ["m20", "h24", "d7", "lifetime"]) {
317
+ const known = projection.providers.filter((p) => p.windows[window].kind !== "unavailable");
318
+ const parts = known.map((p) => ({ providerId: p.providerId, amount: p.windows[window].amount }));
319
+ const dollars = parts.reduce((sum, p) => sum + p.amount, 0), score = weightedScoreV2(parts, providerWeights);
320
+ if (!Number.isFinite(dollars))
321
+ throw new Error("Provider dollar overflow");
322
+ const incomplete = projection.omittedProviders > 0 || known.length !== projection.providers.length;
323
+ const floor = incomplete || known.some((p) => p.windows[window].kind === "floor");
324
+ const equal = (a, b) => Math.abs(a - b) <= 8 * Number.EPSILON * Math.max(a, b);
325
+ const attribution = projection.attribution;
326
+ if (projection.windows[window].kind === "unavailable" && (dollars > 0 || (attribution?.unknownProvider[window].amount ?? 0) > 0))
327
+ throw new Error("Supported provider dollars require an aggregate floor or estimate");
328
+ if (attribution) {
329
+ const unknown = attribution.unknownProvider[window], complete = attribution.attributionComplete[window];
330
+ if (complete && (projection.omittedProviders > 0 || unknown.kind !== "estimate" || unknown.amount !== 0))
331
+ throw new Error("Complete attribution requires no unknown provider remainder");
332
+ if (attribution.unpriced[window] && !projection.approximation.reasons.includes("missing-price"))
333
+ throw new Error("Unpriced attribution requires missing-price provenance");
334
+ if (projection.windows[window].kind === "estimate" && (!complete || attribution.unpriced[window] || unknown.kind !== "estimate" || unknown.amount !== 0))
335
+ throw new Error("Incomplete attribution cannot carry an estimate");
336
+ const total = projection.windows[window], reconciled = dollars + (unknown.amount ?? 0);
337
+ if (!Number.isFinite(reconciled) || total.amount !== null && (reconciled > total.amount && !equal(reconciled, total.amount) || unknown.kind === "estimate" && !equal(reconciled, total.amount)))
338
+ throw new Error("Unknown provider reconciliation mismatch");
339
+ }
340
+ for (const [total, subtotal, label] of [[projection.windows[window], dollars, "dollar"], [projection.scores[window], score, "score"]]) {
341
+ if (total.kind === "unavailable")
342
+ continue;
343
+ if (floor && total.kind === "estimate")
344
+ throw new Error("Partial/provider floor cannot become an exact estimate");
345
+ if (incomplete) {
346
+ if (total.amount < subtotal && !equal(total.amount, subtotal))
347
+ throw new Error(`Known provider ${label} subtotal exceeds total`);
348
+ } else if (!equal(total.amount, subtotal))
349
+ throw new Error(`Provider ${label} total mismatch`);
350
+ }
351
+ }
352
+ }
353
+ function validateRanks(projection, population2, contemporaneous) {
354
+ for (const [values, ranks, pops] of [[projection.windows, projection.dollarRanks, population2.dollars], [projection.scores, projection.scoreRanks, population2.scores]]) {
355
+ for (const w of ["m20", "h24", "d7"]) {
356
+ const r = ranks[w], n = pops[w].count;
357
+ if (r !== null && (n === 0 || n === 1 && r !== 0))
358
+ throw new Error("Rank contradicts empty/singleton population");
359
+ if (contemporaneous && r !== null && (values[w].amount === null || values[w].amount <= 0))
360
+ throw new Error("Rank requires supported positive contemporaneous amount");
361
+ }
362
+ }
363
+ }
364
+ function recentPercentileHeat(projection, gates = { m20: 0.9, h24: 0.9, d7: 0.9 }, population2) {
365
+ for (const w of ["m20", "h24", "d7"])
366
+ if (!Number.isFinite(gates[w]) || gates[w] < 0 || gates[w] > 1)
367
+ throw new Error("Invalid gate");
368
+ for (const [w, label] of [["m20", "on-fire"], ["h24", "hot"], ["d7", "heavy"]]) {
369
+ const amount = projection.scores[w].amount, r = projection.scoreRanks[w], n = population2?.[w].count ?? (r !== null && r > 0 ? 2 : 0);
370
+ if (n >= 2 && amount !== null && amount > 0 && r !== null && r >= gates[w])
371
+ return label;
372
+ }
373
+ return null;
374
+ }
375
+ function isFirstIndexRank(r, n) {
376
+ const nearest = Math.round(r * (n - 1)) / (n - 1);
377
+ return Math.abs(r - nearest) <= 4 * Number.EPSILON * Math.max(r, nearest);
378
+ }
379
+ function validateRootHeatRow(row, context, config) {
380
+ if (!context) {
381
+ if (row.heat)
382
+ throw new Error("Root row requires context");
383
+ return;
384
+ }
385
+ if (!row.heat || config.schemaVersion !== 3)
386
+ throw new Error("Missing root row topology/decisions");
387
+ const heat = row.heat, eligible = rootFamilyEligibilityV1(heat.topology) === "eligible-root";
388
+ for (const [ranks, population2, values] of [[heat.rootDollarRanks, context.population.dollars, row.family.windows], [heat.rootScoreRanks, context.population.scores, row.family.scores]]) {
389
+ for (const w of HEAT_WINDOWS) {
390
+ const r = ranks[w], n = population2[w].count;
391
+ if (!eligible && r !== null)
392
+ throw new Error("Ineligible thread carries root-domain rank");
393
+ if (r !== null && (n === 0 || n === 1 && r !== 0 || population2[w].coverage === "unknown"))
394
+ throw new Error("Root rank population mismatch");
395
+ if (r !== null && n >= 2 && population2[w].coverage === "complete" && !isFirstIndexRank(r, n))
396
+ throw new Error("Root rank outside complete first-index grid");
397
+ if (context.rankAsOf >= row.asOf && r !== null && population2[w].coverage === "complete" && values[w].kind !== "estimate")
398
+ throw new Error("Uncertified value cannot carry a complete root rank");
399
+ if (context.rankAsOf >= row.asOf && r !== null && (values[w].amount === null || values[w].amount <= 0))
400
+ throw new Error("Root rank needs supported positive amount");
401
+ }
402
+ }
403
+ const expected = rootFamilyHeatV1({ topology: heat.topology, family: { contextRevision: heat.decisions.contextRevision, configRevision: row.configRevision, scores: row.family.scores, rootScoreRanks: heat.rootScoreRanks }, context, config });
404
+ if (JSON.stringify(expected) !== JSON.stringify(heat.decisions))
405
+ throw new Error("Root heat decisions/winner mismatch");
406
+ }
407
+ function validateHeatInputs(snapshot) {
408
+ const { heatInputs: input, heat: context } = snapshot;
409
+ if (!context || snapshot.config.value.schemaVersion !== 3 || input.contextRevision !== context.contextRevision || input.topologyRevision !== context.topologyRevision)
410
+ throw new Error("Root input context mismatch");
411
+ if (!input.cohortComplete && snapshot.coverage === "complete")
412
+ throw new Error("Incomplete root inputs require partial snapshot");
413
+ if (input.cohortComplete && (input.pending || input.omitted.count || !input.omitted.exact))
414
+ throw new Error("Incomplete root input cohort");
415
+ const ids = new Set;
416
+ for (const row of input.rows) {
417
+ if (ids.has(row.threadId))
418
+ throw new Error("Duplicate root input");
419
+ ids.add(row.threadId);
420
+ if (row.asOf > context.rankAsOf || row.configRevision !== snapshot.config.revision || rootFamilyEligibilityV1(row.heat.topology) !== "eligible-root" || !row.family.attribution)
421
+ throw new Error("Invalid root input capture");
422
+ validateProjection(row.family, snapshot.config.value.providerWeights);
423
+ validateRootHeatRow(row, context, snapshot.config.value);
424
+ }
425
+ for (const [axis, field, ranks] of [["dollars", "windows", "rootDollarRanks"], ["scores", "scores", "rootScoreRanks"]])
426
+ for (const w of HEAT_WINDOWS) {
427
+ const pop = context.population[axis][w], supported = input.rows.filter((row) => (row.family[field][w].amount ?? 0) > 0);
428
+ if (input.cohortComplete && pop.count > input.rows.length)
429
+ throw new Error("Root cohort below declared population");
430
+ if (pop.coverage === "complete" && pop.count < supported.length)
431
+ throw new Error("Root input population below supported members");
432
+ if (!input.cohortComplete || pop.coverage !== "complete")
433
+ continue;
434
+ if (supported.some((row) => row.family[field][w].kind !== "estimate") || pop.count !== supported.length)
435
+ throw new Error("Complete root input population mismatch");
436
+ const sorted = supported.slice().sort((a, b) => a.family[field][w].amount - b.family[field][w].amount || (a.threadId < b.threadId ? -1 : 1)), expected = new Map;
437
+ let first = 0;
438
+ for (let i = 0;i < sorted.length; i++) {
439
+ if (!i || sorted[i].family[field][w].amount !== sorted[i - 1].family[field][w].amount)
440
+ first = i;
441
+ expected.set(sorted[i].threadId, sorted.length === 1 ? 0 : first / (sorted.length - 1));
442
+ }
443
+ for (const row of input.rows) {
444
+ const actual = row.heat[ranks][w], rank2 = expected.get(row.threadId) ?? null;
445
+ if (actual !== rank2)
446
+ throw new Error("Root input rank/order mismatch");
447
+ }
448
+ }
449
+ }
450
+ // src/heat-settings.ts
451
+ import { z as z2 } from "zod";
452
+ var HEAT_SETTINGS_METHODS = Object.freeze({ read: "threadMetricsHeatSettingsReadV1", save: "threadMetricsHeatSettingsSaveV1" });
453
+ var HEAT_SETTINGS_LIMITS = Object.freeze({ configBytes: 16 * 1024, inputBytes: 18 * 1024, outputBytes: 40 * 1024, operationKeys: 128 });
454
+ var version = z2.string().min(1).max(256);
455
+ var key = z2.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/);
456
+ var providerId = z2.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/);
457
+ var weights2 = z2.record(providerId, z2.number().finite().min(0).max(3)).refine((v) => Object.keys(v).length <= 32, "Too many provider weights");
458
+ var gates = z2.object({ m20: z2.number().finite().min(0).max(1), h24: z2.number().finite().min(0).max(1), d7: z2.number().finite().min(0).max(1) }).strict();
459
+ var thresholds2 = z2.object({ m20: z2.number().finite().min(0).max(1e6), h24: z2.number().finite().min(0).max(1e6), d7: z2.number().finite().min(0).max(1e6), lifetime: z2.number().finite().min(0).max(1e6) }).strict();
460
+ var legacyHeatConfigInputSchema = z2.discriminatedUnion("mode", [
461
+ z2.object({ schemaVersion: z2.literal(2), mode: z2.literal("percentile"), providerWeights: weights2, gates }).strict(),
462
+ z2.object({ schemaVersion: z2.literal(2), mode: z2.literal("absolute"), providerWeights: weights2, thresholds: thresholds2, lifetimeEnabled: z2.boolean().optional() }).strict()
463
+ ]);
464
+ var domain = z2.object({ version: z2.literal(1), kind: z2.literal("root-family") }).strict();
465
+ var heatConfigInputSchema = z2.discriminatedUnion("mode", [
466
+ z2.object({ schemaVersion: z2.literal(3), domain, mode: z2.literal("percentile"), providerWeights: weights2, gates }).strict(),
467
+ z2.object({ schemaVersion: z2.literal(3), domain, mode: z2.literal("absolute"), providerWeights: weights2, thresholds: thresholds2, lifetimeEnabled: z2.literal(false).optional() }).strict()
468
+ ]);
469
+ function canonicalHeatConfig(value) {
470
+ const v = value.schemaVersion === 2 ? legacyHeatConfigInputSchema.parse(value) : heatConfigInputSchema.parse(value);
471
+ return JSON.stringify({ ...v, providerWeights: Object.fromEntries(Object.entries(v.providerWeights).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)), ...v.mode === "absolute" ? { lifetimeEnabled: v.lifetimeEnabled ?? false } : {} });
472
+ }
473
+ function validateHeatConfig(value) {
474
+ const parsed = bounded(z2.union([legacyHeatConfigInputSchema, heatConfigInputSchema]), value, HEAT_SETTINGS_LIMITS.configBytes);
475
+ if (new TextEncoder().encode(canonicalHeatConfig(parsed)).byteLength > HEAT_SETTINGS_LIMITS.configBytes)
476
+ throw new Error("Config byte limit");
477
+ return parsed;
478
+ }
479
+ function validateRootFamilyHeatConfig(value) {
480
+ return bounded(heatConfigInputSchema, value, HEAT_SETTINGS_LIMITS.configBytes);
481
+ }
482
+ function prepareRootFamilyMigration(record, review) {
483
+ const value = validateHeatConfig(record.value);
484
+ if (value.schemaVersion === 3)
485
+ return { kind: "review", base: record, candidate: value };
486
+ if (value.mode === "absolute" && value.lifetimeEnabled && !review?.disableLifetime)
487
+ return { kind: "blocked", reason: "Legacy lifetime heat is enabled. Explicitly review and disable that behavior before root-family migration; stored values were preserved." };
488
+ const candidate = value.mode === "percentile" ? { schemaVersion: 3, domain: ROOT_FAMILY_HEAT_DOMAIN_V1, mode: value.mode, providerWeights: { ...value.providerWeights }, gates: { ...value.gates } } : { schemaVersion: 3, domain: ROOT_FAMILY_HEAT_DOMAIN_V1, mode: value.mode, providerWeights: { ...value.providerWeights }, thresholds: { ...value.thresholds }, lifetimeEnabled: false };
489
+ return { kind: "review", base: record, candidate };
490
+ }
491
+ var heatSettingsReadInputSchema = z2.object({ protocolVersion: z2.literal(1), operationKey: key.optional() }).strict();
492
+ var heatSettingsSaveInputSchema = z2.object({ protocolVersion: z2.literal(1), baseConfigVersion: version, operationKey: key, candidate: heatConfigInputSchema }).strict();
493
+ var heatConfigRecordSchema = z2.object({ version, value: storedScoringConfigSchema }).strip();
494
+ var observation = z2.object({ instance: version, sequence: z2.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER), configVersion: version.nullable() }).strip();
495
+ var outcome = z2.discriminatedUnion("kind", [
496
+ z2.object({ kind: z2.literal("saved"), operationKey: key, record: heatConfigRecordSchema }).strip(),
497
+ z2.object({ kind: z2.literal("conflict"), operationKey: key, record: heatConfigRecordSchema }).strip(),
498
+ z2.object({ kind: z2.literal("unknown"), operationKey: key }).strip()
499
+ ]);
500
+ var heatSettingsReadResultSchema = z2.object({ protocolVersion: z2.literal(1), current: heatConfigRecordSchema, published: observation, writable: z2.boolean(), reason: z2.string().max(512).nullable(), migration: z2.object({ kind: z2.literal("domain-required"), target: z2.object({ version: z2.literal(1), kind: z2.literal("root-family") }).strip() }).strip().optional(), outcome: outcome.optional() }).strip();
501
+ var heatSettingsSaveResultSchema = z2.object({ protocolVersion: z2.literal(1), kind: z2.enum(["saved", "conflict"]), operationKey: key, record: heatConfigRecordSchema, publicationPending: z2.boolean() }).strip();
502
+ function scoringHeatV2(values, ranks, population2, config) {
503
+ legacyHeatConfigInputSchema.parse(config);
504
+ for (const w of ["m20", "h24", "d7"])
505
+ if (!Number.isSafeInteger(population2[w]) || population2[w] < 0 || ranks[w] !== null && (!Number.isFinite(ranks[w]) || ranks[w] < 0 || ranks[w] > 1))
506
+ throw new Error("Invalid heat rank population");
507
+ for (const [w, label] of [["m20", "on-fire"], ["h24", "hot"], ["d7", "heavy"]]) {
508
+ const n2 = values[w].amount;
509
+ if (n2 !== null && Number.isFinite(n2) && n2 > 0 && (config.mode === "absolute" ? n2 >= config.thresholds[w] : population2[w] >= 2 && ranks[w] !== null && ranks[w] >= config.gates[w]))
510
+ return label;
511
+ }
512
+ const n = values.lifetime.amount;
513
+ return config.mode === "absolute" && config.lifetimeEnabled && n !== null && Number.isFinite(n) && n > 0 && n >= config.thresholds.lifetime ? "heavy" : null;
514
+ }
515
+ function bounded(schema, value, maxBytes) {
516
+ const json = JSON.stringify(value);
517
+ if (json === undefined || new TextEncoder().encode(json).byteLength > maxBytes)
518
+ throw new Error("Heat settings byte limit");
519
+ return schema.parse(value);
520
+ }
521
+ function validateHeatSaveInput(value) {
522
+ const input = bounded(heatSettingsSaveInputSchema, value, HEAT_SETTINGS_LIMITS.inputBytes);
523
+ validateHeatConfig(input.candidate);
524
+ return input;
525
+ }
526
+ function reconcileHeatSave(input, read) {
527
+ const o = read.outcome;
528
+ if (o?.operationKey === input.operationKey && o.kind === "saved")
529
+ return canonicalHeatConfig(o.record.value) === canonicalHeatConfig(input.candidate) ? "saved" : "conflict";
530
+ if (o?.operationKey === input.operationKey && o.kind === "conflict")
531
+ return "conflict";
532
+ return read.current.version === input.baseConfigVersion ? "unchanged" : "unknown";
533
+ }
534
+
535
+ // src/index.ts
536
+ import { z as z3 } from "zod";
537
+ import { UsageVectorSchema as UsageVectorSchema2 } from "@phosphorco/bb-usage-algebra/vector";
4
538
  var LIMITS = Object.freeze({ threadIds: 200, seriesPoints: 288, detailRows: 200, inputBytes: 65536, outputBytes: 2097152, describeBytes: 16384, candidates: 128, listedPlugins: 1e4, concurrency: 4, callMs: 2000, discoveryMs: 5000, familyEdges: 50000, familyMembers: 1e4, familyDepth: 256 });
5
539
  var METRIC_BUCKET_MS = 300000;
6
540
  var WINDOW_BUCKETS = { m20: 4, h24: 288, d7: 2016, lifetime: 2016 };
7
541
  function retainedWindowStart(asOf, window) {
8
- time.parse(asOf);
542
+ time2.parse(asOf);
9
543
  windowSchema.parse(window);
10
544
  return Math.max(0, Math.floor(asOf / METRIC_BUCKET_MS) * METRIC_BUCKET_MS - (WINDOW_BUCKETS[window] - 1) * METRIC_BUCKET_MS);
11
545
  }
12
- var METHODS = Object.freeze({ describe: "threadMetricsDescribe", read: "threadMetricsReadV1", detail: "threadMetricsDetailV1" });
13
- var id = z.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/);
14
- var text = z.string().min(1).max(256);
15
- var time = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
16
- var money = z.number().finite().nonnegative().nullable();
17
- var scope = z.enum(["own", "family"]);
18
- var windowSchema = z.enum(["m20", "h24", "d7", "lifetime"]);
19
- var coverageSchema = z.enum(["complete", "partial", "unknown"]);
20
- var coverageReasonSchema = z.enum(["source-gap", "root-unattested", "history-unavailable", "member-cap", "depth-cap", "edge-cap", "missing-thread", "unpriced", "retention", "row-cap", "pagination", "population-incomplete"]);
21
- var reasons = z.array(coverageReasonSchema).max(16);
22
- var approximationSchema = z.object({
23
- approximate: z.boolean(),
24
- reasons: z.array(z.enum(["closest-model", "provider-default", "model-attribution", "bucket-boundary", "missing-price", "source-gap"])).max(6),
25
- rates: z.array(z.object({ providerId: id, modelId: text, rateId: text.nullable(), basis: z.enum(["matched", "closest-model", "provider-default", "unpriced"]) }).superRefine((rate, ctx) => {
546
+ var METHODS = Object.freeze({ describe: "threadMetricsDescribe", read: "threadMetricsReadV1", detail: "threadMetricsDetailV1", recentSnapshot: "threadMetricsRecentSnapshotV1" });
547
+ var id2 = z3.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/);
548
+ var text = z3.string().min(1).max(256);
549
+ var time2 = z3.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
550
+ var money = z3.number().finite().nonnegative().nullable();
551
+ var scope = z3.enum(["own", "family"]);
552
+ var windowSchema = z3.enum(["m20", "h24", "d7", "lifetime"]);
553
+ var coverageSchema = z3.enum(["complete", "partial", "unknown"]);
554
+ var coverageReasonSchema = z3.enum(["source-gap", "root-unattested", "history-unavailable", "member-cap", "depth-cap", "edge-cap", "missing-thread", "unpriced", "retention", "row-cap", "pagination", "population-incomplete"]);
555
+ var reasons2 = z3.array(coverageReasonSchema).max(16);
556
+ var approximationSchema = z3.object({
557
+ approximate: z3.boolean(),
558
+ reasons: z3.array(z3.enum(["closest-model", "provider-default", "model-attribution", "bucket-boundary", "missing-price", "source-gap"])).max(6),
559
+ rates: z3.array(z3.object({ providerId: id2, modelId: text, rateId: text.nullable(), basis: z3.enum(["matched", "closest-model", "provider-default", "unpriced"]) }).superRefine((rate, ctx) => {
26
560
  if (rate.basis === "unpriced" !== (rate.rateId === null))
27
561
  ctx.addIssue({ code: "custom", path: ["rateId"], message: "Priced basis requires rateId; unpriced basis requires null" });
28
562
  })).max(32).optional()
@@ -30,40 +564,40 @@ var approximationSchema = z.object({
30
564
  if (!v.approximate && (v.reasons.length || v.rates?.some((r) => r.basis !== "matched")))
31
565
  ctx.addIssue({ code: "custom", message: "Approximation provenance requires approximate flag" });
32
566
  });
33
- var rank = z.number().finite().min(0).max(1).nullable();
34
- var ranksSchema = z.object({ m20: rank, h24: rank, d7: rank }).strip();
35
- var population = z.object({ asOf: time, count: z.number().int().nonnegative(), coverage: coverageSchema }).strip();
36
- var rankPopulationSchema = z.object({ m20: population, h24: population, d7: population }).strip();
37
- var windowValueSchema = z.object({ tokens: UsageVectorSchema, amount: money }).strip();
38
- var windowsSchema = z.object({ m20: windowValueSchema, h24: windowValueSchema, d7: windowValueSchema, lifetime: windowValueSchema }).strip();
39
- var bucketSchema = z.object({ start: time, amount: money }).strip();
567
+ var rank2 = z3.number().finite().min(0).max(1).nullable();
568
+ var ranksSchema = z3.object({ m20: rank2, h24: rank2, d7: rank2 }).strip();
569
+ var population2 = z3.object({ asOf: time2, count: z3.number().int().nonnegative(), coverage: coverageSchema }).strip();
570
+ var rankPopulationSchema = z3.object({ m20: population2, h24: population2, d7: population2 }).strip();
571
+ var windowValueSchema = z3.object({ tokens: UsageVectorSchema2, amount: money }).strip();
572
+ var windowsSchema = z3.object({ m20: windowValueSchema, h24: windowValueSchema, d7: windowValueSchema, lifetime: windowValueSchema }).strip();
573
+ var bucketSchema = z3.object({ start: time2, amount: money }).strip();
40
574
  var metadata = {
41
- threadId: id,
575
+ threadId: id2,
42
576
  scope,
43
- memberCount: z.number().int().nonnegative().max(LIMITS.familyMembers),
577
+ memberCount: z3.number().int().nonnegative().max(LIMITS.familyMembers),
44
578
  coverage: coverageSchema,
45
- coverageReasons: reasons,
46
- excluded: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
47
- asOf: time,
579
+ coverageReasons: reasons2,
580
+ excluded: z3.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
581
+ asOf: time2,
48
582
  revision: text,
49
583
  priceVersion: text,
50
- currency: z.literal("USD"),
51
- estimated: z.literal(true),
584
+ currency: z3.literal("USD"),
585
+ estimated: z3.literal(true),
52
586
  approximation: approximationSchema,
53
587
  ranks: ranksSchema,
54
588
  rankPopulation: rankPopulationSchema,
55
- reported: z.object({ amount: z.number().finite().nonnegative(), currency: z.string().min(1).max(16), asOf: time }).strip().optional()
589
+ reported: z3.object({ amount: z3.number().finite().nonnegative(), currency: z3.string().min(1).max(16), asOf: time2 }).strip().optional()
56
590
  };
57
- var summaryRowSchema = z.discriminatedUnion("changed", [
58
- z.object({ ...metadata, changed: z.literal(true), windows: windowsSchema, buckets: z.array(bucketSchema).max(LIMITS.seriesPoints).optional() }).strip(),
59
- z.object({ ...metadata, changed: z.literal(false), windows: z.never().optional(), buckets: z.never().optional() }).strip()
591
+ var summaryRowSchema = z3.discriminatedUnion("changed", [
592
+ z3.object({ ...metadata, changed: z3.literal(true), windows: windowsSchema, buckets: z3.array(bucketSchema).max(LIMITS.seriesPoints).optional() }).strip(),
593
+ z3.object({ ...metadata, changed: z3.literal(false), windows: z3.never().optional(), buckets: z3.never().optional() }).strip()
60
594
  ]);
61
- var readInputSchema = z.object({
62
- threadIds: z.array(id).min(1).max(LIMITS.threadIds),
595
+ var readInputSchema = z3.object({
596
+ threadIds: z3.array(id2).min(1).max(LIMITS.threadIds),
63
597
  scope,
64
- includeArchived: z.boolean().optional(),
65
- knownRevisions: z.record(id, text).optional(),
66
- series: z.object({ activeThreadId: id, maxPoints: z.number().int().min(1).max(LIMITS.seriesPoints) }).strict().optional()
598
+ includeArchived: z3.boolean().optional(),
599
+ knownRevisions: z3.record(id2, text).optional(),
600
+ series: z3.object({ activeThreadId: id2, maxPoints: z3.number().int().min(1).max(LIMITS.seriesPoints) }).strict().optional()
67
601
  }).strict().superRefine((v, ctx) => {
68
602
  if (new Set(v.threadIds).size !== v.threadIds.length)
69
603
  ctx.addIssue({ code: "custom", message: "Duplicate thread IDs" });
@@ -72,17 +606,17 @@ var readInputSchema = z.object({
72
606
  if (v.knownRevisions && Object.keys(v.knownRevisions).some((k) => !v.threadIds.includes(k)))
73
607
  ctx.addIssue({ code: "custom", message: "Unrequested revision key" });
74
608
  });
75
- var readResultSchema = z.object({ version: z.literal(1), rows: z.array(summaryRowSchema).max(LIMITS.threadIds) }).strip();
76
- var detailInputSchema = z.object({ threadId: id, scope, includeArchived: z.boolean().optional(), axis: z.enum(["model", "time"]), window: windowSchema, cursor: z.string().min(1).max(1024).optional(), limit: z.number().int().min(1).max(LIMITS.detailRows).optional() }).strict();
77
- var detailBase = { amount: money, tokens: UsageVectorSchema, approximation: approximationSchema };
78
- var detailRowSchema = z.discriminatedUnion("axis", [
79
- z.object({ ...detailBase, axis: z.literal("model"), providerId: id, modelId: text }).strip(),
80
- z.object({ ...detailBase, axis: z.literal("time"), start: time, end: time }).strip().refine((v) => v.end > v.start, { message: "Empty time interval" })
609
+ var readResultSchema = z3.object({ version: z3.literal(1), rows: z3.array(summaryRowSchema).max(LIMITS.threadIds) }).strip();
610
+ var detailInputSchema = z3.object({ threadId: id2, scope, includeArchived: z3.boolean().optional(), axis: z3.enum(["model", "time"]), window: windowSchema, cursor: z3.string().min(1).max(1024).optional(), limit: z3.number().int().min(1).max(LIMITS.detailRows).optional() }).strict();
611
+ var detailBase = { amount: money, tokens: UsageVectorSchema2, approximation: approximationSchema };
612
+ var detailRowSchema = z3.discriminatedUnion("axis", [
613
+ z3.object({ ...detailBase, axis: z3.literal("model"), providerId: id2, modelId: text }).strip(),
614
+ z3.object({ ...detailBase, axis: z3.literal("time"), start: time2, end: time2 }).strip().refine((v) => v.end > v.start, { message: "Empty time interval" })
81
615
  ]);
82
- var detailResultSchema = z.object({ version: z.literal(1), threadId: id, scope, axis: z.enum(["model", "time"]), window: windowSchema, asOf: time, revision: text, priceVersion: text, currency: z.literal("USD"), estimated: z.literal(true), coverage: coverageSchema, coverageReasons: reasons, excluded: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(), approximation: approximationSchema, total: windowValueSchema, rows: z.array(detailRowSchema).max(LIMITS.detailRows), nextCursor: z.string().min(1).max(1024).optional() }).strip();
83
- var describeInputSchema = z.object({}).strict();
84
- var describeEnvelopeSchema = z.object({ protocol: z.literal("bb-thread-metrics"), versions: z.array(z.number().int().positive()).max(16) }).passthrough();
85
- var capabilitiesSchema = z.object({ summary: z.literal(true), series: z.boolean(), detail: z.boolean() }).strip();
616
+ var detailResultSchema = z3.object({ version: z3.literal(1), threadId: id2, scope, axis: z3.enum(["model", "time"]), window: windowSchema, asOf: time2, revision: text, priceVersion: text, currency: z3.literal("USD"), estimated: z3.literal(true), coverage: coverageSchema, coverageReasons: reasons2, excluded: z3.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(), approximation: approximationSchema, total: windowValueSchema, rows: z3.array(detailRowSchema).max(LIMITS.detailRows), nextCursor: z3.string().min(1).max(1024).optional() }).strip();
617
+ var describeInputSchema = z3.object({}).strict();
618
+ var describeEnvelopeSchema = z3.object({ protocol: z3.literal("bb-thread-metrics"), versions: z3.array(z3.number().int().positive()).max(16) }).passthrough();
619
+ var capabilitiesSchema = z3.object({ summary: z3.literal(true), series: z3.boolean(), detail: z3.boolean(), recentSnapshotV1: z3.boolean().optional(), heatSettingsV1: z3.boolean().optional() }).strip();
86
620
  var describeV1Schema = describeEnvelopeSchema.extend({ capabilities: capabilitiesSchema });
87
621
  function parseBounded(schema, value, maxBytes) {
88
622
  const json = JSON.stringify(value);
@@ -100,29 +634,29 @@ function negotiateVersion(value) {
100
634
  const v = describeV1Schema.safeParse(value);
101
635
  return v.success ? { kind: "ready", version: 1, capabilities: v.data.capabilities } : { kind: "incompatible" };
102
636
  }
103
- function dollarPercentiles(population2) {
637
+ function dollarPercentiles(population3) {
104
638
  const seen = new Set;
105
- for (const p of population2) {
639
+ for (const p of population3) {
106
640
  if (seen.has(p.threadId))
107
641
  throw new Error("Duplicate population thread");
108
642
  seen.add(p.threadId);
109
643
  if (p.amount !== null && (!Number.isFinite(p.amount) || p.amount < 0))
110
644
  throw new Error("Invalid dollars");
111
645
  }
112
- const active = population2.filter((p) => p.amount !== null && p.amount > 0);
646
+ const active = population3.filter((p) => p.amount !== null && p.amount > 0);
113
647
  const sorted = active.map((p) => p.amount).sort((a, b) => a - b);
114
648
  const lower = new Map;
115
649
  sorted.forEach((x, i) => {
116
650
  if (!lower.has(x))
117
651
  lower.set(x, i);
118
652
  });
119
- return new Map(population2.map((p) => [p.threadId, p.amount === null || p.amount === 0 ? null : active.length < 2 ? 0 : lower.get(p.amount) / (active.length - 1)]));
653
+ return new Map(population3.map((p) => [p.threadId, p.amount === null || p.amount === 0 ? null : active.length < 2 ? 0 : lower.get(p.amount) / (active.length - 1)]));
120
654
  }
121
- function heat(ranks, gates = { m20: 0.9, h24: 0.9, d7: 0.9 }) {
655
+ function heat(ranks, gates2 = { m20: 0.9, h24: 0.9, d7: 0.9 }) {
122
656
  for (const w of ["m20", "h24", "d7"])
123
- if (!Number.isFinite(gates[w]) || gates[w] < 0 || gates[w] > 1)
657
+ if (!Number.isFinite(gates2[w]) || gates2[w] < 0 || gates2[w] > 1)
124
658
  throw new Error("Invalid gate");
125
- return ranks.m20 !== null && ranks.m20 >= gates.m20 ? "on-fire" : ranks.h24 !== null && ranks.h24 >= gates.h24 ? "hot" : ranks.d7 !== null && ranks.d7 >= gates.d7 ? "heavy" : null;
659
+ return ranks.m20 !== null && ranks.m20 >= gates2.m20 ? "on-fire" : ranks.h24 !== null && ranks.h24 >= gates2.h24 ? "hot" : ranks.d7 !== null && ranks.d7 >= gates2.d7 ? "heavy" : null;
126
660
  }
127
661
  function familyClosure(edges, roots, policy = {}) {
128
662
  const cap = (n, fallback) => {
@@ -222,7 +756,7 @@ function validateDetailResult(input, value) {
222
756
  let priorEnd;
223
757
  const keys = new Set;
224
758
  for (const row of out.rows) {
225
- if (row.approximation.approximate && (!out.approximation.approximate || row.approximation.reasons.some((reason) => !out.approximation.reasons.includes(reason))))
759
+ if (row.approximation.approximate && (!out.approximation.approximate || row.approximation.reasons.some((reason2) => !out.approximation.reasons.includes(reason2))))
226
760
  throw new Error("Aggregate total must retain contributing row approximation provenance");
227
761
  if (row.amount === null && !row.approximation.reasons.includes("missing-price"))
228
762
  throw new Error("Unknown dollars require missing-price provenance");
@@ -233,15 +767,15 @@ function validateDetailResult(input, value) {
233
767
  throw new Error("Invalid time detail range/order/overlap");
234
768
  priorEnd = row.end;
235
769
  }
236
- const key = row.axis === "model" ? JSON.stringify([row.providerId, row.modelId]) : String(row.start);
237
- if (keys.has(key))
770
+ const key2 = row.axis === "model" ? JSON.stringify([row.providerId, row.modelId]) : String(row.start);
771
+ if (keys.has(key2))
238
772
  throw new Error("Duplicate detail row");
239
- keys.add(key);
773
+ keys.add(key2);
240
774
  }
241
775
  if (out.coverage === "complete" && !input.cursor && !out.nextCursor) {
242
- for (const key of ["inputUncached", "cacheRead", "cacheWrite", "output"]) {
243
- const sum = out.rows.reduce((n, r) => n + r.tokens[key], 0);
244
- if (!Number.isFinite(sum) || Math.abs(sum - out.total.tokens[key]) > 0.00000001 * Math.max(1, sum, out.total.tokens[key]))
776
+ for (const key2 of ["inputUncached", "cacheRead", "cacheWrite", "output"]) {
777
+ const sum = out.rows.reduce((n, r) => n + r.tokens[key2], 0);
778
+ if (!Number.isFinite(sum) || Math.abs(sum - out.total.tokens[key2]) > 0.00000001 * Math.max(1, sum, out.total.tokens[key2]))
245
779
  throw new Error("Detail token total mismatch");
246
780
  }
247
781
  const hasUnknown = out.rows.some((r) => r.amount === null);
@@ -273,16 +807,57 @@ export {
273
807
  windowsSchema,
274
808
  windowValueSchema,
275
809
  windowSchema,
810
+ weightedScoreV2,
811
+ validateRootFamilyHeatConfig,
812
+ validateRecentSnapshot,
276
813
  validateReadResult,
814
+ validateHeatSaveInput,
815
+ validateHeatConfig,
277
816
  validateDetailResult,
278
817
  summaryRowSchema,
818
+ storedScoringConfigSchema,
819
+ scoringHeatV2,
820
+ scoringConfigV3Schema,
821
+ scoringConfigV2Schema,
822
+ rootTopologyV1Schema,
823
+ rootFamilyHeatV1,
824
+ rootFamilyHeatDomainV1Schema,
825
+ rootFamilyHeatContextV1Schema,
826
+ rootFamilyEligibilityV1,
827
+ rootFamilyDecisionSetV1Schema,
279
828
  retainedWindowStart,
829
+ reconcileHeatSave,
830
+ recentWindowsSchema,
831
+ recentUsageSchema,
832
+ recentTokenWindowsSchema,
833
+ recentTokenValueSchema,
834
+ recentSnapshotResultSchema,
835
+ recentSnapshotInputSchema,
836
+ recentSnapshotFullSchema,
837
+ recentScopeSchema,
838
+ recentRowSchema,
839
+ recentRootInputSchema,
840
+ recentRootHeatSchema,
841
+ recentPublicationIdentitySchema,
842
+ recentPresentationSchema,
843
+ recentPercentileHeat,
844
+ recentHeatInputsSchema,
845
+ recentDollarSchema,
846
+ recentAttributionSchema,
847
+ recentApproximationSchema,
280
848
  readResultSchema,
281
849
  readInputSchema,
282
850
  ranksSchema,
283
851
  rankPopulationSchema,
852
+ prepareRootFamilyMigration,
284
853
  parseBounded,
285
854
  negotiateVersion,
855
+ heatSettingsSaveResultSchema,
856
+ heatSettingsSaveInputSchema,
857
+ heatSettingsReadResultSchema,
858
+ heatSettingsReadInputSchema,
859
+ heatConfigRecordSchema,
860
+ heatConfigInputSchema,
286
861
  heat,
287
862
  familyClosure,
288
863
  dollarPercentiles,
@@ -296,9 +871,18 @@ export {
296
871
  coverageReasonSchema,
297
872
  classifyOwnerError,
298
873
  capabilitiesSchema,
874
+ canonicalHeatConfig,
299
875
  bucketSchema,
876
+ bounded,
300
877
  approximationSchema,
878
+ ROOT_FAMILY_HEAT_DOMAIN_V1,
879
+ RECENT_LIMITS,
301
880
  METRIC_BUCKET_MS,
302
881
  METHODS,
303
- LIMITS
882
+ LIMITS,
883
+ HEAT_WINDOWS,
884
+ HEAT_SETTINGS_METHODS,
885
+ HEAT_SETTINGS_LIMITS,
886
+ DEFAULT_SCORING_CONFIG_V3,
887
+ DEFAULT_SCORING_CONFIG_V2
304
888
  };