@uptimizr/metrics 0.1.0 → 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/AGENTS.md +129 -0
- package/README.md +43 -21
- package/dist/envelopes.d.ts +280 -0
- package/dist/envelopes.d.ts.map +1 -0
- package/dist/envelopes.js +289 -0
- package/dist/envelopes.js.map +1 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -1
- package/dist/index.js.map +1 -1
- package/dist/narrative.d.ts +164 -0
- package/dist/narrative.d.ts.map +1 -0
- package/dist/narrative.js +150 -0
- package/dist/narrative.js.map +1 -0
- package/dist/panelSpec.d.ts +145 -0
- package/dist/panelSpec.d.ts.map +1 -0
- package/dist/panelSpec.js +286 -0
- package/dist/panelSpec.js.map +1 -0
- package/dist/query.d.ts +192 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +461 -0
- package/dist/query.js.map +1 -0
- package/dist/registry.d.ts +948 -98
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +954 -13
- package/dist/registry.js.map +1 -1
- package/llms.txt +12 -3
- package/package.json +4 -4
package/AGENTS.md
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
The **semantic metric registry**: one machine-readable `MetricDefinition` per Uptimizr analytics
|
|
9
9
|
metric. It is the single source of truth for _what can be asked_ of a collector — the endpoint, the
|
|
10
10
|
filters, the shape and units of a row, the row caps, how to read the answer and what not to trust.
|
|
11
|
+
It also owns the Zod mirrors of the three **result envelopes** (`format=full | table | summary`,
|
|
12
|
+
ADR 0051 §2), so the collector, the tool catalog and the MCP server describe one set of shapes.
|
|
11
13
|
|
|
12
14
|
It is **pure data**: `zod` and a type-only `@uptimizr/schema` import, nothing else. No `node:`
|
|
13
15
|
built-in, no DOM, no database driver. Import it from a browser bundle, a worker or an `npx` CLI.
|
|
@@ -35,6 +37,34 @@ for (const metric of allMetrics()) {
|
|
|
35
37
|
const metric: MetricDefinition | undefined = getMetric("perf_summary");
|
|
36
38
|
```
|
|
37
39
|
|
|
40
|
+
```ts
|
|
41
|
+
import { resultEnvelopeSchema, structuredEnvelopeSchema, getMetric } from "@uptimizr/metrics";
|
|
42
|
+
|
|
43
|
+
const row = getMetric("top_meshes")!.row;
|
|
44
|
+
// Union of the three envelopes, for a caller that can express one (a route's
|
|
45
|
+
// 200 schema, a parser): rows[] | { meta, rows } | a summary digest.
|
|
46
|
+
resultEnvelopeSchema(row).parse(await response.json());
|
|
47
|
+
// The merged OBJECT form of the same union, for an MCP `outputSchema` — the
|
|
48
|
+
// SDK drops an output schema that is not an object, unions included.
|
|
49
|
+
structuredEnvelopeSchema(row);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This package also owns the **vocabulary half** of validating a query-DSL document
|
|
53
|
+
(ADR 0051 §3). `@uptimizr/schema`'s `queryV1Schema` checks the shape; `validateQuery` answers
|
|
54
|
+
the registry's questions and returns them as data:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { validateQuery, nativeDimensions, genericDimensions } from "@uptimizr/metrics";
|
|
58
|
+
|
|
59
|
+
const { issues, metric, tier } = validateQuery(query); // [] issues means it can be run
|
|
60
|
+
issues[0]?.code; // "unsupported_filter" | "dimension_not_native" | "limit_too_large" | …
|
|
61
|
+
issues[0]?.accepted; // what *would* have worked, when that is a closed list
|
|
62
|
+
tier; // "delegated" (the metric's own builder) | "generic" (the shared group-by)
|
|
63
|
+
|
|
64
|
+
nativeDimensions(metric!); // the grain its rows carry — `metric.grainDimensions`
|
|
65
|
+
genericDimensions(metric!); // what it can *also* be grouped by, or [] if it cannot
|
|
66
|
+
```
|
|
67
|
+
|
|
38
68
|
## Rules for agents
|
|
39
69
|
|
|
40
70
|
- **Derive, never restate.** Tool catalogs, OpenAPI paths, capability lists and docs tables are
|
|
@@ -51,10 +81,109 @@ const metric: MetricDefinition | undefined = getMetric("perf_summary");
|
|
|
51
81
|
calls. Rename nothing; add instead.
|
|
52
82
|
- **Respect `limits`.** `maxRows` is the registry's promise that no answer is unbounded;
|
|
53
83
|
`maxSummaryRows` is what a model should be shown.
|
|
84
|
+
- **Describe a result with the envelope schemas, never a hand-written copy.** `tableEnvelopeSchema`,
|
|
85
|
+
`summaryEnvelopeSchema`, `resultEnvelopeSchema` and `structuredEnvelopeSchema` are the one
|
|
86
|
+
definition; `@uptimizr/db/summary` re-exports them under its established names, and the summariser
|
|
87
|
+
that _builds_ an envelope still lives there.
|
|
54
88
|
- **Read `caveats` before you conclude anything.** They record small-sample, sampling-rate and
|
|
55
89
|
capture-gating conditions — a metric with no enabled source channel returns an honest empty
|
|
56
90
|
result, not a zero.
|
|
57
91
|
- **`null` is not `0`.** An aggregate over no samples is SQL `NULL` and means "no data".
|
|
92
|
+
- **`dimensions` is not the grain.** `MetricDefinition.dimensions` lists what a metric can be
|
|
93
|
+
_filtered, keyed or regrouped_ by; `grainDimensions` (read it through `nativeDimensions`) is what
|
|
94
|
+
its rows are actually keyed by. `top_meshes` declares `session` and returns one row per mesh.
|
|
95
|
+
- **`grainDimensions` is declared, not derived.** It used to be read back out of `row.shape`; since
|
|
96
|
+
#304 it is registry data, and `src/__tests__/registry.test.ts` keeps the old derivation as the
|
|
97
|
+
gate on the declaration. Add it to every new entry.
|
|
98
|
+
- **`genericGroupBy` is a claim about portability.** Declare it only where the measure is a
|
|
99
|
+
`count(*)`, a `count(DISTINCT session_id)`, or a `sum`/`avg`/`max` over a **promoted** column —
|
|
100
|
+
the shapes that render identically on all four engines at any grain. A spatial binning and a
|
|
101
|
+
percentile do not, and must not have one. Every measure column it names must also be a column of
|
|
102
|
+
the metric's `row`.
|
|
103
|
+
- **`sourceChannels` is load-bearing.** The collector's project context document
|
|
104
|
+
(`GET /api/v1/context`, ADR 0051 §5) reports a metric under `metrics.disabledByCapture` when
|
|
105
|
+
**every** channel it declares produced no events over the window, which is how an agent learns to
|
|
106
|
+
say "that channel is off" instead of reporting the zero as a finding. An empty `sourceChannels`
|
|
107
|
+
means a derived rollup and is never reported as disabled — so declare the channels a metric
|
|
108
|
+
really reads, no more and no fewer.
|
|
109
|
+
|
|
110
|
+
## Derived metrics (ADR 0051 §4)
|
|
111
|
+
|
|
112
|
+
Most entries name a `build*` aggregation. Five do not, and are not store resources either: the
|
|
113
|
+
insight primitives `insight_baseline`, `insight_movers`, `insight_anomalies`,
|
|
114
|
+
`insight_significance` and `insight_scene_health` are computed in pure TypeScript _over other
|
|
115
|
+
metrics' data_ (`@uptimizr/db`'s `src/insights/`). They carry `derived: "insight"`, and there are
|
|
116
|
+
three kinds of entry rather than two:
|
|
117
|
+
|
|
118
|
+
| Predicate | Entry |
|
|
119
|
+
| ------------------- | -------------------------------------------------------------- |
|
|
120
|
+
| `isResourceMetric` | A store read — no builder, no querystring, no `format`. |
|
|
121
|
+
| `isDerivedMetric` | Computed in TypeScript; a real aggregate with an endpoint. |
|
|
122
|
+
| `isAggregateMetric` | Either a builder or a derivation — i.e. "takes a querystring". |
|
|
123
|
+
|
|
124
|
+
Prefer `isAggregateMetric` over `metric.builder != null` anywhere the question is "is this served as
|
|
125
|
+
an aggregate?", or a derived metric silently drops out of the envelope and OpenAPI surfaces.
|
|
126
|
+
|
|
127
|
+
Two things a derived entry carries that are worth reading before you use one. `insight_significance`
|
|
128
|
+
picks its statistical test from the **compared** metric's own entry — a headline column whose
|
|
129
|
+
`rateOf` names a denominator gets a two-proportion test, a bare count gets a Poisson rate test, and
|
|
130
|
+
everything else gets Welch's t — so `columns[...].rateOf` is load-bearing, not decoration.
|
|
131
|
+
`insight_scene_health` declares its per-factor **weights in its `caveats`** so they are visible in
|
|
132
|
+
`capabilities` and in the generated tool catalog; they are a judgement, published so it can be
|
|
133
|
+
argued with and overridden per request.
|
|
134
|
+
|
|
135
|
+
## Panel specs (ADR 0051 §7)
|
|
136
|
+
|
|
137
|
+
The same split, one level up. `@uptimizr/schema`'s `panelSpecV1Schema` says a spec is well formed;
|
|
138
|
+
`validatePanelSpec(spec)` says whether it will draw anything. It runs `validateQuery` over the
|
|
139
|
+
spec's query (prefixing each issue's `path` with `query.`) and adds the two checks only the registry
|
|
140
|
+
can make — does the chart suit the metric's grain, and does the `encoding` name columns the metric
|
|
141
|
+
actually returns:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import {
|
|
145
|
+
validatePanelSpec,
|
|
146
|
+
suggestChart,
|
|
147
|
+
defaultEncoding,
|
|
148
|
+
chartsForMetric,
|
|
149
|
+
} from "@uptimizr/metrics";
|
|
150
|
+
|
|
151
|
+
const { issues, metric, tier } = validatePanelSpec(spec); // [] issues means it can be pinned
|
|
152
|
+
issues[0]?.code; // the DSL's codes, plus "chart_grain_mismatch" | "unknown_encoding_column"
|
|
153
|
+
issues[0]?.accepted; // the charts that would have worked, or the metric's real result columns
|
|
154
|
+
|
|
155
|
+
suggestChart(metric, query); // what to pre-fill; always satisfies `chartSuitsMetric`
|
|
156
|
+
defaultEncoding(metric, chart); // the metric's axis/label on `x`, its measure on `y`
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`PANEL_CHART_RULES` is the compatibility table as data — the validator, `suggestChart` and the
|
|
160
|
+
published table all read it, so they cannot drift:
|
|
161
|
+
|
|
162
|
+
| Chart | Requires |
|
|
163
|
+
| --------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
164
|
+
| `table` | nothing — any metric's rows can be listed |
|
|
165
|
+
| `stat` | a single-record result (a `project`-grain metric with no grain dimensions) |
|
|
166
|
+
| `bar` | a label column, a measure column, and a ranked or bucketed grain (`mesh`, `scene`, `session`, `row`, `bucket`) |
|
|
167
|
+
| `line` / `area` | an ordered axis column — in practice a `bucket`-grain metric |
|
|
168
|
+
| `heatmap2d` | a `bin` grain (the metric already bins its input into a grid) |
|
|
169
|
+
| `world3d` | a `voxel` grain (the metric already bins its input into world-space cells) |
|
|
170
|
+
|
|
171
|
+
`chartSuitsMetric(chart, metric)` is the predicate behind one row; `chartsForMetric(metric)` lists
|
|
172
|
+
every chart that fits. `axisColumn` / `labelColumn` / `measureColumn` read the column semantics the
|
|
173
|
+
rules depend on, and `resultColumns(metric, query)` is what an encoding is checked against — the
|
|
174
|
+
metric's declared `row` on the delegated tier, but the grouped dimensions plus the declared measures
|
|
175
|
+
on the generic tier, because a regrouped `top_meshes` has no `mesh` column.
|
|
176
|
+
|
|
177
|
+
**Rules for agents:**
|
|
178
|
+
|
|
179
|
+
- **A pinned panel is read much later than it is written**, which is why the chart is checked at all.
|
|
180
|
+
A `line` over `top_meshes` does not fail — it draws something a reader takes for a trend. Pin time
|
|
181
|
+
is the moment somebody is paying attention, so that is where the refusal belongs.
|
|
182
|
+
- **Widen the table with a `PanelChartRule`, never a special case.** A rule's `requires` string is
|
|
183
|
+
what the collector's `400` quotes; `src/__tests__/panelSpec.test.ts` pins the whole chart × metric
|
|
184
|
+
matrix and the rendered table.
|
|
185
|
+
- A rule may read only the **grain and the column semantics**, like everything else here. If a metric
|
|
186
|
+
charts badly, fix its `label` / `axis` / `measure` declarations.
|
|
58
187
|
|
|
59
188
|
## Where the SQL lives
|
|
60
189
|
|
package/README.md
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
Everything that used to restate the query surface by hand is **derived** from this package: the
|
|
9
9
|
agent tool catalog in [`@uptimizr/agent-core`](../agent-core), the `uptimizr://capabilities`
|
|
10
|
-
resource and tool output schemas in [`@uptimizr/mcp`](../mcp)
|
|
10
|
+
resource and tool output schemas in [`@uptimizr/mcp`](../mcp) — which describe a row **and** the
|
|
11
|
+
`format=full | table | summary` result envelopes this package also defines (ADR 0051 §2) — the
|
|
12
|
+
collector's
|
|
11
13
|
`GET /api/v1/openapi.json` document, and the endpoint/tool tables in the docs. Coverage therefore
|
|
12
14
|
cannot drift: CI fails when an aggregation has no registry entry, when a row schema does not match
|
|
13
15
|
real query output, or when an endpoint's querystring keys diverge from its declared filters.
|
|
@@ -28,12 +30,12 @@ pnpm add @uptimizr/metrics
|
|
|
28
30
|
```ts
|
|
29
31
|
import { allMetrics, getMetric, METRIC_IDS } from "@uptimizr/metrics";
|
|
30
32
|
|
|
31
|
-
METRIC_IDS.length; //
|
|
33
|
+
METRIC_IDS.length; // 78 metrics (70 aggregations + 5 insight derivations + 3 store resources)
|
|
32
34
|
|
|
33
35
|
const metric = getMetric("top_meshes");
|
|
34
36
|
metric?.title; // "Most-interacted meshes"
|
|
35
37
|
metric?.endpoint?.path; // "/api/v1/meshes/top"
|
|
36
|
-
metric?.filters; // ["since", "until", "bins", "limit", "session"]
|
|
38
|
+
metric?.filters; // ["since", "until", "bins", "limit", "session", "format"]
|
|
37
39
|
metric?.row; // z.ZodObject — the shape of one row
|
|
38
40
|
metric?.columns.count?.unit; // "count"
|
|
39
41
|
metric?.limits.maxRows; // the hard cap no consumer may exceed
|
|
@@ -44,23 +46,23 @@ const byCategory = Object.groupBy(allMetrics(), (m) => m.category);
|
|
|
44
46
|
|
|
45
47
|
## What a `MetricDefinition` carries
|
|
46
48
|
|
|
47
|
-
| Field | Meaning
|
|
48
|
-
| ----------------------- |
|
|
49
|
-
| `id` | Stable snake_case id. Also the DSL metric name and the **agent tool name**.
|
|
50
|
-
| `title`, `description` | Agent-facing prose: what it measures and what one row is.
|
|
51
|
-
| `builder` | The `build*` aggregation in `@uptimizr/db` that computes it. Absent for the
|
|
52
|
-
| `endpoint` | `{ method, path, pathParams }` — the canned collector route, when one exists.
|
|
53
|
-
| `grain` | What one row represents (`project`, `scene`, `session`, `mesh`, `bin`, `voxel`, `bucket`, `row`).
|
|
54
|
-
| `dimensions` | The `DimensionId`s
|
|
55
|
-
| `filters` | The `FilterId`s the endpoint accepts — exactly its Zod querystring keys.
|
|
56
|
-
| `row` | `z.ZodObject` for one row: the source for OpenAPI, tool output schemas and numeric coercion.
|
|
57
|
-
| `columns` | Per-column `{ description, unit, measure, label, rateOf }`.
|
|
58
|
-
| `limits` | `{ maxRows, maxSummaryRows }` — no consumer can ask for an unbounded payload.
|
|
59
|
-
| `interpretation` | How to read the result.
|
|
60
|
-
| `caveats` | Small-sample, capture-gating and sampling-rate warnings.
|
|
61
|
-
| `sourceChannels` | The `EventType` capture channels that feed it (ADR 0012).
|
|
62
|
-
| `related`, `comparable` | Metrics worth reading alongside; comparison semantics for deltas.
|
|
63
|
-
| `category` | Grouping used by the docs, the capabilities resource and the health score.
|
|
49
|
+
| Field | Meaning |
|
|
50
|
+
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| `id` | Stable snake_case id. Also the DSL metric name and the **agent tool name**. |
|
|
52
|
+
| `title`, `description` | Agent-facing prose: what it measures and what one row is. |
|
|
53
|
+
| `builder` | The `build*` aggregation in `@uptimizr/db` that computes it. Absent for the three **resource** entries (`session_meta`, `session_narrative`, `scene_representation`), which are store reads, and for the five `derived: "insight"` entries, which are computed in TypeScript. |
|
|
54
|
+
| `endpoint` | `{ method, path, pathParams }` — the canned collector route, when one exists. |
|
|
55
|
+
| `grain` | What one row represents (`project`, `scene`, `session`, `mesh`, `bin`, `voxel`, `bucket`, `row`). |
|
|
56
|
+
| `dimensions` | The `DimensionId`s it can be filtered, keyed or regrouped by — `grainDimensions` is what its rows are actually keyed by. Closed vocabulary. |
|
|
57
|
+
| `filters` | The `FilterId`s the endpoint accepts — exactly its Zod querystring keys. |
|
|
58
|
+
| `row` | `z.ZodObject` for one row: the source for OpenAPI, tool output schemas (wrapped in the result envelopes below) and numeric coercion. |
|
|
59
|
+
| `columns` | Per-column `{ description, unit, measure, label, rateOf }`. |
|
|
60
|
+
| `limits` | `{ maxRows, maxSummaryRows }` — no consumer can ask for an unbounded payload. |
|
|
61
|
+
| `interpretation` | How to read the result. |
|
|
62
|
+
| `caveats` | Small-sample, capture-gating and sampling-rate warnings. |
|
|
63
|
+
| `sourceChannels` | The `EventType` capture channels that feed it (ADR 0012). |
|
|
64
|
+
| `related`, `comparable` | Metrics worth reading alongside; comparison semantics for deltas. |
|
|
65
|
+
| `category` | Grouping used by the docs, the capabilities resource and the health score. |
|
|
64
66
|
|
|
65
67
|
## Exports
|
|
66
68
|
|
|
@@ -75,7 +77,27 @@ const byCategory = Object.groupBy(allMetrics(), (m) => m.category);
|
|
|
75
77
|
**Helpers**
|
|
76
78
|
|
|
77
79
|
- `allMetrics()`, `getMetric(id)`, `isMetricId(value)`
|
|
78
|
-
- `metricForBuilder(builder)`, `isResourceMetric(metric)
|
|
80
|
+
- `metricForBuilder(builder)`, and the entry-kind predicates `isResourceMetric(metric)`,
|
|
81
|
+
`isDerivedMetric(metric)`, `isAggregateMetric(metric)`
|
|
82
|
+
- `validateQuery(query)`, `nativeDimensions(metric)`, `genericDimensions(metric)`,
|
|
83
|
+
`queryTier(metric, query)` — the query-DSL vocabulary checks (ADR 0051 §3)
|
|
84
|
+
|
|
85
|
+
**Result envelopes** (ADR 0051 §2)
|
|
86
|
+
|
|
87
|
+
The Zod mirrors of `format=full | table | summary`. They live here, next to the row schemas they
|
|
88
|
+
wrap, because the collector (`@uptimizr/db`, which re-exports them from `@uptimizr/db/summary`
|
|
89
|
+
under its own names), the generated tool catalog and the MCP server all need them and none of
|
|
90
|
+
those may depend on the others. The summariser that _builds_ an envelope stays in
|
|
91
|
+
`@uptimizr/db/summary`.
|
|
92
|
+
|
|
93
|
+
- `resultFormatSchema` — the `full | table | summary` enum.
|
|
94
|
+
- `tableEnvelopeSchema(row)` — `{ meta, rows }`; `tableMetaSchema` — the `meta` block alone.
|
|
95
|
+
- `summaryEnvelopeSchema` — the four grain-driven digests, discriminated on `kind`.
|
|
96
|
+
- `resultEnvelopeSchema(row, full?)` — the union of the three, for a caller that can express one
|
|
97
|
+
(a route's 200 response schema, a parser). `full` defaults to an array of `row`.
|
|
98
|
+
- `structuredEnvelopeSchema(row)` — the same union merged into **one loose object**, every key
|
|
99
|
+
optional. An MCP `outputSchema` must be an object schema: the SDK drops anything else, a
|
|
100
|
+
top-level union included, and then advertises no schema at all.
|
|
79
101
|
|
|
80
102
|
**Types**
|
|
81
103
|
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod mirrors of the three result envelopes (ADR 0051 §2, design sketch §B.1).
|
|
3
|
+
*
|
|
4
|
+
* `format=full | table | summary` is the shared querystring of every
|
|
5
|
+
* registry-served aggregate endpoint, and the same three shapes are what an
|
|
6
|
+
* agent gets back through MCP. They are described **here**, next to the row
|
|
7
|
+
* schemas they wrap, because three different packages need them and none of
|
|
8
|
+
* them may depend on the others:
|
|
9
|
+
*
|
|
10
|
+
* - `@uptimizr/db` attaches each route's 200 response schema from the registry,
|
|
11
|
+
* so the collector's Zod type provider serialises a formatted response
|
|
12
|
+
* *through* one of these — without them a `table` or `summary` response would
|
|
13
|
+
* be stripped to nothing on its way out. `@uptimizr/db/summary` re-exports
|
|
14
|
+
* every schema below under its established names, so that package's public
|
|
15
|
+
* API is unchanged.
|
|
16
|
+
* - `@uptimizr/agent-core` derives each generated tool's `outputSchema` from
|
|
17
|
+
* them, and `@uptimizr/mcp` validates `structuredContent` with them. Neither
|
|
18
|
+
* package may depend on `@uptimizr/db` (it carries a ~37 MB DuckDB binding;
|
|
19
|
+
* `dependencies.test.ts` in both packages fails if it reappears) — which is
|
|
20
|
+
* exactly why the *pure* schemas live in this dependency-free package.
|
|
21
|
+
*
|
|
22
|
+
* Only the schemas moved. The summariser that *builds* an envelope
|
|
23
|
+
* (`summarizeRows`, `tableResult`, `clusterCells`, …) is still
|
|
24
|
+
* `@uptimizr/db/summary`'s; relocating it is #337.
|
|
25
|
+
*/
|
|
26
|
+
import { z } from "zod";
|
|
27
|
+
/**
|
|
28
|
+
* `format=full | table | summary`, the shared querystring value every
|
|
29
|
+
* registry-served aggregate endpoint accepts. Spelled out literally rather than
|
|
30
|
+
* built from a `RESULT_FORMATS` array so the inferred type is the string union
|
|
31
|
+
* itself and a route handler keeps its narrowing.
|
|
32
|
+
*/
|
|
33
|
+
export declare const resultFormatSchema: z.ZodEnum<{
|
|
34
|
+
summary: "summary";
|
|
35
|
+
full: "full";
|
|
36
|
+
table: "table";
|
|
37
|
+
}>;
|
|
38
|
+
/** `format=summary`: one of the four grain-driven shapes. */
|
|
39
|
+
export declare const summaryEnvelopeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
40
|
+
kind: z.ZodLiteral<"ranked">;
|
|
41
|
+
top: z.ZodArray<z.ZodObject<{
|
|
42
|
+
label: z.ZodString;
|
|
43
|
+
value: z.ZodNullable<z.ZodNumber>;
|
|
44
|
+
share: z.ZodNullable<z.ZodNumber>;
|
|
45
|
+
shareInterval: z.ZodOptional<z.ZodObject<{
|
|
46
|
+
low: z.ZodNumber;
|
|
47
|
+
high: z.ZodNumber;
|
|
48
|
+
}, z.core.$strip>>;
|
|
49
|
+
drill: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
50
|
+
drillQuery: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
51
|
+
}, z.core.$strip>>;
|
|
52
|
+
rest: z.ZodObject<{
|
|
53
|
+
rows: z.ZodNumber;
|
|
54
|
+
value: z.ZodNullable<z.ZodNumber>;
|
|
55
|
+
share: z.ZodNullable<z.ZodNumber>;
|
|
56
|
+
}, z.core.$strip>;
|
|
57
|
+
metric: z.ZodEnum<{
|
|
58
|
+
[x: string]: string;
|
|
59
|
+
}>;
|
|
60
|
+
range: z.ZodObject<{
|
|
61
|
+
since: z.ZodNullable<z.ZodNumber>;
|
|
62
|
+
until: z.ZodNullable<z.ZodNumber>;
|
|
63
|
+
}, z.core.$strip>;
|
|
64
|
+
filters: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
65
|
+
sampleSize: z.ZodObject<{
|
|
66
|
+
sessions: z.ZodNullable<z.ZodNumber>;
|
|
67
|
+
events: z.ZodNullable<z.ZodNumber>;
|
|
68
|
+
}, z.core.$strip>;
|
|
69
|
+
total: z.ZodNullable<z.ZodNumber>;
|
|
70
|
+
measure: z.ZodNullable<z.ZodObject<{
|
|
71
|
+
column: z.ZodString;
|
|
72
|
+
unit: z.ZodNullable<z.ZodString>;
|
|
73
|
+
additive: z.ZodBoolean;
|
|
74
|
+
}, z.core.$strip>>;
|
|
75
|
+
confidence: z.ZodOptional<z.ZodObject<{
|
|
76
|
+
kind: z.ZodLiteral<"wilson">;
|
|
77
|
+
level: z.ZodNumber;
|
|
78
|
+
note: z.ZodString;
|
|
79
|
+
}, z.core.$strip>>;
|
|
80
|
+
reading: z.ZodString;
|
|
81
|
+
caveats: z.ZodArray<z.ZodString>;
|
|
82
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
83
|
+
kind: z.ZodLiteral<"series">;
|
|
84
|
+
series: z.ZodObject<{
|
|
85
|
+
axis: z.ZodString;
|
|
86
|
+
points: z.ZodNumber;
|
|
87
|
+
first: z.ZodNullable<z.ZodNumber>;
|
|
88
|
+
last: z.ZodNullable<z.ZodNumber>;
|
|
89
|
+
min: z.ZodNullable<z.ZodNumber>;
|
|
90
|
+
max: z.ZodNullable<z.ZodNumber>;
|
|
91
|
+
firstLabel: z.ZodNullable<z.ZodString>;
|
|
92
|
+
lastLabel: z.ZodNullable<z.ZodString>;
|
|
93
|
+
minLabel: z.ZodNullable<z.ZodString>;
|
|
94
|
+
maxLabel: z.ZodNullable<z.ZodString>;
|
|
95
|
+
trend: z.ZodEnum<{
|
|
96
|
+
up: "up";
|
|
97
|
+
down: "down";
|
|
98
|
+
flat: "flat";
|
|
99
|
+
}>;
|
|
100
|
+
slope: z.ZodNullable<z.ZodNumber>;
|
|
101
|
+
}, z.core.$strip>;
|
|
102
|
+
metric: z.ZodEnum<{
|
|
103
|
+
[x: string]: string;
|
|
104
|
+
}>;
|
|
105
|
+
range: z.ZodObject<{
|
|
106
|
+
since: z.ZodNullable<z.ZodNumber>;
|
|
107
|
+
until: z.ZodNullable<z.ZodNumber>;
|
|
108
|
+
}, z.core.$strip>;
|
|
109
|
+
filters: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
110
|
+
sampleSize: z.ZodObject<{
|
|
111
|
+
sessions: z.ZodNullable<z.ZodNumber>;
|
|
112
|
+
events: z.ZodNullable<z.ZodNumber>;
|
|
113
|
+
}, z.core.$strip>;
|
|
114
|
+
total: z.ZodNullable<z.ZodNumber>;
|
|
115
|
+
measure: z.ZodNullable<z.ZodObject<{
|
|
116
|
+
column: z.ZodString;
|
|
117
|
+
unit: z.ZodNullable<z.ZodString>;
|
|
118
|
+
additive: z.ZodBoolean;
|
|
119
|
+
}, z.core.$strip>>;
|
|
120
|
+
confidence: z.ZodOptional<z.ZodObject<{
|
|
121
|
+
kind: z.ZodLiteral<"wilson">;
|
|
122
|
+
level: z.ZodNumber;
|
|
123
|
+
note: z.ZodString;
|
|
124
|
+
}, z.core.$strip>>;
|
|
125
|
+
reading: z.ZodString;
|
|
126
|
+
caveats: z.ZodArray<z.ZodString>;
|
|
127
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
128
|
+
kind: z.ZodLiteral<"clusters">;
|
|
129
|
+
axes: z.ZodArray<z.ZodString>;
|
|
130
|
+
occupiedCells: z.ZodNumber;
|
|
131
|
+
densityThreshold: z.ZodNumber;
|
|
132
|
+
clusters: z.ZodArray<z.ZodObject<{
|
|
133
|
+
centroid: z.ZodArray<z.ZodNumber>;
|
|
134
|
+
extent: z.ZodObject<{
|
|
135
|
+
min: z.ZodArray<z.ZodNumber>;
|
|
136
|
+
max: z.ZodArray<z.ZodNumber>;
|
|
137
|
+
}, z.core.$strip>;
|
|
138
|
+
cells: z.ZodNumber;
|
|
139
|
+
weight: z.ZodNumber;
|
|
140
|
+
share: z.ZodNullable<z.ZodNumber>;
|
|
141
|
+
drill: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
142
|
+
region: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
143
|
+
regions: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
144
|
+
nearestMesh: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
145
|
+
distance: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
146
|
+
}, z.core.$strip>>;
|
|
147
|
+
rest: z.ZodObject<{
|
|
148
|
+
clusters: z.ZodNumber;
|
|
149
|
+
cells: z.ZodNumber;
|
|
150
|
+
weight: z.ZodNullable<z.ZodNumber>;
|
|
151
|
+
share: z.ZodNullable<z.ZodNumber>;
|
|
152
|
+
}, z.core.$strip>;
|
|
153
|
+
metric: z.ZodEnum<{
|
|
154
|
+
[x: string]: string;
|
|
155
|
+
}>;
|
|
156
|
+
range: z.ZodObject<{
|
|
157
|
+
since: z.ZodNullable<z.ZodNumber>;
|
|
158
|
+
until: z.ZodNullable<z.ZodNumber>;
|
|
159
|
+
}, z.core.$strip>;
|
|
160
|
+
filters: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
161
|
+
sampleSize: z.ZodObject<{
|
|
162
|
+
sessions: z.ZodNullable<z.ZodNumber>;
|
|
163
|
+
events: z.ZodNullable<z.ZodNumber>;
|
|
164
|
+
}, z.core.$strip>;
|
|
165
|
+
total: z.ZodNullable<z.ZodNumber>;
|
|
166
|
+
measure: z.ZodNullable<z.ZodObject<{
|
|
167
|
+
column: z.ZodString;
|
|
168
|
+
unit: z.ZodNullable<z.ZodString>;
|
|
169
|
+
additive: z.ZodBoolean;
|
|
170
|
+
}, z.core.$strip>>;
|
|
171
|
+
confidence: z.ZodOptional<z.ZodObject<{
|
|
172
|
+
kind: z.ZodLiteral<"wilson">;
|
|
173
|
+
level: z.ZodNumber;
|
|
174
|
+
note: z.ZodString;
|
|
175
|
+
}, z.core.$strip>>;
|
|
176
|
+
reading: z.ZodString;
|
|
177
|
+
caveats: z.ZodArray<z.ZodString>;
|
|
178
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
179
|
+
kind: z.ZodLiteral<"record">;
|
|
180
|
+
record: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>;
|
|
181
|
+
rates: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
182
|
+
value: z.ZodNullable<z.ZodNumber>;
|
|
183
|
+
numerator: z.ZodString;
|
|
184
|
+
denominator: z.ZodString;
|
|
185
|
+
}, z.core.$strip>>;
|
|
186
|
+
metric: z.ZodEnum<{
|
|
187
|
+
[x: string]: string;
|
|
188
|
+
}>;
|
|
189
|
+
range: z.ZodObject<{
|
|
190
|
+
since: z.ZodNullable<z.ZodNumber>;
|
|
191
|
+
until: z.ZodNullable<z.ZodNumber>;
|
|
192
|
+
}, z.core.$strip>;
|
|
193
|
+
filters: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
194
|
+
sampleSize: z.ZodObject<{
|
|
195
|
+
sessions: z.ZodNullable<z.ZodNumber>;
|
|
196
|
+
events: z.ZodNullable<z.ZodNumber>;
|
|
197
|
+
}, z.core.$strip>;
|
|
198
|
+
total: z.ZodNullable<z.ZodNumber>;
|
|
199
|
+
measure: z.ZodNullable<z.ZodObject<{
|
|
200
|
+
column: z.ZodString;
|
|
201
|
+
unit: z.ZodNullable<z.ZodString>;
|
|
202
|
+
additive: z.ZodBoolean;
|
|
203
|
+
}, z.core.$strip>>;
|
|
204
|
+
confidence: z.ZodOptional<z.ZodObject<{
|
|
205
|
+
kind: z.ZodLiteral<"wilson">;
|
|
206
|
+
level: z.ZodNumber;
|
|
207
|
+
note: z.ZodString;
|
|
208
|
+
}, z.core.$strip>>;
|
|
209
|
+
reading: z.ZodString;
|
|
210
|
+
caveats: z.ZodArray<z.ZodString>;
|
|
211
|
+
}, z.core.$strip>], "kind">;
|
|
212
|
+
/** The `meta` block `format=table` wraps a metric's rows in. */
|
|
213
|
+
export declare const tableMetaSchema: z.ZodObject<{
|
|
214
|
+
metric: z.ZodEnum<{
|
|
215
|
+
[x: string]: string;
|
|
216
|
+
}>;
|
|
217
|
+
range: z.ZodObject<{
|
|
218
|
+
since: z.ZodNullable<z.ZodNumber>;
|
|
219
|
+
until: z.ZodNullable<z.ZodNumber>;
|
|
220
|
+
}, z.core.$strip>;
|
|
221
|
+
filters: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
222
|
+
sampleSize: z.ZodObject<{
|
|
223
|
+
sessions: z.ZodNullable<z.ZodNumber>;
|
|
224
|
+
events: z.ZodNullable<z.ZodNumber>;
|
|
225
|
+
}, z.core.$strip>;
|
|
226
|
+
rows: z.ZodNumber;
|
|
227
|
+
truncated: z.ZodBoolean;
|
|
228
|
+
limits: z.ZodObject<{
|
|
229
|
+
maxRows: z.ZodNumber;
|
|
230
|
+
maxSummaryRows: z.ZodNumber;
|
|
231
|
+
}, z.core.$strip>;
|
|
232
|
+
}, z.core.$strip>;
|
|
233
|
+
/** `format=table`: the `meta` envelope around a metric's own row schema. */
|
|
234
|
+
export declare function tableEnvelopeSchema(row: z.ZodType): z.ZodObject;
|
|
235
|
+
/**
|
|
236
|
+
* The result a caller gets from a surface that honours `format`: the union of
|
|
237
|
+
* the three envelopes.
|
|
238
|
+
*
|
|
239
|
+
* Order matters. The `full` shape is first, so a default request is parsed by
|
|
240
|
+
* precisely the schema it was parsed by before this existed and its bytes
|
|
241
|
+
* cannot drift. The envelopes are unambiguous against it — `table` is an object
|
|
242
|
+
* with `meta` and `rows`, `summary` an object with `kind` and `reading`, and
|
|
243
|
+
* neither can satisfy a row array or a stats row's required numeric columns.
|
|
244
|
+
*
|
|
245
|
+
* `full` defaults to a plain array of `row`, which is what an agent-facing
|
|
246
|
+
* caller wants; the collector passes its own route-level 200 schema instead,
|
|
247
|
+
* because a single-record route (the spatial `stats` endpoints) answers with a
|
|
248
|
+
* bare object rather than an array.
|
|
249
|
+
*/
|
|
250
|
+
export declare function resultEnvelopeSchema(row: z.ZodType, full?: z.ZodType): z.ZodType;
|
|
251
|
+
/**
|
|
252
|
+
* The **object** form of {@link resultEnvelopeSchema}, for a tool that must
|
|
253
|
+
* advertise its result as a single JSON Schema object — which is what MCP's
|
|
254
|
+
* `outputSchema` is: a `type: "object"` schema the SDK validates
|
|
255
|
+
* `structuredContent` against, on the server with Zod and again on the client
|
|
256
|
+
* with Ajv.
|
|
257
|
+
*
|
|
258
|
+
* A top-level `z.union` cannot be used there. The MCP TypeScript SDK normalises
|
|
259
|
+
* an output schema to an object schema and **silently drops** anything that is
|
|
260
|
+
* not one (`normalizeObjectSchema` returns `undefined` for a union), so
|
|
261
|
+
* `tools/list` would advertise no output schema at all and every call would then
|
|
262
|
+
* fail validation — worse than the bug this replaces. So the three envelopes are
|
|
263
|
+
* merged into one loose object whose every key is optional:
|
|
264
|
+
*
|
|
265
|
+
* - `rows` — `format=full` (wrapped as `{ rows }` by the MCP server, so a
|
|
266
|
+
* single-record read looks like every other) and the rows of `format=table`.
|
|
267
|
+
* Its element schema stays the metric's own, so a column that is out of
|
|
268
|
+
* contract is still reported by name.
|
|
269
|
+
* - `meta` — only `format=table` carries it.
|
|
270
|
+
* - `kind` and the summary fields — only `format=summary` carries them, and
|
|
271
|
+
* `kind` says which grain-specific fields (`top`, `series`, `clusters`,
|
|
272
|
+
* `record`) came with it.
|
|
273
|
+
*
|
|
274
|
+
* The object is deliberately *loose*: an envelope key a newer collector adds is
|
|
275
|
+
* passed through rather than dropped. Callers that can express a union — a
|
|
276
|
+
* collector route's response schema, a test — should use
|
|
277
|
+
* {@link resultEnvelopeSchema}, which discriminates strictly.
|
|
278
|
+
*/
|
|
279
|
+
export declare function structuredEnvelopeSchema(row: z.ZodType, metric?: string): z.ZodObject;
|
|
280
|
+
//# sourceMappingURL=envelopes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelopes.d.ts","sourceRoot":"","sources":["../src/envelopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;EAAuC,CAAC;AAgKvE,6DAA6D;AAC7D,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAKhC,CAAC;AAEH,gEAAgE;AAChE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;iBAQ1B,CAAC;AAEH,4EAA4E;AAC5E,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,SAAS,CAK/D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,GAAE,CAAC,CAAC,OAAsB,GAAG,CAAC,CAAC,OAAO,CAE9F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,SAAS,CAuDrF"}
|