@phosphorco/bb-thread-metrics 0.2.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,5 +1,6 @@
1
1
  // src/recent.ts
2
2
  import { z } from "zod";
3
+ import { UsageVectorSchema } from "@phosphorco/bb-usage-algebra/vector";
3
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 });
4
5
  var id = z.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/);
5
6
  var revision = z.string().min(1).max(256);
@@ -16,17 +17,37 @@ var recentDollarSchema = z.discriminatedUnion("kind", [
16
17
  z.object({ kind: z.literal("unavailable"), amount: z.null() }).strip()
17
18
  ]);
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();
19
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) => {
20
27
  if (!v.approximate && v.reasons.length)
21
28
  c.addIssue({ code: "custom", message: "Approximation reasons require approximate flag" });
22
29
  });
23
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();
24
43
  var recentScopeSchema = z.object({
25
44
  memberCount: count,
26
45
  coverage,
27
46
  coverageReasons: reasons,
28
47
  priceVersion: revision,
29
48
  approximation: recentApproximationSchema,
49
+ usage: recentUsageSchema.optional(),
50
+ attribution: recentAttributionSchema.optional(),
30
51
  windows: recentWindowsSchema,
31
52
  scores: recentWindowsSchema,
32
53
  providers: z.array(provider).max(RECENT_LIMITS.providers),
@@ -38,17 +59,123 @@ var recentScopeSchema = z.object({
38
59
  c.addIssue({ code: "custom", message: "Duplicate providers" });
39
60
  if (v.omittedProviders && v.coverage === "complete")
40
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
+ }
41
83
  });
42
- var recentRowSchema = z.object({ threadId: id, revision, configRevision: revision, asOf: time, ownLastFoldAt: time.nullable(), familyLastFoldAt: time.nullable(), own: recentScopeSchema, family: recentScopeSchema }).strip();
43
84
  var recentSnapshotInputSchema = z.object({ ifRevision: revision.optional() }).strict();
44
- var thresholds = windowShape(z.number().finite().nonnegative());
45
- var weights = z.record(id, z.number().finite().nonnegative()).refine((v) => Object.keys(v).length <= RECENT_LIMITS.providers, "Too many provider weights");
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");
46
87
  var configBase = { schemaVersion: z.literal(2), providerWeights: weights };
47
88
  var scoringConfigV2Schema = z.discriminatedUnion("mode", [
48
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(),
49
- z.object({ ...configBase, mode: z.literal("absolute"), thresholds }).strip()
90
+ z.object({ ...configBase, mode: z.literal("absolute"), thresholds, lifetimeEnabled: z.boolean().optional() }).strip()
50
91
  ]);
51
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();
52
179
  function weightedScoreV2(values, providerWeights) {
53
180
  if (values.length > RECENT_LIMITS.providers)
54
181
  throw new Error("Too many score providers");
@@ -76,9 +203,11 @@ function weightedScoreV2(values, providerWeights) {
76
203
  var population = z.object({ count, coverage }).strip();
77
204
  var populations = z.object({ m20: population, h24: population, d7: population }).strip();
78
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();
79
207
  var recentSnapshotFullSchema = z.object({
80
208
  version: z.literal(1),
81
209
  revision,
210
+ publication: recentPublicationIdentitySchema.optional(),
82
211
  unchanged: z.literal(false),
83
212
  currency: z.literal("USD"),
84
213
  estimated: z.literal(true),
@@ -86,7 +215,9 @@ var recentSnapshotFullSchema = z.object({
86
215
  asOf: time,
87
216
  rankAsOf: time,
88
217
  recentWindowMs: z.literal(RECENT_LIMITS.windowMs),
89
- config: z.object({ revision, value: scoringConfigV2Schema }).strip(),
218
+ config: z.object({ revision, value: storedScoringConfigSchema }).strip(),
219
+ heat: rootFamilyHeatContextV1Schema.optional(),
220
+ heatInputs: recentHeatInputsSchema.optional(),
90
221
  coverage,
91
222
  coverageReasons: reasons,
92
223
  materializationPending: count,
@@ -101,13 +232,15 @@ function validateRecentSnapshot(input, value) {
101
232
  if (bytes(value) > RECENT_LIMITS.bytes)
102
233
  throw new Error("Snapshot byte limit");
103
234
  const raw = value;
104
- if (raw?.rows !== undefined) {
105
- if (!Array.isArray(raw.rows) || raw.rows.length > RECENT_LIMITS.rows)
106
- throw new Error("Snapshot row limit");
107
- for (const row of raw.rows)
108
- if (bytes(row) > RECENT_LIMITS.rowBytes)
109
- throw new Error("Snapshot row byte limit");
110
- }
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
+ }
111
244
  if (value && typeof value === "object" && value.unchanged === true) {
112
245
  for (const field of Object.keys(recentSnapshotFullSchema.shape))
113
246
  if (!["version", "revision", "unchanged"].includes(field) && Object.hasOwn(value, field))
@@ -119,6 +252,10 @@ function validateRecentSnapshot(input, value) {
119
252
  throw new Error("Unmatched snapshot revision");
120
253
  return out;
121
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");
122
259
  if (out.rankAsOf > out.asOf)
123
260
  throw new Error("Future rank time");
124
261
  if ((out.materializationPending || out.omittedRows.count || !out.omittedRows.exact) && out.coverage === "complete")
@@ -146,7 +283,19 @@ function validateRecentSnapshot(input, value) {
146
283
  validateProjection(row[scope], out.config.value.providerWeights);
147
284
  validateRanks(row[scope], out.population[scope], out.rankAsOf >= row.asOf);
148
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
+ }
149
296
  }
297
+ if (out.heatInputs)
298
+ validateHeatInputs(out);
150
299
  if (out.omittedRows.count && out.rows.length && out.cutoffLastFoldAt !== previousActivity)
151
300
  throw new Error("Missing eviction cutoff");
152
301
  return out;
@@ -173,6 +322,21 @@ function validateProjection(projection, providerWeights) {
173
322
  const incomplete = projection.omittedProviders > 0 || known.length !== projection.providers.length;
174
323
  const floor = incomplete || known.some((p) => p.windows[window].kind === "floor");
175
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
+ }
176
340
  for (const [total, subtotal, label] of [[projection.windows[window], dollars, "dollar"], [projection.scores[window], score, "score"]]) {
177
341
  if (total.kind === "unavailable")
178
342
  continue;
@@ -197,21 +361,180 @@ function validateRanks(projection, population2, contemporaneous) {
197
361
  }
198
362
  }
199
363
  }
200
- function recentPercentileHeat(projection, gates = { m20: 0.9, h24: 0.9, d7: 0.9 }) {
364
+ function recentPercentileHeat(projection, gates = { m20: 0.9, h24: 0.9, d7: 0.9 }, population2) {
201
365
  for (const w of ["m20", "h24", "d7"])
202
366
  if (!Number.isFinite(gates[w]) || gates[w] < 0 || gates[w] > 1)
203
367
  throw new Error("Invalid gate");
204
368
  for (const [w, label] of [["m20", "on-fire"], ["h24", "hot"], ["d7", "heavy"]]) {
205
- const amount = projection.scores[w].amount, r = projection.scoreRanks[w];
206
- if (amount !== null && amount > 0 && r !== null && r >= gates[w])
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])
207
371
  return label;
208
372
  }
209
373
  return null;
210
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
+ }
211
534
 
212
535
  // src/index.ts
213
- import { z as z2 } from "zod";
214
- import { UsageVectorSchema } from "@phosphorco/bb-usage-algebra/vector";
536
+ import { z as z3 } from "zod";
537
+ import { UsageVectorSchema as UsageVectorSchema2 } from "@phosphorco/bb-usage-algebra/vector";
215
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 });
216
539
  var METRIC_BUCKET_MS = 300000;
217
540
  var WINDOW_BUCKETS = { m20: 4, h24: 288, d7: 2016, lifetime: 2016 };
@@ -221,19 +544,19 @@ function retainedWindowStart(asOf, window) {
221
544
  return Math.max(0, Math.floor(asOf / METRIC_BUCKET_MS) * METRIC_BUCKET_MS - (WINDOW_BUCKETS[window] - 1) * METRIC_BUCKET_MS);
222
545
  }
223
546
  var METHODS = Object.freeze({ describe: "threadMetricsDescribe", read: "threadMetricsReadV1", detail: "threadMetricsDetailV1", recentSnapshot: "threadMetricsRecentSnapshotV1" });
224
- var id2 = z2.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/);
225
- var text = z2.string().min(1).max(256);
226
- var time2 = z2.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
227
- var money = z2.number().finite().nonnegative().nullable();
228
- var scope = z2.enum(["own", "family"]);
229
- var windowSchema = z2.enum(["m20", "h24", "d7", "lifetime"]);
230
- var coverageSchema = z2.enum(["complete", "partial", "unknown"]);
231
- var coverageReasonSchema = z2.enum(["source-gap", "root-unattested", "history-unavailable", "member-cap", "depth-cap", "edge-cap", "missing-thread", "unpriced", "retention", "row-cap", "pagination", "population-incomplete"]);
232
- var reasons2 = z2.array(coverageReasonSchema).max(16);
233
- var approximationSchema = z2.object({
234
- approximate: z2.boolean(),
235
- reasons: z2.array(z2.enum(["closest-model", "provider-default", "model-attribution", "bucket-boundary", "missing-price", "source-gap"])).max(6),
236
- rates: z2.array(z2.object({ providerId: id2, modelId: text, rateId: text.nullable(), basis: z2.enum(["matched", "closest-model", "provider-default", "unpriced"]) }).superRefine((rate, ctx) => {
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) => {
237
560
  if (rate.basis === "unpriced" !== (rate.rateId === null))
238
561
  ctx.addIssue({ code: "custom", path: ["rateId"], message: "Priced basis requires rateId; unpriced basis requires null" });
239
562
  })).max(32).optional()
@@ -241,40 +564,40 @@ var approximationSchema = z2.object({
241
564
  if (!v.approximate && (v.reasons.length || v.rates?.some((r) => r.basis !== "matched")))
242
565
  ctx.addIssue({ code: "custom", message: "Approximation provenance requires approximate flag" });
243
566
  });
244
- var rank2 = z2.number().finite().min(0).max(1).nullable();
245
- var ranksSchema = z2.object({ m20: rank2, h24: rank2, d7: rank2 }).strip();
246
- var population2 = z2.object({ asOf: time2, count: z2.number().int().nonnegative(), coverage: coverageSchema }).strip();
247
- var rankPopulationSchema = z2.object({ m20: population2, h24: population2, d7: population2 }).strip();
248
- var windowValueSchema = z2.object({ tokens: UsageVectorSchema, amount: money }).strip();
249
- var windowsSchema = z2.object({ m20: windowValueSchema, h24: windowValueSchema, d7: windowValueSchema, lifetime: windowValueSchema }).strip();
250
- var bucketSchema = z2.object({ start: time2, 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();
251
574
  var metadata = {
252
575
  threadId: id2,
253
576
  scope,
254
- memberCount: z2.number().int().nonnegative().max(LIMITS.familyMembers),
577
+ memberCount: z3.number().int().nonnegative().max(LIMITS.familyMembers),
255
578
  coverage: coverageSchema,
256
579
  coverageReasons: reasons2,
257
- excluded: z2.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
580
+ excluded: z3.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
258
581
  asOf: time2,
259
582
  revision: text,
260
583
  priceVersion: text,
261
- currency: z2.literal("USD"),
262
- estimated: z2.literal(true),
584
+ currency: z3.literal("USD"),
585
+ estimated: z3.literal(true),
263
586
  approximation: approximationSchema,
264
587
  ranks: ranksSchema,
265
588
  rankPopulation: rankPopulationSchema,
266
- reported: z2.object({ amount: z2.number().finite().nonnegative(), currency: z2.string().min(1).max(16), asOf: time2 }).strip().optional()
589
+ reported: z3.object({ amount: z3.number().finite().nonnegative(), currency: z3.string().min(1).max(16), asOf: time2 }).strip().optional()
267
590
  };
268
- var summaryRowSchema = z2.discriminatedUnion("changed", [
269
- z2.object({ ...metadata, changed: z2.literal(true), windows: windowsSchema, buckets: z2.array(bucketSchema).max(LIMITS.seriesPoints).optional() }).strip(),
270
- z2.object({ ...metadata, changed: z2.literal(false), windows: z2.never().optional(), buckets: z2.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()
271
594
  ]);
272
- var readInputSchema = z2.object({
273
- threadIds: z2.array(id2).min(1).max(LIMITS.threadIds),
595
+ var readInputSchema = z3.object({
596
+ threadIds: z3.array(id2).min(1).max(LIMITS.threadIds),
274
597
  scope,
275
- includeArchived: z2.boolean().optional(),
276
- knownRevisions: z2.record(id2, text).optional(),
277
- series: z2.object({ activeThreadId: id2, maxPoints: z2.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()
278
601
  }).strict().superRefine((v, ctx) => {
279
602
  if (new Set(v.threadIds).size !== v.threadIds.length)
280
603
  ctx.addIssue({ code: "custom", message: "Duplicate thread IDs" });
@@ -283,17 +606,17 @@ var readInputSchema = z2.object({
283
606
  if (v.knownRevisions && Object.keys(v.knownRevisions).some((k) => !v.threadIds.includes(k)))
284
607
  ctx.addIssue({ code: "custom", message: "Unrequested revision key" });
285
608
  });
286
- var readResultSchema = z2.object({ version: z2.literal(1), rows: z2.array(summaryRowSchema).max(LIMITS.threadIds) }).strip();
287
- var detailInputSchema = z2.object({ threadId: id2, scope, includeArchived: z2.boolean().optional(), axis: z2.enum(["model", "time"]), window: windowSchema, cursor: z2.string().min(1).max(1024).optional(), limit: z2.number().int().min(1).max(LIMITS.detailRows).optional() }).strict();
288
- var detailBase = { amount: money, tokens: UsageVectorSchema, approximation: approximationSchema };
289
- var detailRowSchema = z2.discriminatedUnion("axis", [
290
- z2.object({ ...detailBase, axis: z2.literal("model"), providerId: id2, modelId: text }).strip(),
291
- z2.object({ ...detailBase, axis: z2.literal("time"), start: time2, end: time2 }).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" })
292
615
  ]);
293
- var detailResultSchema = z2.object({ version: z2.literal(1), threadId: id2, scope, axis: z2.enum(["model", "time"]), window: windowSchema, asOf: time2, revision: text, priceVersion: text, currency: z2.literal("USD"), estimated: z2.literal(true), coverage: coverageSchema, coverageReasons: reasons2, excluded: z2.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(), approximation: approximationSchema, total: windowValueSchema, rows: z2.array(detailRowSchema).max(LIMITS.detailRows), nextCursor: z2.string().min(1).max(1024).optional() }).strip();
294
- var describeInputSchema = z2.object({}).strict();
295
- var describeEnvelopeSchema = z2.object({ protocol: z2.literal("bb-thread-metrics"), versions: z2.array(z2.number().int().positive()).max(16) }).passthrough();
296
- var capabilitiesSchema = z2.object({ summary: z2.literal(true), series: z2.boolean(), detail: z2.boolean(), recentSnapshotV1: z2.boolean().optional() }).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();
297
620
  var describeV1Schema = describeEnvelopeSchema.extend({ capabilities: capabilitiesSchema });
298
621
  function parseBounded(schema, value, maxBytes) {
299
622
  const json = JSON.stringify(value);
@@ -329,11 +652,11 @@ function dollarPercentiles(population3) {
329
652
  });
330
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)]));
331
654
  }
332
- 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 }) {
333
656
  for (const w of ["m20", "h24", "d7"])
334
- if (!Number.isFinite(gates[w]) || gates[w] < 0 || gates[w] > 1)
657
+ if (!Number.isFinite(gates2[w]) || gates2[w] < 0 || gates2[w] > 1)
335
658
  throw new Error("Invalid gate");
336
- 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;
337
660
  }
338
661
  function familyClosure(edges, roots, policy = {}) {
339
662
  const cap = (n, fallback) => {
@@ -444,15 +767,15 @@ function validateDetailResult(input, value) {
444
767
  throw new Error("Invalid time detail range/order/overlap");
445
768
  priorEnd = row.end;
446
769
  }
447
- const key = row.axis === "model" ? JSON.stringify([row.providerId, row.modelId]) : String(row.start);
448
- if (keys.has(key))
770
+ const key2 = row.axis === "model" ? JSON.stringify([row.providerId, row.modelId]) : String(row.start);
771
+ if (keys.has(key2))
449
772
  throw new Error("Duplicate detail row");
450
- keys.add(key);
773
+ keys.add(key2);
451
774
  }
452
775
  if (out.coverage === "complete" && !input.cursor && !out.nextCursor) {
453
- for (const key of ["inputUncached", "cacheRead", "cacheWrite", "output"]) {
454
- const sum = out.rows.reduce((n, r) => n + r.tokens[key], 0);
455
- 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]))
456
779
  throw new Error("Detail token total mismatch");
457
780
  }
458
781
  const hasUnknown = out.rows.some((r) => r.amount === null);
@@ -485,27 +808,56 @@ export {
485
808
  windowValueSchema,
486
809
  windowSchema,
487
810
  weightedScoreV2,
811
+ validateRootFamilyHeatConfig,
488
812
  validateRecentSnapshot,
489
813
  validateReadResult,
814
+ validateHeatSaveInput,
815
+ validateHeatConfig,
490
816
  validateDetailResult,
491
817
  summaryRowSchema,
818
+ storedScoringConfigSchema,
819
+ scoringHeatV2,
820
+ scoringConfigV3Schema,
492
821
  scoringConfigV2Schema,
822
+ rootTopologyV1Schema,
823
+ rootFamilyHeatV1,
824
+ rootFamilyHeatDomainV1Schema,
825
+ rootFamilyHeatContextV1Schema,
826
+ rootFamilyEligibilityV1,
827
+ rootFamilyDecisionSetV1Schema,
493
828
  retainedWindowStart,
829
+ reconcileHeatSave,
494
830
  recentWindowsSchema,
831
+ recentUsageSchema,
832
+ recentTokenWindowsSchema,
833
+ recentTokenValueSchema,
495
834
  recentSnapshotResultSchema,
496
835
  recentSnapshotInputSchema,
497
836
  recentSnapshotFullSchema,
498
837
  recentScopeSchema,
499
838
  recentRowSchema,
839
+ recentRootInputSchema,
840
+ recentRootHeatSchema,
841
+ recentPublicationIdentitySchema,
842
+ recentPresentationSchema,
500
843
  recentPercentileHeat,
844
+ recentHeatInputsSchema,
501
845
  recentDollarSchema,
846
+ recentAttributionSchema,
502
847
  recentApproximationSchema,
503
848
  readResultSchema,
504
849
  readInputSchema,
505
850
  ranksSchema,
506
851
  rankPopulationSchema,
852
+ prepareRootFamilyMigration,
507
853
  parseBounded,
508
854
  negotiateVersion,
855
+ heatSettingsSaveResultSchema,
856
+ heatSettingsSaveInputSchema,
857
+ heatSettingsReadResultSchema,
858
+ heatSettingsReadInputSchema,
859
+ heatConfigRecordSchema,
860
+ heatConfigInputSchema,
509
861
  heat,
510
862
  familyClosure,
511
863
  dollarPercentiles,
@@ -519,11 +871,18 @@ export {
519
871
  coverageReasonSchema,
520
872
  classifyOwnerError,
521
873
  capabilitiesSchema,
874
+ canonicalHeatConfig,
522
875
  bucketSchema,
876
+ bounded,
523
877
  approximationSchema,
878
+ ROOT_FAMILY_HEAT_DOMAIN_V1,
524
879
  RECENT_LIMITS,
525
880
  METRIC_BUCKET_MS,
526
881
  METHODS,
527
882
  LIMITS,
883
+ HEAT_WINDOWS,
884
+ HEAT_SETTINGS_METHODS,
885
+ HEAT_SETTINGS_LIMITS,
886
+ DEFAULT_SCORING_CONFIG_V3,
528
887
  DEFAULT_SCORING_CONFIG_V2
529
888
  };