@uptimizr/collector-server 2.1.0 → 2.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 +179 -6
- package/README.md +69 -0
- package/dist/__tests__/support/registryRequests.d.ts +2 -1
- package/dist/__tests__/support/registryRequests.d.ts.map +1 -1
- package/dist/__tests__/support/registryRequests.js +17 -1
- package/dist/__tests__/support/registryRequests.js.map +1 -1
- package/dist/__tests__/support/subscriptionFixtures.d.ts +63 -0
- package/dist/__tests__/support/subscriptionFixtures.d.ts.map +1 -0
- package/dist/__tests__/support/subscriptionFixtures.js +17 -0
- package/dist/__tests__/support/subscriptionFixtures.js.map +1 -0
- package/dist/agentReport.d.ts +256 -0
- package/dist/agentReport.d.ts.map +1 -0
- package/dist/agentReport.js +861 -0
- package/dist/agentReport.js.map +1 -0
- package/dist/app.d.ts +13 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +112 -5
- package/dist/app.js.map +1 -1
- package/dist/audit.d.ts +5 -1
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +24 -4
- package/dist/audit.js.map +1 -1
- package/dist/auth.d.ts +41 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +47 -0
- package/dist/auth.js.map +1 -1
- package/dist/cli.js +99 -2
- package/dist/cli.js.map +1 -1
- package/dist/clickhouseStore.d.ts.map +1 -1
- package/dist/clickhouseStore.js +44 -2
- package/dist/clickhouseStore.js.map +1 -1
- package/dist/config.d.ts +49 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +21 -0
- package/dist/config.js.map +1 -1
- package/dist/connectionLimiter.d.ts +23 -0
- package/dist/connectionLimiter.d.ts.map +1 -0
- package/dist/connectionLimiter.js +24 -0
- package/dist/connectionLimiter.js.map +1 -0
- package/dist/duckdbStore.d.ts.map +1 -1
- package/dist/duckdbStore.js +44 -1
- package/dist/duckdbStore.js.map +1 -1
- package/dist/internalDispatch.d.ts +36 -0
- package/dist/internalDispatch.d.ts.map +1 -0
- package/dist/internalDispatch.js +50 -0
- package/dist/internalDispatch.js.map +1 -0
- package/dist/memoryStore.d.ts.map +1 -1
- package/dist/memoryStore.js +314 -1
- package/dist/memoryStore.js.map +1 -1
- package/dist/mssqlStore.d.ts.map +1 -1
- package/dist/mssqlStore.js +44 -2
- package/dist/mssqlStore.js.map +1 -1
- package/dist/postgresStore.d.ts.map +1 -1
- package/dist/postgresStore.js +44 -2
- package/dist/postgresStore.js.map +1 -1
- package/dist/projectMetadata.d.ts +82 -0
- package/dist/projectMetadata.d.ts.map +1 -0
- package/dist/projectMetadata.js +50 -0
- package/dist/projectMetadata.js.map +1 -0
- package/dist/routes/context.d.ts +135 -0
- package/dist/routes/context.d.ts.map +1 -0
- package/dist/routes/context.js +306 -0
- package/dist/routes/context.js.map +1 -0
- package/dist/routes/insights.d.ts +36 -0
- package/dist/routes/insights.d.ts.map +1 -0
- package/dist/routes/insights.js +810 -0
- package/dist/routes/insights.js.map +1 -0
- package/dist/routes/live.d.ts +8 -0
- package/dist/routes/live.d.ts.map +1 -1
- package/dist/routes/live.js +8 -7
- package/dist/routes/live.js.map +1 -1
- package/dist/routes/mcp.d.ts +36 -0
- package/dist/routes/mcp.d.ts.map +1 -0
- package/dist/routes/mcp.js +262 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/meta.d.ts +16 -2
- package/dist/routes/meta.d.ts.map +1 -1
- package/dist/routes/meta.js +699 -10
- package/dist/routes/meta.js.map +1 -1
- package/dist/routes/metadata.d.ts +31 -0
- package/dist/routes/metadata.d.ts.map +1 -0
- package/dist/routes/metadata.js +163 -0
- package/dist/routes/metadata.js.map +1 -0
- package/dist/routes/narrative.d.ts +36 -0
- package/dist/routes/narrative.d.ts.map +1 -0
- package/dist/routes/narrative.js +95 -0
- package/dist/routes/narrative.js.map +1 -0
- package/dist/routes/panels.d.ts +44 -0
- package/dist/routes/panels.d.ts.map +1 -0
- package/dist/routes/panels.js +108 -0
- package/dist/routes/panels.js.map +1 -0
- package/dist/routes/query-dsl.d.ts +50 -0
- package/dist/routes/query-dsl.d.ts.map +1 -0
- package/dist/routes/query-dsl.js +359 -0
- package/dist/routes/query-dsl.js.map +1 -0
- package/dist/routes/query.d.ts +36 -0
- package/dist/routes/query.d.ts.map +1 -1
- package/dist/routes/query.js +101 -13
- package/dist/routes/query.js.map +1 -1
- package/dist/routes/subscriptions.d.ts +52 -0
- package/dist/routes/subscriptions.d.ts.map +1 -0
- package/dist/routes/subscriptions.js +290 -0
- package/dist/routes/subscriptions.js.map +1 -0
- package/dist/store.d.ts +113 -2
- package/dist/store.d.ts.map +1 -1
- package/dist/subscriptions/cli.d.ts +36 -0
- package/dist/subscriptions/cli.d.ts.map +1 -0
- package/dist/subscriptions/cli.js +143 -0
- package/dist/subscriptions/cli.js.map +1 -0
- package/dist/subscriptions/delivery.d.ts +101 -0
- package/dist/subscriptions/delivery.d.ts.map +1 -0
- package/dist/subscriptions/delivery.js +165 -0
- package/dist/subscriptions/delivery.js.map +1 -0
- package/dist/subscriptions/evaluate.d.ts +119 -0
- package/dist/subscriptions/evaluate.d.ts.map +1 -0
- package/dist/subscriptions/evaluate.js +326 -0
- package/dist/subscriptions/evaluate.js.map +1 -0
- package/dist/subscriptions/scheduler.d.ts +97 -0
- package/dist/subscriptions/scheduler.d.ts.map +1 -0
- package/dist/subscriptions/scheduler.js +332 -0
- package/dist/subscriptions/scheduler.js.map +1 -0
- package/dist/subscriptions/stream.d.ts +40 -0
- package/dist/subscriptions/stream.d.ts.map +1 -0
- package/dist/subscriptions/stream.js +38 -0
- package/dist/subscriptions/stream.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +25 -0
- package/dist/version.js.map +1 -0
- package/dist/webhookSignature.d.ts +64 -0
- package/dist/webhookSignature.d.ts.map +1 -0
- package/dist/webhookSignature.js +91 -0
- package/dist/webhookSignature.js.map +1 -0
- package/llms.txt +38 -1
- package/package.json +17 -14
package/AGENTS.md
CHANGED
|
@@ -40,6 +40,7 @@ mint one with `uptimizr new-key <projectId> --capabilities query` instead.
|
|
|
40
40
|
| `uptimizr migrate` | Apply store migrations. |
|
|
41
41
|
| `uptimizr regions set <scene>` | Replace a scene's named regions from `--file <regions.json>`. |
|
|
42
42
|
| `uptimizr regions get <scene>` | Print a scene's named regions as JSON. |
|
|
43
|
+
| `uptimizr agent report` | Run a read-only agent once and write a Markdown report (see below). |
|
|
43
44
|
| `uptimizr help` | Usage. |
|
|
44
45
|
|
|
45
46
|
`new-key` flags: `--capabilities <list>` (comma-separated; default `query`), `--label <name>`,
|
|
@@ -60,17 +61,54 @@ project you mint is the one the running collector resolves.
|
|
|
60
61
|
Installed as a dependency, the package exposes the `uptimizr` CLI plus the legacy
|
|
61
62
|
`uptimizr-collector` bin (equivalent to `uptimizr serve`).
|
|
62
63
|
|
|
64
|
+
### `uptimizr agent report` — headless scheduled reports (ADR 0051 §6)
|
|
65
|
+
|
|
66
|
+
Runs the headless `runAgent` loop from `@uptimizr/agent-core` **once**, in this process,
|
|
67
|
+
over the generated read-only tool catalog against the collector's query API, and writes
|
|
68
|
+
Markdown to a file, stdout or a signed webhook. The collector gains no in-process LLM loop;
|
|
69
|
+
scheduling is the operator's cron / systemd timer / GitHub Action.
|
|
70
|
+
|
|
71
|
+
| Flag | Meaning |
|
|
72
|
+
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
73
|
+
| `--skill <name>` | Required. A packaged skill: `attention_hotspots` (needs `--scene`), `conversion_investigation`, `performance_regression_triage`, `weekly_scene_health`, `xr_comfort_audit`. |
|
|
74
|
+
| `--list-skills` | Print the skills with their descriptions and the metrics each one reads. |
|
|
75
|
+
| `--scene <id>` | Scope the report to one scene. |
|
|
76
|
+
| `--window <NdNhNw>` | Window back from now (`24h`, `7d` default, `2w`), or `--since` / `--until` in epoch ms. |
|
|
77
|
+
| `--out <file or ->` | Markdown destination (default `-`, stdout). |
|
|
78
|
+
| `--json <file or ->` | Structured report: tool calls with arguments, durations and outcomes, plus token usage when reported. |
|
|
79
|
+
| `--webhook <url>` | `POST {markdown, report}` to an `http(s)` URL. |
|
|
80
|
+
| `--max-steps <n>` | Cap on provider turns (default `8`). |
|
|
81
|
+
| `--dry-run` | Print the prompt and tool list; call no provider. |
|
|
82
|
+
|
|
83
|
+
Environment — read from the environment only and never persisted:
|
|
84
|
+
`UPTIMIZR_COLLECTOR_URL`, `UPTIMIZR_API_KEY` (a `query` key is enough; the command only ever
|
|
85
|
+
reads), `UPTIMIZR_AGENT_PROVIDER` (`anthropic` default | `openai` | `scripted`),
|
|
86
|
+
`UPTIMIZR_AGENT_MODEL`, `UPTIMIZR_AGENT_API_KEY` (falls back to `ANTHROPIC_API_KEY` /
|
|
87
|
+
`OPENAI_API_KEY`), `UPTIMIZR_AGENT_ENDPOINT`, `UPTIMIZR_WEBHOOK_SECRET`. The provider key
|
|
88
|
+
never reaches a log, a report or an error message.
|
|
89
|
+
|
|
90
|
+
The system prompt is the shared analytics guidelines plus the rendered `GET /api/v1/context`
|
|
91
|
+
document, so a run uses the project's real scene ids and custom-event names; a collector
|
|
92
|
+
without that endpoint degrades silently. Webhook bodies carry
|
|
93
|
+
`X-Uptimizr-Signature: sha256=<hex HMAC-SHA-256 of the raw body>` and `X-Uptimizr-Delivery`.
|
|
94
|
+
Exit codes: `0` ok · `1` usage/config · `2` provider or delivery failure · `3` report
|
|
95
|
+
produced but incomplete (a tool call failed, or no answer).
|
|
96
|
+
|
|
97
|
+
`UPTIMIZR_AGENT_PROVIDER=scripted` is a documented, model-free provider: it calls exactly the
|
|
98
|
+
tools the skill names and prints what the collector returned. It is for proving wiring in CI —
|
|
99
|
+
it produces data, not analysis.
|
|
100
|
+
|
|
63
101
|
## API keys and capabilities (ADR 0051 §7)
|
|
64
102
|
|
|
65
103
|
A key carries a **set of capabilities**, not a single role. `new-key` defaults to `query`;
|
|
66
104
|
`init` / `new-project` mint their single key with `query`, `query:raw` and `annotate`.
|
|
67
105
|
|
|
68
|
-
| Capability | Unlocks
|
|
69
|
-
| ----------- |
|
|
70
|
-
| `query` | The aggregate analytics API, the scene registry, the live token exchange, and `GET /api/v1/audit`.
|
|
71
|
-
| `query:raw` | Raw per-session
|
|
72
|
-
| `annotate` | The project **metadata** write path (annotations, glossary, saved analyses, panel specs). Never events.
|
|
73
|
-
| `ingest` | Reserved for server-side write paths. Public ingestion is keyless, so issued keys are normally read keys.
|
|
106
|
+
| Capability | Unlocks |
|
|
107
|
+
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
108
|
+
| `query` | The aggregate analytics API, the scene registry, the live token exchange, and `GET /api/v1/audit`. |
|
|
109
|
+
| `query:raw` | Raw per-session data: `GET /api/v1/sessions/:id/events`, `GET /api/v1/live/sessions/:id`, and the compacted `GET /api/v1/sessions/:id/narrative`. |
|
|
110
|
+
| `annotate` | The project **metadata** write path (annotations, glossary, saved analyses, panel specs). Never events. |
|
|
111
|
+
| `ingest` | Reserved for server-side write paths. Public ingestion is keyless, so issued keys are normally read keys. |
|
|
74
112
|
|
|
75
113
|
The raw endpoints are gated **twice**: the collector must run with `ENABLE_RAW_SESSION_RETENTION`
|
|
76
114
|
**and** the key must hold `query:raw` — otherwise `403`. Retention alone is not enough.
|
|
@@ -100,8 +138,49 @@ Ingestion keeps its separate `COLLECTOR_INGEST_RATE_LIMIT_*` budget.
|
|
|
100
138
|
- The read API: sessions, heatmaps (`pointer`, `camera`, `position`, `world`, `gaze`, `mesh-uv`,
|
|
101
139
|
`click-rays`, `flow`, `perf`, `errors`), mesh/interaction insights, performance and diagnostics,
|
|
102
140
|
scene/path/funnel analytics, scene representations and regions, and the live SSE endpoints.
|
|
141
|
+
- **`POST /api/v1/query`** (and `GET /api/v1/query?q=<url-encoded JSON>`) — **the query DSL**
|
|
142
|
+
(ADR 0051 §3): one endpoint that runs any registry metric. See below.
|
|
103
143
|
- `GET /health` — liveness probe, unauthenticated.
|
|
104
144
|
|
|
145
|
+
### The query DSL: `POST /api/v1/query` (ADR 0051 §3)
|
|
146
|
+
|
|
147
|
+
One endpoint for every metric. Name the `metric`, bound it with a `range`, narrow it with the
|
|
148
|
+
filters that metric declares, cap it, pick the envelope:
|
|
149
|
+
|
|
150
|
+
```jsonc
|
|
151
|
+
{
|
|
152
|
+
"v": 1,
|
|
153
|
+
"metric": "mesh_sources",
|
|
154
|
+
"range": { "since": 1757000000000, "until": 1757600000000 },
|
|
155
|
+
"filters": { "scene": "lobby", "cameraMode": "first-person" },
|
|
156
|
+
"limit": 20,
|
|
157
|
+
"format": "summary",
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`GET /api/v1/query?q=<url-encoded JSON>` takes the same document (8 KiB cap) for GET-only clients.
|
|
162
|
+
Both are reads: same `query` capability, same audit trail, same aggregations.
|
|
163
|
+
|
|
164
|
+
- The grammar is **closed** — no SQL, no expression language, typed filters, bounded output,
|
|
165
|
+
unknown keys rejected. `range` is required; `format` defaults to `table` here, not `full`.
|
|
166
|
+
- A metric, dimension or filter outside the registry's vocabulary is a `400` listing **every**
|
|
167
|
+
objection, each with a stable `code`, the offending `path` and (for a closed list) `accepted`.
|
|
168
|
+
Read `accepted` instead of guessing again.
|
|
169
|
+
- `dimensions` may be any subset a metric declares **when** its measure is a portable count —
|
|
170
|
+
event counts, mesh and interaction tallies, input actions, camera gestures. A spatial heatmap or a
|
|
171
|
+
percentile is computed at one fixed grain and refuses anything else, naming the grain it supports.
|
|
172
|
+
- **`compare`** — another `{ range }` or `{ segment }`; the result comes back joined on the
|
|
173
|
+
dimension key as `{ current, previous, delta, deltaPct }`, with a significance test where the
|
|
174
|
+
measure is a count and both windows clear the metric's minimum. Never subtract two results by hand.
|
|
175
|
+
- **`explain: true`** — the compiled plan instead of the rows: the tier, the SQL with its parameters
|
|
176
|
+
left unbound, `params` by name and type (never value), `rowsScanned`, and `warnings` (a capture
|
|
177
|
+
channel that produced nothing, a sample below the metric's minimum, a truncated result).
|
|
178
|
+
- **`drillQuery`** — every row of a `summary` carries the whole query narrowed to that row, ready to
|
|
179
|
+
send straight back.
|
|
180
|
+
- `order` takes a measure column, and only where the result is a ranked list.
|
|
181
|
+
- `filters.event` (an ADR 0038 step predicate, applied as a **cohort** of sessions) and
|
|
182
|
+
`filters.device` (`os` / `browser` on `session_start`) exist only on the generic tier.
|
|
183
|
+
|
|
105
184
|
### Result envelopes: `format=full | table | summary` (ADR 0051 §2)
|
|
106
185
|
|
|
107
186
|
Every aggregate endpoint accepts `format`. It **filters nothing** — it picks the result envelope:
|
|
@@ -114,6 +193,23 @@ Every aggregate endpoint accepts `format`. It **filters nothing** — it picks t
|
|
|
114
193
|
the registry's `maxSummaryRows`. **This is what makes a 500-bin heatmap affordable for an LLM** —
|
|
115
194
|
prefer it over `full` when feeding a model.
|
|
116
195
|
|
|
196
|
+
### Session narrative: `GET /api/v1/sessions/:id/narrative` (`query:raw`)
|
|
197
|
+
|
|
198
|
+
The one per-session read worth an agent's time. It compacts the raw stream into an ordered account
|
|
199
|
+
of what the session did — scene changes, per-mesh dwell above `minDwellMs`, interactions, frame
|
|
200
|
+
dips below `fpsThreshold`, errors, capability changes, XR entry/exit, the end reason — with
|
|
201
|
+
timestamps **relative to the session's first event** and a closing `summary` entry of totals.
|
|
202
|
+
Bounded by `maxEntries` (default 200, hard cap 1000).
|
|
203
|
+
|
|
204
|
+
`format` here is `full` | `table` | **`text`**. Prefer `text`: one line per entry, about a third of
|
|
205
|
+
the tokens of the JSON, which is what makes a whole session affordable in a context window. There
|
|
206
|
+
is no `summary` envelope — a narrative is already one.
|
|
207
|
+
|
|
208
|
+
Gated **twice**, like the raw stream: `ENABLE_RAW_SESSION_RETENTION` **and** `query:raw`, either
|
|
209
|
+
missing is `403`; an unknown session is `404`. It is a projection, never the stream: no
|
|
210
|
+
`visitorId`, no URL or page metadata, no positions or rays, no `device` detail beyond the engine,
|
|
211
|
+
and custom-event property **keys** only — never their values.
|
|
212
|
+
|
|
117
213
|
### Filters
|
|
118
214
|
|
|
119
215
|
Common query params: `since`, `until` (epoch ms), `bins`, `limit`, `scene`, `session`,
|
|
@@ -129,6 +225,25 @@ set is carried **inside the signed token**, so `GET /api/v1/live/sessions/:id` c
|
|
|
129
225
|
`query:raw` even though `EventSource` cannot send headers. `GET /api/v1/live/presence` and
|
|
130
226
|
`/live/stream` use the same `?token=...`.
|
|
131
227
|
|
|
228
|
+
### Hosted MCP (`/mcp`, ADR 0051 §7)
|
|
229
|
+
|
|
230
|
+
`COLLECTOR_MCP_HTTP=1` (off by default) makes the collector serve the **Model Context Protocol**
|
|
231
|
+
over MCP's Streamable HTTP transport: `POST /mcp` for JSON-RPC, `GET /mcp` for the server→client
|
|
232
|
+
SSE stream, `DELETE /mcp` to end a session. It is the same server `@uptimizr/mcp` runs over stdio —
|
|
233
|
+
same tools, resources and prompts, built by the same factory — so a remote agent needs only a URL
|
|
234
|
+
and a key, with nothing installed locally.
|
|
235
|
+
|
|
236
|
+
- Every request is authenticated with `x-api-key` **or** `Authorization: Bearer <key>` (a bearer
|
|
237
|
+
alias accepted on this route only) and needs `query`: `401` without a key, `403` without the
|
|
238
|
+
capability, `403` if a session id is presented by a different key than opened it.
|
|
239
|
+
- Tool calls are dispatched to the collector's own query routes **in process**, so they run the
|
|
240
|
+
same validation, scoping and result envelope as the equivalent `curl`, and cost the caller's
|
|
241
|
+
ordinary per-key rate-limit budget once.
|
|
242
|
+
- Bounded by `COLLECTOR_MCP_MAX_SESSIONS` (default `50`, one too many → `503`) and
|
|
243
|
+
`COLLECTOR_MCP_SESSION_TTL_MS` (default 30 minutes idle). Audited with `surface: "mcp-http"`.
|
|
244
|
+
- Behind a reverse proxy, disable response buffering for `/mcp` (it answers with SSE) and pin a
|
|
245
|
+
session to one instance if you run several.
|
|
246
|
+
|
|
132
247
|
## Storage (`COLLECTOR_STORE`)
|
|
133
248
|
|
|
134
249
|
| Value | Store |
|
|
@@ -142,6 +257,59 @@ set is carried **inside the signed token**, so `GET /api/v1/live/sessions/:id` c
|
|
|
142
257
|
All four return **identical analytics** (the cross-engine parity suite). Aggregations are computed
|
|
143
258
|
at **query time** in v1 — no materialized views.
|
|
144
259
|
|
|
260
|
+
## Conditional subscriptions (ADR 0051 §6)
|
|
261
|
+
|
|
262
|
+
Standing predicates over a registry metric, delivered over SSE and signed webhooks.
|
|
263
|
+
|
|
264
|
+
- `GET /api/v1/subscriptions` · `GET /api/v1/subscriptions/:id` ·
|
|
265
|
+
`GET /api/v1/subscriptions/:id/events` — need `query`.
|
|
266
|
+
- `POST /api/v1/subscriptions` · `PATCH /api/v1/subscriptions/:id` (`{ enabled }` only) ·
|
|
267
|
+
`DELETE /api/v1/subscriptions/:id` · `POST /api/v1/subscriptions/:id/test[?deliver=true]` —
|
|
268
|
+
need **`annotate`**: creating one is how a caller asks the collector to make an outbound
|
|
269
|
+
request on its behalf.
|
|
270
|
+
- `GET /api/v1/subscriptions/stream?token=…` — SSE, live-token auth (ADR 0032 §7), optional
|
|
271
|
+
`&id=` filter, shares `LIVE_MAX_CONNECTIONS`.
|
|
272
|
+
|
|
273
|
+
Predicates: `threshold` (on the metric's registry headline column only), `anomaly`, `movers`,
|
|
274
|
+
`new_value`, `presence`. `evaluate.every` ≥ 1m, `evaluate.window` ≥ 1h. 100 per project, last
|
|
275
|
+
100 firings each.
|
|
276
|
+
|
|
277
|
+
**A webhook secret is write-only** — accepted on create, never returned; reads carry a mask.
|
|
278
|
+
**Webhook egress is off until `COLLECTOR_WEBHOOK_ALLOWED_HOSTS` names the hosts**, because a
|
|
279
|
+
subscription URL arrives over HTTP and is therefore request-controlled input to an outbound
|
|
280
|
+
request. Bodies are signed `X-Uptimizr-Signature: sha256=<hex>` over the raw bytes; verify before
|
|
281
|
+
parsing, in constant time.
|
|
282
|
+
|
|
283
|
+
`POST …/test` is a dry run by default and answers with _why_ it did or did not fire.
|
|
284
|
+
|
|
285
|
+
## Pinned panels (ADR 0051 §7)
|
|
286
|
+
|
|
287
|
+
Declarative panels an agent leaves on the project's dashboard. A spec is a title, a query, a chart
|
|
288
|
+
name, an optional encoding, a span and a one-line note — **data**, which the dashboard draws with
|
|
289
|
+
panel components `@uptimizr/react` already ships. No module is loaded and nothing is evaluated, so
|
|
290
|
+
ADR 0041's remote-panel trust decision is not widened.
|
|
291
|
+
|
|
292
|
+
- `GET /api/v1/panels` (`limit`) — the project's pinned panels, **oldest first**: these are grid
|
|
293
|
+
positions, not a feed. Needs `query`.
|
|
294
|
+
- `POST /api/v1/panels` → `201` · `PUT /api/v1/panels/:id` · `DELETE /api/v1/panels/:id` → `204` —
|
|
295
|
+
need **`annotate`**, and are audited like every other metadata write. `PUT` is a full replacement
|
|
296
|
+
(half a spec is not a panel); the row keeps its id, its place in the grid and its original
|
|
297
|
+
authorship. An unknown id — or one belonging to another project, which is deliberately
|
|
298
|
+
indistinguishable — is `404`.
|
|
299
|
+
- **Validated twice**, for two different questions: `panelSpecV1Schema` (`@uptimizr/schema`) for the
|
|
300
|
+
shape, then `validatePanelSpec` (`@uptimizr/metrics`) for the vocabulary — does the metric exist
|
|
301
|
+
and accept these filters, does the chart suit its grain, do the encoding columns exist in its
|
|
302
|
+
result. A failure is `400 { error, issues }`, the same body `POST /api/v1/query` answers with, so
|
|
303
|
+
a client fixes the spec from the response instead of guessing. The second check runs at **pin**
|
|
304
|
+
time because a pinned panel is read weeks later: a line chart with no axis to walk along does not
|
|
305
|
+
fail, it draws something a reader takes for a trend.
|
|
306
|
+
- `409` when the project already holds `LIMITS.maxProjectPanelSpecs` (50) panels.
|
|
307
|
+
- `query.range` may be the literal `"inherit"`, which the dashboard resolves against its filter bar
|
|
308
|
+
on every load, or an explicit `{ since, until }` to pin one period.
|
|
309
|
+
- OpenAPI operation ids `list_panels`, `pin_panel`, `update_panel` and `unpin_panel`, under a
|
|
310
|
+
`panels` tag. `CollectorStore` gains `createPanelSpec` / `listPanelSpecs` / `updatePanelSpec` /
|
|
311
|
+
`deletePanelSpec`, implemented by all five stores.
|
|
312
|
+
|
|
145
313
|
## Other configuration
|
|
146
314
|
|
|
147
315
|
- Server / browser access: `COLLECTOR_HOST` (`0.0.0.0`), `COLLECTOR_PORT` (`4318`),
|
|
@@ -151,9 +319,14 @@ at **query time** in v1 — no materialized views.
|
|
|
151
319
|
`LIVE_MAX_CONNECTIONS`, `LIVE_PRESENCE_INTERVAL_MS`.
|
|
152
320
|
- Rate limits: `COLLECTOR_RATE_LIMIT_MAX`, `COLLECTOR_RATE_LIMIT_WINDOW_MS`,
|
|
153
321
|
`COLLECTOR_INGEST_RATE_LIMIT_MAX`, `COLLECTOR_INGEST_RATE_LIMIT_WINDOW_MS`.
|
|
322
|
+
- Subscriptions: `COLLECTOR_SUBSCRIPTIONS` (default on; `0` keeps the API and runs no timers),
|
|
323
|
+
`COLLECTOR_SUBSCRIPTIONS_MAX_CONCURRENT` (default `4`),
|
|
324
|
+
**`COLLECTOR_WEBHOOK_ALLOWED_HOSTS`** (empty = no webhook egress at all).
|
|
154
325
|
- Agent audit: **`AUDIT_RETENTION_DAYS`** (default `30`; `0` = keep forever),
|
|
155
326
|
`AUDIT_DASHBOARD_REQUESTS` (default off — requests carrying `x-uptimizr-client: dashboard` are
|
|
156
327
|
skipped as a volume filter, **not** a security boundary).
|
|
328
|
+
- Hosted MCP: `COLLECTOR_MCP_HTTP` (off by default), `COLLECTOR_MCP_MAX_SESSIONS` (`50`),
|
|
329
|
+
`COLLECTOR_MCP_SESSION_TTL_MS` (`1800000`).
|
|
157
330
|
- All-in-one dashboard: `COLLECTOR_DASHBOARD_DIR` (point it at a static dashboard export and one
|
|
158
331
|
process serves ingestion, queries and the UI), `COLLECTOR_CSP` (`strict` or `off`).
|
|
159
332
|
|
package/README.md
CHANGED
|
@@ -75,6 +75,52 @@ so it needs no API key. Over HTTP the same thing is
|
|
|
75
75
|
an `annotate`-capable key, and from a client build `registerRegions` in
|
|
76
76
|
`@uptimizr/sdk-core`.
|
|
77
77
|
|
|
78
|
+
### Scheduled reports: `uptimizr agent report`
|
|
79
|
+
|
|
80
|
+
Run a read-only analytics agent **once** and write a Markdown report — a weekly
|
|
81
|
+
scene-health digest with nobody in the chair. It is an ordinary CLI process that
|
|
82
|
+
reads this collector's query API with an ordinary key, so the collector itself
|
|
83
|
+
gains no LLM loop and scheduling stays yours (cron, a systemd timer, a GitHub
|
|
84
|
+
Action).
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# The narrow key a report should hold
|
|
88
|
+
npx -p @uptimizr/collector-server uptimizr new-key <projectId> \
|
|
89
|
+
--capabilities query --label "weekly-report"
|
|
90
|
+
|
|
91
|
+
export UPTIMIZR_COLLECTOR_URL=https://collect.example.com
|
|
92
|
+
export UPTIMIZR_API_KEY=utk_… # the query-only key above
|
|
93
|
+
export UPTIMIZR_AGENT_API_KEY=sk-ant-… # your own provider key
|
|
94
|
+
|
|
95
|
+
npx -p @uptimizr/collector-server uptimizr agent report \
|
|
96
|
+
--skill weekly_scene_health --scene lobby --window 7d \
|
|
97
|
+
--out report.md --json report.json --webhook https://hooks.example.com/uptimizr
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- `--list-skills` prints the investigations this release ships
|
|
101
|
+
(`attention_hotspots`, `conversion_investigation`,
|
|
102
|
+
`performance_regression_triage`, `weekly_scene_health`, `xr_comfort_audit`) and
|
|
103
|
+
the metrics each one reads — the same packaged methodologies `@uptimizr/mcp`
|
|
104
|
+
offers as prompts.
|
|
105
|
+
- `--dry-run` prints the exact prompt and tool list and calls no provider;
|
|
106
|
+
`UPTIMIZR_AGENT_PROVIDER=scripted` runs the whole path with no model, no key
|
|
107
|
+
and no egress (useful in CI — it produces data, not analysis).
|
|
108
|
+
- Provider configuration is read from the environment only and never persisted:
|
|
109
|
+
`UPTIMIZR_AGENT_PROVIDER` (`anthropic` | `openai` | `scripted`),
|
|
110
|
+
`UPTIMIZR_AGENT_MODEL`, `UPTIMIZR_AGENT_API_KEY`, `UPTIMIZR_AGENT_ENDPOINT`.
|
|
111
|
+
The key is never logged, echoed or written into a report.
|
|
112
|
+
- Webhook deliveries are signed with
|
|
113
|
+
`X-Uptimizr-Signature: sha256=<hex HMAC-SHA-256 of the raw body>` keyed with
|
|
114
|
+
`UPTIMIZR_WEBHOOK_SECRET`, plus a unique `X-Uptimizr-Delivery` id.
|
|
115
|
+
- Exit codes: `0` success · `1` usage/configuration · `2` provider or delivery
|
|
116
|
+
failure · `3` report produced but incomplete (a tool call failed, or no answer).
|
|
117
|
+
|
|
118
|
+
Every report ends with a **Method** section listing each tool call and its
|
|
119
|
+
arguments, so an unattended, model-written document stays auditable. See
|
|
120
|
+
`uptimizr agent report --help`, or the
|
|
121
|
+
[deploy guide](https://uptimizr.com/docs/deploy/collector/#scheduled-agent-reports)
|
|
122
|
+
for a copy-pasteable weekly GitHub Actions workflow.
|
|
123
|
+
|
|
78
124
|
### All-in-one: serve the dashboard too
|
|
79
125
|
|
|
80
126
|
The collector can also serve a pre-built static dashboard from its own origin, so
|
|
@@ -207,6 +253,26 @@ what makes a 500-bin heatmap affordable for an LLM. See
|
|
|
207
253
|
|
|
208
254
|
- `GET /health` — liveness probe.
|
|
209
255
|
|
|
256
|
+
### Hosted MCP (`/mcp`, opt-in)
|
|
257
|
+
|
|
258
|
+
Start the collector with `COLLECTOR_MCP_HTTP=1` and it also speaks the **Model Context Protocol**
|
|
259
|
+
over Streamable HTTP, so a remote AI client connects with a URL and an API key instead of running
|
|
260
|
+
`npx @uptimizr/mcp` locally (ADR 0051 §7). It is off by default — without the variable the route is
|
|
261
|
+
not registered.
|
|
262
|
+
|
|
263
|
+
| Method | Path | Purpose |
|
|
264
|
+
| -------- | ------ | ---------------------------------------------------------------------- |
|
|
265
|
+
| `POST` | `/mcp` | JSON-RPC. Without `Mcp-Session-Id`, only `initialize` opens a session. |
|
|
266
|
+
| `GET` | `/mcp` | The server→client SSE stream for an existing session. |
|
|
267
|
+
| `DELETE` | `/mcp` | End a session and release its slot. |
|
|
268
|
+
|
|
269
|
+
Every request is authenticated with `x-api-key` or `Authorization: Bearer <key>` and needs `query`;
|
|
270
|
+
a session may only be driven by the key that opened it. Tools, resources and prompts are exactly
|
|
271
|
+
`@uptimizr/mcp`'s. Sessions are capped by `COLLECTOR_MCP_MAX_SESSIONS` (default `50`, one too many
|
|
272
|
+
→ `503`) and expire after `COLLECTOR_MCP_SESSION_TTL_MS` idle (default 30 minutes); tool calls are
|
|
273
|
+
audited with `surface: "mcp-http"`. Behind a reverse proxy, turn response buffering **off** for
|
|
274
|
+
`/mcp`. See the [MCP guide](https://uptimizr.com/docs/guides/mcp/#hosted-transport-streamable-http).
|
|
275
|
+
|
|
210
276
|
## Security
|
|
211
277
|
|
|
212
278
|
`@fastify/helmet`, `@fastify/cors` (restricted to `COLLECTOR_CORS_ORIGINS`), and
|
|
@@ -223,6 +289,7 @@ if `VISITOR_HASH_SECRET` is missing.
|
|
|
223
289
|
| Live SSE routes (`/api/v1/live/*` `GET`s) | `?token=...` | Browser `EventSource` cannot attach custom headers, so live streams use short-lived bearer tokens. |
|
|
224
290
|
| `GET /health` | None | Liveness probe. |
|
|
225
291
|
| `GET /api/v1/openapi.json` | None | API documentation, not data — a client needs it before it has a key. Rate-limited like every other route. |
|
|
292
|
+
| `/mcp` (when `COLLECTOR_MCP_HTTP=1`) | `x-api-key` | Hosted MCP. `Authorization: Bearer <key>` is accepted as an alias on this route only; the session id is never a credential on its own. |
|
|
226
293
|
|
|
227
294
|
### API keys and capabilities
|
|
228
295
|
|
|
@@ -306,6 +373,8 @@ Environment-driven (see [`.env.example`](../../../.env.example)):
|
|
|
306
373
|
- Rate limits: `COLLECTOR_RATE_LIMIT_MAX`, `COLLECTOR_RATE_LIMIT_WINDOW_MS`,
|
|
307
374
|
`COLLECTOR_INGEST_RATE_LIMIT_MAX`, `COLLECTOR_INGEST_RATE_LIMIT_WINDOW_MS`
|
|
308
375
|
(a key's own budget overrides the first pair).
|
|
376
|
+
- Hosted MCP: `COLLECTOR_MCP_HTTP` (off by default), `COLLECTOR_MCP_MAX_SESSIONS`
|
|
377
|
+
(default `50`), `COLLECTOR_MCP_SESSION_TTL_MS` (default `1800000`).
|
|
309
378
|
- Agent audit: `AUDIT_RETENTION_DAYS` (default `30`, `0` = keep forever),
|
|
310
379
|
`AUDIT_DASHBOARD_REQUESTS` (default off).
|
|
311
380
|
- All-in-one dashboard: `COLLECTOR_DASHBOARD_DIR` (optional; see
|
|
@@ -34,7 +34,8 @@ export declare const TEST_PROXY: {
|
|
|
34
34
|
export declare const PATH_PARAM_VALUES: Readonly<Record<string, string>>;
|
|
35
35
|
/**
|
|
36
36
|
* Query parameters an endpoint needs beyond the shared range. Only the genuinely
|
|
37
|
-
* required ones: `mesh` for the per-mesh UV heatmap
|
|
37
|
+
* required ones: `mesh` for the per-mesh UV heatmap, `steps` for the funnel and
|
|
38
|
+
* `metric` for the baseline.
|
|
38
39
|
*/
|
|
39
40
|
export declare const REQUIRED_QUERY: Readonly<Record<string, Record<string, string>>>;
|
|
40
41
|
/** The two resource reads, which legitimately 404 when nothing is registered. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registryRequests.d.ts","sourceRoot":"","sources":["../../../src/__tests__/support/registryRequests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,8EAA8E;AAC9E,eAAO,MAAM,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"registryRequests.d.ts","sourceRoot":"","sources":["../../../src/__tests__/support/registryRequests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,8EAA8E;AAC9E,eAAO,MAAM,WAAW,EAAE,eA6BzB,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,UAAU;;;;YAIW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;;;;;cAM5C,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;;;;;CAMrF,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAI9D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAY3E,CAAC;AAEF,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAG/C,CAAC;AAEH;;;GAGG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,gBAAgB,EACxB,KAAK,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GAC3C,MAAM,CAYR"}
|
|
@@ -33,6 +33,14 @@ export const TEST_CONFIG = {
|
|
|
33
33
|
cspMode: "strict",
|
|
34
34
|
auditRetentionDays: 30,
|
|
35
35
|
auditDashboardRequests: false,
|
|
36
|
+
mcpHttpEnabled: false,
|
|
37
|
+
mcpMaxSessions: 50,
|
|
38
|
+
mcpSessionTtlMs: 1_800_000,
|
|
39
|
+
// The sweeps build an app per case; leaving the scheduler off keeps them free
|
|
40
|
+
// of timers they never assert on. Webhook egress stays disallowed by default.
|
|
41
|
+
subscriptions: false,
|
|
42
|
+
subscriptionsMaxConcurrent: 4,
|
|
43
|
+
webhookAllowedHosts: [],
|
|
36
44
|
};
|
|
37
45
|
/** Fixture scene proxy, so the `scene_representation` resource has a hit. */
|
|
38
46
|
export const TEST_PROXY = {
|
|
@@ -60,13 +68,21 @@ export const PATH_PARAM_VALUES = {
|
|
|
60
68
|
};
|
|
61
69
|
/**
|
|
62
70
|
* Query parameters an endpoint needs beyond the shared range. Only the genuinely
|
|
63
|
-
* required ones: `mesh` for the per-mesh UV heatmap
|
|
71
|
+
* required ones: `mesh` for the per-mesh UV heatmap, `steps` for the funnel and
|
|
72
|
+
* `metric` for the baseline.
|
|
64
73
|
*/
|
|
65
74
|
export const REQUIRED_QUERY = {
|
|
66
75
|
"/api/v1/heatmaps/mesh-uv": { mesh: "box" },
|
|
67
76
|
"/api/v1/funnel": {
|
|
68
77
|
steps: JSON.stringify([{ type: "session_start" }, { type: "pointer_click" }]),
|
|
69
78
|
},
|
|
79
|
+
// A baseline is a baseline *of* something: the metric is the subject rather
|
|
80
|
+
// than a filter, so it is the one insight parameter with no default.
|
|
81
|
+
"/api/v1/insights/baseline": { metric: "list_sessions" },
|
|
82
|
+
// --- anomalies (#306) --- same subject-is-the-parameter rule.
|
|
83
|
+
"/api/v1/insights/anomalies": { metric: "list_sessions" },
|
|
84
|
+
// --- significance / scene health (#307) ---
|
|
85
|
+
"/api/v1/insights/significance": { metric: "list_sessions" },
|
|
70
86
|
};
|
|
71
87
|
/** The two resource reads, which legitimately 404 when nothing is registered. */
|
|
72
88
|
export const RESOURCE_METRICS = new Set([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registryRequests.js","sourceRoot":"","sources":["../../../src/__tests__/support/registryRequests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,8EAA8E;AAC9E,MAAM,CAAC,MAAM,WAAW,GAAoB;IAC1C,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,CAAC;IACP,WAAW,EAAE,EAAE;IACf,iBAAiB,EAAE,aAAa;IAChC,yBAAyB,EAAE,KAAK;IAChC,YAAY,EAAE,MAAM;IACpB,eAAe,EAAE,kBAAkB;IACnC,0BAA0B,EAAE,IAAI;IAChC,cAAc,EAAE,OAAO;IACvB,kBAAkB,EAAE,GAAG;IACvB,sBAAsB,EAAE,KAAK;IAC7B,YAAY,EAAE,IAAI;IAClB,iBAAiB,EAAE,MAAM;IACzB,kBAAkB,EAAE,IAAI;IACxB,uBAAuB,EAAE,MAAM;IAC/B,UAAU,EAAE,KAAK;IACjB,SAAS,EAAE,SAAS;IACpB,OAAO,EAAE,QAAQ;IACjB,kBAAkB,EAAE,EAAE;IACtB,sBAAsB,EAAE,KAAK;
|
|
1
|
+
{"version":3,"file":"registryRequests.js","sourceRoot":"","sources":["../../../src/__tests__/support/registryRequests.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for the subscription route suite (#311).
|
|
3
|
+
*
|
|
4
|
+
* Re-exported through this module rather than imported directly in the test so
|
|
5
|
+
* the numbers and the mask come from the single definitions in
|
|
6
|
+
* `@uptimizr/schema` and `@uptimizr/db` — a test that hard-codes `100` or
|
|
7
|
+
* `"••••••••"` stops testing the bound the moment the bound moves.
|
|
8
|
+
*
|
|
9
|
+
* Not a test file — the filename has no `.test.` segment, so Vitest does not
|
|
10
|
+
* collect it.
|
|
11
|
+
*/
|
|
12
|
+
export declare const LIMITS: {
|
|
13
|
+
readonly maxBatchEvents: 1000;
|
|
14
|
+
readonly maxProjectIdLength: 128;
|
|
15
|
+
readonly maxSessionIdLength: 128;
|
|
16
|
+
readonly maxSdkVersionLength: 64;
|
|
17
|
+
readonly maxUrlLength: 2048;
|
|
18
|
+
readonly maxTitleLength: 512;
|
|
19
|
+
readonly maxReferrerLength: 2048;
|
|
20
|
+
readonly maxLanguageLength: 35;
|
|
21
|
+
readonly maxMeshNameLength: 256;
|
|
22
|
+
readonly maxAssetNameLength: 1024;
|
|
23
|
+
readonly maxCustomNameLength: 128;
|
|
24
|
+
readonly maxCustomPropValueLength: 1024;
|
|
25
|
+
readonly maxCustomPropEntries: 64;
|
|
26
|
+
readonly maxUserIdLength: 128;
|
|
27
|
+
readonly maxUserTraitValueLength: 1024;
|
|
28
|
+
readonly maxUserTraitEntries: 64;
|
|
29
|
+
readonly maxSceneDescriptionLength: 256;
|
|
30
|
+
readonly maxCameraNameLength: 128;
|
|
31
|
+
readonly maxSceneProxyMeshNameLength: 256;
|
|
32
|
+
readonly maxSceneProxyMeshPathLength: 512;
|
|
33
|
+
readonly maxSceneProxyMeshes: 10000;
|
|
34
|
+
readonly maxSceneRegionLabelLength: 120;
|
|
35
|
+
readonly maxSceneRegionDescriptionLength: 500;
|
|
36
|
+
readonly maxSceneRegions: 200;
|
|
37
|
+
readonly maxSubscriptionNameLength: 120;
|
|
38
|
+
readonly maxSubscriptionDeliveries: 4;
|
|
39
|
+
readonly maxSubscriptionsPerProject: 100;
|
|
40
|
+
readonly maxSubscriptionEvents: 100;
|
|
41
|
+
readonly maxNodeIdLength: 128;
|
|
42
|
+
readonly maxBoneIdLength: 128;
|
|
43
|
+
readonly maxChildPathLength: 512;
|
|
44
|
+
readonly maxGraphicsDiagnosticMessageLength: 1024;
|
|
45
|
+
readonly maxGraphicsDiagnosticCodeLength: 64;
|
|
46
|
+
readonly maxAnnotationTextLength: 2000;
|
|
47
|
+
readonly maxAnnotationTargetIdLength: 256;
|
|
48
|
+
readonly maxProjectAnnotations: 500;
|
|
49
|
+
readonly maxGlossaryTermLength: 64;
|
|
50
|
+
readonly maxGlossaryMeaningLength: 500;
|
|
51
|
+
readonly maxProjectGlossaryEntries: 200;
|
|
52
|
+
readonly maxSavedAnalysisTitleLength: 120;
|
|
53
|
+
readonly maxSavedAnalysisConclusionLength: 4000;
|
|
54
|
+
readonly maxSavedAnalysisQueryLength: 8000;
|
|
55
|
+
readonly maxProjectSavedAnalyses: 200;
|
|
56
|
+
readonly maxPanelSpecTitleLength: 120;
|
|
57
|
+
readonly maxPanelSpecNoteLength: 500;
|
|
58
|
+
readonly maxPanelEncodingColumnLength: 64;
|
|
59
|
+
readonly maxProjectPanelSpecs: 50;
|
|
60
|
+
};
|
|
61
|
+
/** What every read path shows in place of a stored webhook secret. */
|
|
62
|
+
export declare const MASKED_SECRET_PLACEHOLDER = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
63
|
+
//# sourceMappingURL=subscriptionFixtures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscriptionFixtures.d.ts","sourceRoot":"","sources":["../../../src/__tests__/support/subscriptionFixtures.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAgB,CAAC;AAEpC,sEAAsE;AACtE,eAAO,MAAM,yBAAyB,qDAAgB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { LIMITS as SCHEMA_LIMITS } from "@uptimizr/schema";
|
|
2
|
+
import { MASKED_SECRET } from "@uptimizr/db";
|
|
3
|
+
/**
|
|
4
|
+
* Shared constants for the subscription route suite (#311).
|
|
5
|
+
*
|
|
6
|
+
* Re-exported through this module rather than imported directly in the test so
|
|
7
|
+
* the numbers and the mask come from the single definitions in
|
|
8
|
+
* `@uptimizr/schema` and `@uptimizr/db` — a test that hard-codes `100` or
|
|
9
|
+
* `"••••••••"` stops testing the bound the moment the bound moves.
|
|
10
|
+
*
|
|
11
|
+
* Not a test file — the filename has no `.test.` segment, so Vitest does not
|
|
12
|
+
* collect it.
|
|
13
|
+
*/
|
|
14
|
+
export const LIMITS = SCHEMA_LIMITS;
|
|
15
|
+
/** What every read path shows in place of a stored webhook secret. */
|
|
16
|
+
export const MASKED_SECRET_PLACEHOLDER = MASKED_SECRET;
|
|
17
|
+
//# sourceMappingURL=subscriptionFixtures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscriptionFixtures.js","sourceRoot":"","sources":["../../../src/__tests__/support/subscriptionFixtures.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C;;;;;;;;;;GAUG;AAEH,MAAM,CAAC,MAAM,MAAM,GAAG,aAAa,CAAC;AAEpC,sEAAsE;AACtE,MAAM,CAAC,MAAM,yBAAyB,GAAG,aAAa,CAAC"}
|