@phosphorco/bb-thread-metrics 0.0.0-stage → 0.2.0
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/CONTRACT.md +400 -0
- package/LICENSE +21 -0
- package/README.md +57 -2
- package/dist/bb.js +208 -0
- package/dist/index.js +529 -0
- package/dist/testing.js +92 -0
- package/dist/types/bb.d.ts +710 -0
- package/dist/types/index.d.ts +944 -0
- package/dist/types/recent.d.ts +1661 -0
- package/dist/types/testing.d.ts +21 -0
- package/package.json +22 -4
package/CONTRACT.md
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
# Thread metrics wire v1 (@phosphorco/bb-thread-metrics 0.2.0 candidate)
|
|
2
|
+
|
|
3
|
+
| Supplier owns | Package owns | Host owns |
|
|
4
|
+
| --- | --- | --- |
|
|
5
|
+
| Ledger, edge index, pricing data, observation/retention, source coverage and revisions | Bounded DTOs, dual-edge closure, dollar percentile/heat, fixed RPC, injected adapters and conformance | Discovery triggers, demand batching, caching, navigation, thread panel rendering and recovery |
|
|
6
|
+
|
|
7
|
+
The package is pure data and transport, without storage, collectors, pricing seeds,
|
|
8
|
+
React, polling or lifecycle scheduling. Root uses Zod and the seed-free algebra
|
|
9
|
+
`/vector` entry. `/bb` accepts injected SDK objects; `/testing` exercises real
|
|
10
|
+
registered handlers on the SDK fake host.
|
|
11
|
+
|
|
12
|
+
## API sketch (cross-lane alignment)
|
|
13
|
+
|
|
14
|
+
- `familyClosure(edges, roots, policy?)`: union of parent and lifecycle-owner
|
|
15
|
+
links, reflexive, cycle/diamond safe, hidden always included, archived default
|
|
16
|
+
true, deleted excluded. Explicit input/member/depth caps and coverage reasons.
|
|
17
|
+
- `dollarPercentiles(population)`: per-window host-wide active dollar
|
|
18
|
+
population (finite positive amounts only); ties use strict-lower-count / (N-1).
|
|
19
|
+
Singleton and all-equal populations rank 0; no median mark. Zero, unknown and
|
|
20
|
+
inactive get null. `heat(ranks)` gates m20/on-fire, h24/hot, d7/heavy at >=0.9.
|
|
21
|
+
- `threadMetricsDescribe({})`: `{protocol:'bb-thread-metrics',versions:[1],
|
|
22
|
+
capabilities:{summary:true,series:boolean,detail:boolean}}`. Negotiate envelope
|
|
23
|
+
before decoding v1 capabilities; unknown output fields tolerated.
|
|
24
|
+
- `threadMetricsReadV1({threadIds,scope,includeArchived?,knownRevisions?,
|
|
25
|
+
series?:{activeThreadId,maxPoints}})` <=200 unique IDs. A single active ID may
|
|
26
|
+
request last-24h buckets, <=288. Summary rows never contain descendant records.
|
|
27
|
+
- `threadMetricsDetailV1({threadId,scope,includeArchived?,axis,window,cursor?,limit?})`
|
|
28
|
+
is optional, explicitly on demand. Axis `model|time`; bounded rows,
|
|
29
|
+
optional nextCursor, total and coverage metadata. No summary fanout.
|
|
30
|
+
|
|
31
|
+
Every summary row has threadId/scope/memberCount/coverage/coverageReasons/asOf/
|
|
32
|
+
revision/priceVersion/currency:'USD'/estimated:true/approximation/ranks. Changed
|
|
33
|
+
rows add windows `{m20,h24,d7,lifetime}` of `{tokens:UsageVector,amount:number|null}`;
|
|
34
|
+
unchanged rows omit windows but still carry current metadata and ranks. Revisions
|
|
35
|
+
must fingerprint scope/archive policy/window content/series demand; a producer
|
|
36
|
+
may return unchanged only for a matching known revision. Consumers merge the
|
|
37
|
+
fresh metadata even when unchanged. Price corrections and sliding windows must
|
|
38
|
+
invalidate content revisions. Approximation has bounded reasons and optional
|
|
39
|
+
provider/model/rate attribution (matched, closest-model, provider-default,
|
|
40
|
+
unpriced); missing price is last resort. Provider reported costs remain separate.
|
|
41
|
+
|
|
42
|
+
Inputs are strict and byte bounded; outputs strip additive fields and enforce
|
|
43
|
+
byte bounds before decoding. Suppliers echo only requested IDs once each; missing
|
|
44
|
+
IDs are partial coverage, never implicit zero. Detail rows reconcile to the stated
|
|
45
|
+
total only when complete; pagination and row limits are explicit. USD ranks must
|
|
46
|
+
be computed from the host-wide dollar population using the same scope and archived
|
|
47
|
+
policy, never token totals or the visible/requested set. Shared descendants count
|
|
48
|
+
once per root. Overlap across separate family roots is comparative data and must
|
|
49
|
+
never be summed into host spend. Ranks carry population asOf/count metadata per window.
|
|
50
|
+
|
|
51
|
+
Discovery lists once, deduplicates target IDs, probes bounded candidates using
|
|
52
|
+
bounded concurrency, per-call and overall deadlines. Aborting a probe does not
|
|
53
|
+
release its slot until its actual SDK promise settles. A deadline ends the pass;
|
|
54
|
+
late results cannot publish. Failures are independent, keyed by target plugin ID.
|
|
55
|
+
Mount/refresh/reconnect/plugin-lifecycle triggers belong to the consumer; no poll.
|
|
56
|
+
|
|
57
|
+
## Wire details and producer obligations
|
|
58
|
+
|
|
59
|
+
Timestamps are Unix milliseconds, finite safe nonnegative integers. Revisions
|
|
60
|
+
and price versions are nonempty strings <=256 characters. Thread/provider IDs
|
|
61
|
+
are <=200 characters, begin alphanumeric and contain only letters, digits,
|
|
62
|
+
underscore, period, colon or hyphen. Model and rate IDs are <=256 characters.
|
|
63
|
+
Amounts are finite nonnegative USD values or null. Null is unknown, never zero;
|
|
64
|
+
null estimates require `missing-price` approximation provenance. `reported`
|
|
65
|
+
uses its own currency and timestamp and is never added to estimated amounts.
|
|
66
|
+
|
|
67
|
+
Coverage is `complete|partial|unknown`; closed reasons are `source-gap`, `root-unattested`,
|
|
68
|
+
`history-unavailable`, `member-cap`, `depth-cap`, `edge-cap`, `missing-thread`,
|
|
69
|
+
`unpriced`, `retention`, `row-cap`, `pagination`, `population-incomplete`.
|
|
70
|
+
Approximation reasons are `closest-model`, `provider-default`,
|
|
71
|
+
`model-attribution`, `bucket-boundary`, `missing-price`, `source-gap`.
|
|
72
|
+
Nonempty approximation reasons or unmatched rate attribution require the
|
|
73
|
+
`approximate` flag. Each summary carries <=32 rate attribution records, not an
|
|
74
|
+
unbounded catalog; a supplier may omit that optional list while retaining reasons.
|
|
75
|
+
|
|
76
|
+
Family closure scans <=50,000 edge records and emits <=10,000 members to depth
|
|
77
|
+
<=256. Policy may lower each cap; it may never increase a package bound. Roots
|
|
78
|
+
are <=200. Duplicate edge identities are invalid. A missing root downgrades
|
|
79
|
+
coverage and contributes no invented member. Archived nodes remain traversable
|
|
80
|
+
when excluded from membership, so a live grandchild remains reachable. Deleted
|
|
81
|
+
nodes and traversal through them are excluded. Family results are traversal-order
|
|
82
|
+
arrays and callers must not infer rank from their order. Source completeness is
|
|
83
|
+
an explicit policy input: a complete closure only proves the supplied snapshot.
|
|
84
|
+
|
|
85
|
+
Dollar population input is explicitly supplied, one amount per distinct thread.
|
|
86
|
+
Amounts must match the requested scope/archive policy at a common snapshot.
|
|
87
|
+
Unknown and zero-dollar threads receive null rank and do not enter the active
|
|
88
|
+
priced population. For N positive known-dollar threads, rank is the number of
|
|
89
|
+
strictly lower amounts divided by N-1; N=1 yields zero. Thus equal peers share
|
|
90
|
+
rank, an all-equal population ranks zero, a unique maximum ranks one, and a
|
|
91
|
+
median thread receives no default heat. Unknown-priced active threads mean
|
|
92
|
+
population coverage is incomplete and must be disclosed. Per-window
|
|
93
|
+
`rankPopulation` contains priced-active count, asOf and coverage. The consumer
|
|
94
|
+
may suppress heat on incomplete coverage without replacing null dollars with zero.
|
|
95
|
+
|
|
96
|
+
Summary output is `{version:1,rows}`. Read omissions mean unavailable data;
|
|
97
|
+
consumers retain prior values as stale or show unavailable. An omitted row is
|
|
98
|
+
never zero spend. A changed row carries windows and optionally buckets;
|
|
99
|
+
unchanged carries current metadata/ranks only. Recognized `windows` or `buckets`
|
|
100
|
+
content is rejected on unchanged rows before output stripping, even if series was
|
|
101
|
+
requested. Truly unknown additive output fields are stripped. With no series requested the registration adapter strips no known
|
|
102
|
+
bucket field silently: it rejects it. The supplier must avoid computing raw
|
|
103
|
+
breakdowns for summaries; no descendant/model detail field exists in that DTO.
|
|
104
|
+
Buckets have aligned five-minute starts in the last 288 fixed buckets including
|
|
105
|
+
the current bucket at asOf, are strictly increasing, never future, and never
|
|
106
|
+
exceed the requested point count. Null bucket dollars require top-level
|
|
107
|
+
`missing-price` approximation provenance, including when all windows are priced. The input explicitly names
|
|
108
|
+
one active requested thread. The package cannot independently verify UI focus.
|
|
109
|
+
|
|
110
|
+
Known revisions are bounded by requested IDs and must refer to content for that
|
|
111
|
+
exact scope/archive/series projection. Ingest revision alone is insufficient:
|
|
112
|
+
the revision MUST change with every input capable of changing membership or
|
|
113
|
+
sums: rolling-window/bucket expiry, rate table generation, scope/archive/series
|
|
114
|
+
policy, edge/index revision, membership evidence and retention all participate.
|
|
115
|
+
A change to an input must change revision even if the current numeric sum happens
|
|
116
|
+
to remain equal. A source unable to establish this revision must disclose unknown
|
|
117
|
+
coverage and cannot safely reuse a known revision for an unchanged answer. An unchanged answer still updates asOf,
|
|
118
|
+
coverage, approximation and rank metadata in the consumer cache. Suppliers must
|
|
119
|
+
compare cheap projection revisions before expensive aggregation; the package
|
|
120
|
+
validates the echoed revision and shape but cannot inspect supplier storage or
|
|
121
|
+
prove that comparison cost.
|
|
122
|
+
|
|
123
|
+
Detail outputs echo thread/scope/axis/window and include asOf, revision,
|
|
124
|
+
priceVersion, currency, estimated, coverage, approximation, total, rows and an
|
|
125
|
+
optional nextCursor. Rows carry tokens, amount and approximation, plus either
|
|
126
|
+
providerId/modelId or start/end. Limits default to 200; cursors <=1024
|
|
127
|
+
characters. Identities are unique per page. Complete unpaginated detail must
|
|
128
|
+
reconcile all four vector components and dollars to total (relative tolerance
|
|
129
|
+
1e-8); null row dollars require a null total. A null detail total always requires
|
|
130
|
+
top-level `missing-price` approximation provenance, even on partial/unknown pages
|
|
131
|
+
with no rows. Pagination must disclose partial
|
|
132
|
+
coverage. Total describes the whole requested projection, not just the page.
|
|
133
|
+
Across pages the supplier must keep one revision stable or reject/restart its
|
|
134
|
+
cursor. Consumers must dedup page identities and discard mixed revisions.
|
|
135
|
+
Model rows aggregate by provider/model after deduplicating family members; time
|
|
136
|
+
rows partition the requested retained window without overlap. Sparse unoccupied
|
|
137
|
+
gaps are allowed and never imply invented spend. Source
|
|
138
|
+
observation/model/bucket uncertainty remains in coverage/approximation.
|
|
139
|
+
|
|
140
|
+
## Runtime adapters and admission
|
|
141
|
+
|
|
142
|
+
`registerThreadMetricsOwner(bb, port)` registers strict byte-admitted input
|
|
143
|
+
schemas and validates all responses. Port has capabilities, async read, and
|
|
144
|
+
optional async detail; detail capability must match the handler. Describe/read
|
|
145
|
+
are always registered; detail is registered only when supported. All use
|
|
146
|
+
`experimental_discoverable:true`; no experimental discovery endpoint is called.
|
|
147
|
+
SDK imports in `/bb` are type-only. Public SDK server and browser types are
|
|
148
|
+
accepted by client and discovery helpers.
|
|
149
|
+
|
|
150
|
+
`createThreadMetricsClient(sdk, pluginId)` binds every call to that target.
|
|
151
|
+
Describe negotiates wire 1 (highest shared supported wire version) before decoding
|
|
152
|
+
capabilities. Fixed read/detail methods are v1; discovery must precede consumer
|
|
153
|
+
use and optional capabilities must be honored. Native errors are classified:
|
|
154
|
+
404/unknown_method -> absent; 503 -> disabled; AbortError -> aborted;
|
|
155
|
+
TimeoutError -> timeout; ZodError -> malformed; other faults -> failed.
|
|
156
|
+
|
|
157
|
+
`createThreadMetricsDiscovery(sdk)` is one retained per-mounted-coordinator
|
|
158
|
+
resource. Its `discover(options?)` lists once and probes only running/degraded
|
|
159
|
+
plugins, deduped by target ID. It examines <=10,000 listed entries, probes <=128
|
|
160
|
+
candidates, with <=4 actual outstanding probe reads, <=2000ms per probe and a
|
|
161
|
+
<=5000ms overall deadline. Options can lower all budgets. Truncated/unstarted
|
|
162
|
+
candidates are counted in omittedCount and complete is false. This bounds work
|
|
163
|
+
after the SDK list materializes; the SDK owns list transport bytes and its native
|
|
164
|
+
list response is already materialized before this package can inspect it.
|
|
165
|
+
|
|
166
|
+
A local probe timeout aborts its signal and publishes timeout, but its lane stays
|
|
167
|
+
occupied until the actual promise settles. Overall deadline returns a result and
|
|
168
|
+
suppresses late publication. The coordinator tracks actual list/probe settlement
|
|
169
|
+
across passes, exposing `outstandingReads`. A new pass while running or draining
|
|
170
|
+
returns `listError:'busy'` and performs no I/O. Consumers must retain the same
|
|
171
|
+
coordinator over refresh/reconnect; recreating one bypasses admission. There is
|
|
172
|
+
no global registry or singleton, no automatic retry, queue or poll. Abort stops
|
|
173
|
+
publication, and failures remain per-target; callback faults do not discard
|
|
174
|
+
other target results. A genuinely unresponsive SDK leaves this coordinator busy
|
|
175
|
+
until that SDK read settles; this is honest backpressure, not proof of remote
|
|
176
|
+
cancellation. Consumers retain their last good snapshot during busy/failure.
|
|
177
|
+
|
|
178
|
+
Input byte limit is 64 KiB, all read/detail output 2 MiB, describe 16 KiB.
|
|
179
|
+
UTF-8 JSON size is checked before schema decoding (including additive fields).
|
|
180
|
+
SDK response objects are already materialized before this check. DTO/schema
|
|
181
|
+
admission does not claim to bound remote transport memory or arbitrary supplier
|
|
182
|
+
work. Output schemas strip additive fields and inputs reject unknown fields.
|
|
183
|
+
|
|
184
|
+
## Verification and remaining proof
|
|
185
|
+
|
|
186
|
+
`bun run test` builds JS and tsc declarations, then imports emitted public entries.
|
|
187
|
+
`bun run typecheck` checks source, and `tsc -p tsconfig.public-tests.json --noEmit`
|
|
188
|
+
checks emitted public types with both SDK environments. `npm pack --dry-run
|
|
189
|
+
--json` inventories the package. No dist is committed.
|
|
190
|
+
|
|
191
|
+
The `/testing` kit registers the adopter's real factory on createFakePluginHost,
|
|
192
|
+
then exercises negotiated describe, representative read/detail cases, strict
|
|
193
|
+
input rejection, count bounds and response semantics through real registered
|
|
194
|
+
handlers. Advertised detail requires detail cases. Every check is locally
|
|
195
|
+
bounded and the report identifies failures; registration/storage behavior and
|
|
196
|
+
live network cancellation are not proven by the kit. The kit does not generate
|
|
197
|
+
a replacement handler or claim exhaustive semantic proof. The supplier must add
|
|
198
|
+
its own rolling-expiry, rate-change, retained-revision, paginated consistency,
|
|
199
|
+
edge-index completeness and dollar-population tests. Host integration tests must
|
|
200
|
+
prove absence/failure/recovery/caching and navigation; live composition and registry
|
|
201
|
+
publication remain separate steward obligations. Optional SDK peers describe the
|
|
202
|
+
intended range; local checks use installed SDK 0.6.52+phosphor, not every version.
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
## Membership boundary and time clipping (owner rulings)
|
|
206
|
+
|
|
207
|
+
Shared metrics detail supports **model and time only**. It does not enumerate
|
|
208
|
+
members or offer descendant spend rows over RPC. The pure `familyClosure` export
|
|
209
|
+
is reusable internal algebra over an explicitly supplied edge snapshot, not a
|
|
210
|
+
membership discovery endpoint. `readV1` exposes memberCount only. Descendant
|
|
211
|
+
spend/member listing belongs to a ledger-specific internal debrief RPC pending
|
|
212
|
+
adoption of the existing `bb-thread-membership` protocol after stable publication.
|
|
213
|
+
No membership package dependency is added while its exact registry version is
|
|
214
|
+
unavailable. That protocol owns opaque scopes, member enumeration, revision-fenced
|
|
215
|
+
paging and direct/inherited attribution; metrics must not duplicate it.
|
|
216
|
+
|
|
217
|
+
Coverage vocabulary follows membership: `complete|partial|unknown`. Summary and
|
|
218
|
+
detail may include `excluded`, a nonnegative safe integer counting members/rows
|
|
219
|
+
known to be excluded under the supplier's projection. Omit it when not countable;
|
|
220
|
+
its absence never implies zero. Coverage reasons retain `source-gap` and describe
|
|
221
|
+
why data is partial. All membership/sum input revision duties above are mandatory.
|
|
222
|
+
|
|
223
|
+
Time detail uses five-minute fixed buckets, consistent with usage-algebra's
|
|
224
|
+
last-N windows. For asOf t, B=300000, and N=4/288/2016 for m20/h24/d7:
|
|
225
|
+
`lower=max(0,floor(t/B)*B-(N-1)*B)`. Lifetime time detail is limited to the same
|
|
226
|
+
retained 2016 buckets as d7; a lifetime total including earlier history therefore
|
|
227
|
+
needs partial retention coverage rather than claiming complete reconciliation.
|
|
228
|
+
`retainedWindowStart(asOf,window)` exposes this lower bound. Summary series uses
|
|
229
|
+
the same boundary with N=288.
|
|
230
|
+
|
|
231
|
+
Each time interval is half-open `[start,end)`. Start is aligned to B, at least
|
|
232
|
+
lower and strictly less than asOf. End is an aligned bucket boundary or exactly
|
|
233
|
+
asOf, is greater than start and no greater than asOf. Coarser groups spanning
|
|
234
|
+
multiple buckets are allowed. The current interval **must be clipped to asOf**;
|
|
235
|
+
it must not label future time up to the next bucket boundary. At an exact bucket
|
|
236
|
+
boundary the new current bucket contributes zero duration and its zero-width
|
|
237
|
+
interval is omitted. Within each page rows must be sorted by start and disjoint
|
|
238
|
+
(`next.start >= previous.end`); gaps are allowed. The validator enforces these
|
|
239
|
+
rules independently of whether dollar/vector totals happen to reconcile.
|
|
240
|
+
Cross-page interval ordering, completeness and revision consistency remain the
|
|
241
|
+
supplier/consumer paging obligation.
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
## Unattested root evidence (initial 0.1.0 owner ruling)
|
|
245
|
+
|
|
246
|
+
`root-unattested` means the supplier cannot attest the root thread's own usage
|
|
247
|
+
evidence. It is distinct from `source-gap` and general retention: observed
|
|
248
|
+
family/descendant spend does not establish the missing root's spend. The ledger
|
|
249
|
+
owns detection, omission/floor policy and later recovery; this contract adds no
|
|
250
|
+
membership enumeration or root-policy decision.
|
|
251
|
+
|
|
252
|
+
For partial coverage with `root-unattested`, positive reported dollars are an
|
|
253
|
+
observed lower bound (floor). Consumers phrase that value **"at least $X"**,
|
|
254
|
+
not a complete spend total. Heat compares these floor dollar amounts under the
|
|
255
|
+
same population policy, preserving partial population coverage; it must not
|
|
256
|
+
invent missing root dollars or substitute tokens. Zero observed root evidence
|
|
257
|
+
must not be shown as zero actual spend: omit the unsupported spend result or
|
|
258
|
+
render it unavailable, retaining any previous good value only as explicitly stale.
|
|
259
|
+
A missing/unattested root is never evidence that actual spend was $0.
|
|
260
|
+
|
|
261
|
+
When root evidence is later attested, the ledger reconciles it idempotently,
|
|
262
|
+
changes the projection revision when the membership/sum input changes and clears
|
|
263
|
+
`root-unattested` only after the matching evidence is recovered. The consumer
|
|
264
|
+
replaces its floor/unavailable state from the authoritative response. Actual
|
|
265
|
+
recovery/deduplication and UI wording implementations are separate adopter work;
|
|
266
|
+
this package supplies the reason codec and preserves observed values unchanged.
|
|
267
|
+
|
|
268
|
+
## Byte conformance and aggregate approximation hardening
|
|
269
|
+
|
|
270
|
+
The conformance kit admits every positive read/detail case with `parseBounded`
|
|
271
|
+
before invoking a handler; shape-only parsing is insufficient. Every supplier
|
|
272
|
+
must reject the mandatory shape-valid oversized read input: 200 unique IDs of
|
|
273
|
+
200 characters plus a 256-character known revision per ID (133051 UTF-8 JSON
|
|
274
|
+
bytes, above the 65536-byte input cap). Count/field validation alone accepts this
|
|
275
|
+
input, so the kit requires a structured invalid_input or Zod validation failure
|
|
276
|
+
from the real registered handler boundary. A raw-schema supplier that omits byte
|
|
277
|
+
admission fails source conformance even if all ordinary reads work. This proves
|
|
278
|
+
local handler admission, not a limit on the SDK's prior transport allocation.
|
|
279
|
+
|
|
280
|
+
Detail's top-level `approximation` describes the **aggregate total**, not merely
|
|
281
|
+
the page or a separate pricing lookup. If any contributing returned row has
|
|
282
|
+
`approximate:true`, the aggregate must also be approximate and retain every
|
|
283
|
+
reason on that row. A contradictory exact aggregate or a missing row reason is
|
|
284
|
+
rejected. This requirement applies on partial/unknown pages too. Aggregate
|
|
285
|
+
reasons may be a superset of current-page reasons (unseen contributors or
|
|
286
|
+
aggregation uncertainty); partial pages still need not sum to the whole total.
|
|
287
|
+
Across pages the supplier must retain the total's full provenance consistently.
|
|
288
|
+
Existing null-money requirements remain: null total requires top-level
|
|
289
|
+
missing-price provenance, and null row amounts require row missing-price
|
|
290
|
+
provenance. Additive unknown output fields remain tolerated.
|
|
291
|
+
|
|
292
|
+
Optional rate attribution remains optional; no caller is required to populate
|
|
293
|
+
`rates`. When present, every matched/closest-model/provider-default record must
|
|
294
|
+
have a non-null rateId identifying the selected priced rate. An unpriced record
|
|
295
|
+
must have a null rateId. The codec rejects both contradictory pairings; it does
|
|
296
|
+
not verify a rate against the supplier's operator-owned table.
|
|
297
|
+
|
|
298
|
+
## Ordinary client deadlines and publication fences
|
|
299
|
+
|
|
300
|
+
`createThreadMetricsClient` binds the owner and validates the wire data. It only
|
|
301
|
+
forwards the caller's AbortSignal to the SDK. It creates no per-call deadline,
|
|
302
|
+
queue, outstanding-read limit, generation fence or automatic retry, and does not
|
|
303
|
+
promise that abort rejects the call or stops remote work. A supplier/transport
|
|
304
|
+
that ignores abort can still return a schema-valid result after cancellation.
|
|
305
|
+
The discovery coordinator's bounded lifetime/admission does not extend to these
|
|
306
|
+
ordinary read/detail calls.
|
|
307
|
+
|
|
308
|
+
Consumers own read/detail deadlines and actual outstanding admission across
|
|
309
|
+
refresh/reconnect/replacement requests. Racing a call against a timeout permits
|
|
310
|
+
local UI progress but must not free its transport slot until the actual promise
|
|
311
|
+
settles. A new owner/client instance must not bypass the demand coordinator's
|
|
312
|
+
retained admission budget. After **every await**, before publishing/caching any
|
|
313
|
+
response or error, consumers check their abort state and current generation
|
|
314
|
+
(owner, thread/scope/archive/window/series demand and view lifecycle) and discard
|
|
315
|
+
obsolete outcomes. These post-await fences are required even when the SDK claims
|
|
316
|
+
cancellation support. Keep last good data during failure according to the host's
|
|
317
|
+
staleness policy; a late response cannot revive a disposed/replaced demand.
|
|
318
|
+
No package-global lifecycle or client cancellation machinery is introduced.
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
## Additive recent snapshot capability (package0.2)
|
|
322
|
+
|
|
323
|
+
The new optional boolean `recentSnapshotV1` capability requires the exact fixed
|
|
324
|
+
`threadMetricsRecentSnapshotV1` handler; registration rejects either mismatch.
|
|
325
|
+
Input is strict `{ifRevision?:string}` within1024 canonical UTF-8 JSON bytes.
|
|
326
|
+
Full replies have wire version1, `unchanged:false`, opaque revision, immutable
|
|
327
|
+
publication time, rank time,72h recent window, a published config revision/value,
|
|
328
|
+
explicit coverage/materialization-pending/cap state and rows. Unchanged replies
|
|
329
|
+
have version1, `unchanged:true`, revision and no recognized full-only payload.
|
|
330
|
+
Recognized rows/config/times/population/coverage/cap fields are rejected BEFORE
|
|
331
|
+
stripping; unrelated future additive fields remain allowed within the byte cap. The revision
|
|
332
|
+
must exactly match requested ifRevision. A new supplier instance must not reuse a
|
|
333
|
+
revision for different content. Reads do not advance revision or run fold,
|
|
334
|
+
closure, rank or materializer work; suppliers serve a held published object.
|
|
335
|
+
Adapters validate shape/bytes but cannot prove supplier implementation purity.
|
|
336
|
+
|
|
337
|
+
Each row has independent own/family last-fold timestamps, asOf and content
|
|
338
|
+
revision; at least one activity timestamp must be in the envelope's72h window.
|
|
339
|
+
The row score-config revision must match the published envelope config. Own and
|
|
340
|
+
family each carry memberCount/coverage/priceVersion, compact approximation
|
|
341
|
+
provenance, four raw dollar windows and
|
|
342
|
+
provider-attributed dollar windows, omitted-provider count, four weighted-score
|
|
343
|
+
windows, and separately named dollar/score percentile ranks. `estimate`/`floor`
|
|
344
|
+
values are finite nonnegative amounts; `unavailable` has null amount. Explicit
|
|
345
|
+
floor0 is a supplier attestation; unsupported root zeros must be unavailable,
|
|
346
|
+
never inferred from a partial population or a missing row. Family providers must
|
|
347
|
+
represent deduplicated actual members, not sums of other family rows.
|
|
348
|
+
|
|
349
|
+
Full canonical JSON is capped at4MiB including additive unknown fields,1000 rows,
|
|
350
|
+
8KiB per raw row before stripping, and32 provider entries per scope. Omitted
|
|
351
|
+
providers/rows, uncertain omission counts (`exact:false`, even lower-bound0),
|
|
352
|
+
and pending materialization cannot claim complete coverage. These
|
|
353
|
+
are post-transport decoded bounds, not raw-network/preparse allocation limits.
|
|
354
|
+
Providers select most recent rows within both row/byte caps, publish cutoff and
|
|
355
|
+
honest omission state; row omission alone proves neither absence nor zero. The
|
|
356
|
+
codec always checks finite known provider subtotals and weighted arithmetic,
|
|
357
|
+
including when other providers are unavailable or omitted. Available totals must
|
|
358
|
+
cover every known subtotal within eight relative machine epsilons (no absolute
|
|
359
|
+
small-dollar epsilon); incomplete provider sets or contributing floors
|
|
360
|
+
require a floor rather than an estimate. Unknown components may yield unavailable
|
|
361
|
+
totals, never invented zero. Complete provider sets reconcile exact totals.
|
|
362
|
+
|
|
363
|
+
Each scope AND provider has `approximation:{approximate,reasons}` independently
|
|
364
|
+
of observation coverage. Compact bounded reasons retain `closest-model`,
|
|
365
|
+
`provider-default`, `model-attribution`, `bucket-boundary`, `missing-price`, and
|
|
366
|
+
`source-gap`; aggregate provenance must include contributing provider reasons.
|
|
367
|
+
Unavailable prices require `missing-price`. Unavailable observations are different:
|
|
368
|
+
a partial/unknown projection with source-gap approximation and source-gap,
|
|
369
|
+
root-unattested, history-unavailable or missing-thread coverage may retain
|
|
370
|
+
unavailable values without claiming a price failure. Any explicit unpriced
|
|
371
|
+
coverage still requires missing-price, and provider missing-price reasons must
|
|
372
|
+
remain in aggregate approximation. Suppliers must not label an actual pricing
|
|
373
|
+
gap as an observation gap. This is the lossless reason mapping
|
|
374
|
+
from readV1; per-model rate associations remain in on-demand read/detail and are
|
|
375
|
+
not copied into listing rows. `priceVersion` still identifies the price table.
|
|
376
|
+
Real materializer conformance remains necessary before release.
|
|
377
|
+
|
|
378
|
+
Scoring-config schemaVersion2 is versioned separately from normalization semantics.
|
|
379
|
+
Default mode is percentile with gates .9 m20/h24/d7; omitted provider weights mean1.
|
|
380
|
+
Absolute mode supplies thresholds for all four windows. `weightedScoreV2` sums
|
|
381
|
+
provider dollars times weights, returns unknown when a component is unavailable,
|
|
382
|
+
and rejects invalid/duplicate provider values and arithmetic overflow. Weighting
|
|
383
|
+
never reprices the raw-dollar fields. UI, settings CAS and scoring policy remain
|
|
384
|
+
ledger/surface-owned; this package contains no settings storage or sliders.
|
|
385
|
+
|
|
386
|
+
Ranks cannot be non-null for an empty population; a positive singleton ranks0.
|
|
387
|
+
When rankAsOf is at or after the row value epoch, zero or unavailable amounts require null rank.
|
|
388
|
+
Older rank epochs may deliberately remain while newer amounts change; consumers
|
|
389
|
+
must gate heat on supported positive CURRENT amounts as well as ranks.
|
|
390
|
+
`recentPercentileHeat` applies that current weighted-score gate; legacy
|
|
391
|
+
`heat(ranks)` alone is insufficient for recent rows. This codec does not rebuild
|
|
392
|
+
or prove actual population percentiles.
|
|
393
|
+
|
|
394
|
+
Supplier ranks recompute at most once/minute outside reads. A publication binds
|
|
395
|
+
configuration and scored content atomically; pending changes retain explicit
|
|
396
|
+
coverage. Consumers hold the object by SDK-owner identity and use keyed row
|
|
397
|
+
selectors. They may conditionally poll at30s while interested/visible/connected,
|
|
398
|
+
coalescing existing hints without a per-thread request. Covered listing rows need
|
|
399
|
+
no readV1; explicit inspection outside the window may use the existing bounded
|
|
400
|
+
read/detail lane. The contract package itself never polls or retries.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Phosphor
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,58 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @phosphorco/bb-thread-metrics
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Version0.2.0 candidate (not released): adds a bounded recent snapshot and scoring-configv2. Existing wirev1 summaries, family closure, heat ranks and optional
|
|
4
|
+
on-demand breakdowns for independent BB plugins. See [CONTRACT.md](CONTRACT.md)
|
|
5
|
+
for ownership, wire semantics and proof limits.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { familyClosure, dollarPercentiles, heat } from '@phosphorco/bb-thread-metrics';
|
|
9
|
+
import { createThreadMetricsDiscovery, createThreadMetricsClient } from '@phosphorco/bb-thread-metrics/bb';
|
|
10
|
+
|
|
11
|
+
// Retain one coordinator per mounted demand resource, including across refresh.
|
|
12
|
+
const discovery = createThreadMetricsDiscovery(sdk);
|
|
13
|
+
const pass = await discovery.discover({ signal });
|
|
14
|
+
for (const owner of pass.rows) {
|
|
15
|
+
if (owner.state !== 'ready') continue;
|
|
16
|
+
const client = createThreadMetricsClient(sdk, owner.pluginId);
|
|
17
|
+
const summary = await client.read({ threadIds: ['thr_example'], scope: 'family' });
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Trigger discovery on mount, refresh, reconnect or plugin lifecycle. `busy` means
|
|
22
|
+
previous actual SDK reads are still outstanding; retain good data and retry only
|
|
23
|
+
on a later consumer trigger. Creating another coordinator to bypass busy defeats
|
|
24
|
+
the bound. No polling or automatic retry is supplied.
|
|
25
|
+
|
|
26
|
+
`bun run test` builds public JS/declarations before testing emitted entries.
|
|
27
|
+
`bun run typecheck` checks source; `tsc -p tsconfig.public-tests.json --noEmit`
|
|
28
|
+
checks emitted declaration compatibility with server and browser SDKs. The
|
|
29
|
+
`/testing` entry's `runSupplierConformance` accepts the adopter's real registration
|
|
30
|
+
factory and representative read/detail cases. It returns explicit source
|
|
31
|
+
conformance checks, never live host acceptance.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Recent snapshot (0.2 candidate)
|
|
35
|
+
|
|
36
|
+
Owners may advertise `capabilities.recentSnapshotV1:true` with a matching
|
|
37
|
+
`recentSnapshot(input)` handler. `createThreadMetricsClient(...).recentSnapshot({ifRevision?})`
|
|
38
|
+
returns the immutable precomputed object or an unchanged revision. Absence of this
|
|
39
|
+
optional capability leaves the0.1 read/detail contract intact. Old consumers may
|
|
40
|
+
ignore it. The adapter does not add refresh timers, caches or materialization.
|
|
41
|
+
|
|
42
|
+
The full response holds own/family raw estimated-dollar windows and provider
|
|
43
|
+
contributions, separately named weighted scores, raw dollar ranks and score ranks,
|
|
44
|
+
activity/materialization/rank times, coverage/cap/pending flags and the published
|
|
45
|
+
scoring configuration. Zero is explicit supplier evidence; omission/unavailable is
|
|
46
|
+
not zero. Config weights affect heat scores, never retained dollars. Defaults are
|
|
47
|
+
weight1 and current p90 percentile gates. Raw/scored populations remain distinct.
|
|
48
|
+
|
|
49
|
+
Bounds:72h recent activity,1000 rows,4MiB canonical JSON and8KiB/row,32 provider
|
|
50
|
+
entries per projection/config. Bounds include unknown additive fields before
|
|
51
|
+
schema stripping; the SDK has already parsed transport JSON. Providers enforce
|
|
52
|
+
materializer work/storage and publication bounds independently. Clients must not
|
|
53
|
+
turn row omission into an eager per-ID read storm. The planned coordinator uses
|
|
54
|
+
one held snapshot/mount+30s conditional cadence; the package schedules nothing.
|
|
55
|
+
|
|
56
|
+
Current source tests are package/adapter proof only. Registry release, ledger
|
|
57
|
+
materialization, live byte/time measurement, all consumer adoptions and native
|
|
58
|
+
host/freshness proof remain separate gates.
|