@uptimizr/collector-server 2.0.1 → 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.
Files changed (139) hide show
  1. package/AGENTS.md +187 -11
  2. package/README.md +91 -7
  3. package/dist/__tests__/support/registryRequests.d.ts +2 -1
  4. package/dist/__tests__/support/registryRequests.d.ts.map +1 -1
  5. package/dist/__tests__/support/registryRequests.js +17 -1
  6. package/dist/__tests__/support/registryRequests.js.map +1 -1
  7. package/dist/__tests__/support/subscriptionFixtures.d.ts +63 -0
  8. package/dist/__tests__/support/subscriptionFixtures.d.ts.map +1 -0
  9. package/dist/__tests__/support/subscriptionFixtures.js +17 -0
  10. package/dist/__tests__/support/subscriptionFixtures.js.map +1 -0
  11. package/dist/agentReport.d.ts +256 -0
  12. package/dist/agentReport.d.ts.map +1 -0
  13. package/dist/agentReport.js +861 -0
  14. package/dist/agentReport.js.map +1 -0
  15. package/dist/app.d.ts +13 -0
  16. package/dist/app.d.ts.map +1 -1
  17. package/dist/app.js +112 -5
  18. package/dist/app.js.map +1 -1
  19. package/dist/audit.d.ts +5 -1
  20. package/dist/audit.d.ts.map +1 -1
  21. package/dist/audit.js +24 -4
  22. package/dist/audit.js.map +1 -1
  23. package/dist/auth.d.ts +41 -1
  24. package/dist/auth.d.ts.map +1 -1
  25. package/dist/auth.js +47 -0
  26. package/dist/auth.js.map +1 -1
  27. package/dist/cli.js +133 -13
  28. package/dist/cli.js.map +1 -1
  29. package/dist/cliStore.d.ts +31 -2
  30. package/dist/cliStore.d.ts.map +1 -1
  31. package/dist/cliStore.js +34 -0
  32. package/dist/cliStore.js.map +1 -1
  33. package/dist/clickhouseStore.d.ts.map +1 -1
  34. package/dist/clickhouseStore.js +44 -2
  35. package/dist/clickhouseStore.js.map +1 -1
  36. package/dist/config.d.ts +49 -0
  37. package/dist/config.d.ts.map +1 -1
  38. package/dist/config.js +21 -0
  39. package/dist/config.js.map +1 -1
  40. package/dist/connectionLimiter.d.ts +23 -0
  41. package/dist/connectionLimiter.d.ts.map +1 -0
  42. package/dist/connectionLimiter.js +24 -0
  43. package/dist/connectionLimiter.js.map +1 -0
  44. package/dist/duckdbStore.d.ts.map +1 -1
  45. package/dist/duckdbStore.js +44 -1
  46. package/dist/duckdbStore.js.map +1 -1
  47. package/dist/internalDispatch.d.ts +36 -0
  48. package/dist/internalDispatch.d.ts.map +1 -0
  49. package/dist/internalDispatch.js +50 -0
  50. package/dist/internalDispatch.js.map +1 -0
  51. package/dist/memoryStore.d.ts.map +1 -1
  52. package/dist/memoryStore.js +314 -1
  53. package/dist/memoryStore.js.map +1 -1
  54. package/dist/mssqlStore.d.ts.map +1 -1
  55. package/dist/mssqlStore.js +44 -2
  56. package/dist/mssqlStore.js.map +1 -1
  57. package/dist/postgresStore.d.ts.map +1 -1
  58. package/dist/postgresStore.js +44 -2
  59. package/dist/postgresStore.js.map +1 -1
  60. package/dist/projectMetadata.d.ts +82 -0
  61. package/dist/projectMetadata.d.ts.map +1 -0
  62. package/dist/projectMetadata.js +50 -0
  63. package/dist/projectMetadata.js.map +1 -0
  64. package/dist/routes/context.d.ts +135 -0
  65. package/dist/routes/context.d.ts.map +1 -0
  66. package/dist/routes/context.js +306 -0
  67. package/dist/routes/context.js.map +1 -0
  68. package/dist/routes/insights.d.ts +36 -0
  69. package/dist/routes/insights.d.ts.map +1 -0
  70. package/dist/routes/insights.js +810 -0
  71. package/dist/routes/insights.js.map +1 -0
  72. package/dist/routes/live.d.ts +8 -0
  73. package/dist/routes/live.d.ts.map +1 -1
  74. package/dist/routes/live.js +8 -7
  75. package/dist/routes/live.js.map +1 -1
  76. package/dist/routes/mcp.d.ts +36 -0
  77. package/dist/routes/mcp.d.ts.map +1 -0
  78. package/dist/routes/mcp.js +262 -0
  79. package/dist/routes/mcp.js.map +1 -0
  80. package/dist/routes/meta.d.ts +16 -2
  81. package/dist/routes/meta.d.ts.map +1 -1
  82. package/dist/routes/meta.js +699 -10
  83. package/dist/routes/meta.js.map +1 -1
  84. package/dist/routes/metadata.d.ts +31 -0
  85. package/dist/routes/metadata.d.ts.map +1 -0
  86. package/dist/routes/metadata.js +163 -0
  87. package/dist/routes/metadata.js.map +1 -0
  88. package/dist/routes/narrative.d.ts +36 -0
  89. package/dist/routes/narrative.d.ts.map +1 -0
  90. package/dist/routes/narrative.js +95 -0
  91. package/dist/routes/narrative.js.map +1 -0
  92. package/dist/routes/panels.d.ts +44 -0
  93. package/dist/routes/panels.d.ts.map +1 -0
  94. package/dist/routes/panels.js +108 -0
  95. package/dist/routes/panels.js.map +1 -0
  96. package/dist/routes/query-dsl.d.ts +50 -0
  97. package/dist/routes/query-dsl.d.ts.map +1 -0
  98. package/dist/routes/query-dsl.js +359 -0
  99. package/dist/routes/query-dsl.js.map +1 -0
  100. package/dist/routes/query.d.ts +36 -0
  101. package/dist/routes/query.d.ts.map +1 -1
  102. package/dist/routes/query.js +101 -13
  103. package/dist/routes/query.js.map +1 -1
  104. package/dist/routes/subscriptions.d.ts +52 -0
  105. package/dist/routes/subscriptions.d.ts.map +1 -0
  106. package/dist/routes/subscriptions.js +290 -0
  107. package/dist/routes/subscriptions.js.map +1 -0
  108. package/dist/store.d.ts +113 -2
  109. package/dist/store.d.ts.map +1 -1
  110. package/dist/subscriptions/cli.d.ts +36 -0
  111. package/dist/subscriptions/cli.d.ts.map +1 -0
  112. package/dist/subscriptions/cli.js +143 -0
  113. package/dist/subscriptions/cli.js.map +1 -0
  114. package/dist/subscriptions/delivery.d.ts +101 -0
  115. package/dist/subscriptions/delivery.d.ts.map +1 -0
  116. package/dist/subscriptions/delivery.js +165 -0
  117. package/dist/subscriptions/delivery.js.map +1 -0
  118. package/dist/subscriptions/evaluate.d.ts +119 -0
  119. package/dist/subscriptions/evaluate.d.ts.map +1 -0
  120. package/dist/subscriptions/evaluate.js +326 -0
  121. package/dist/subscriptions/evaluate.js.map +1 -0
  122. package/dist/subscriptions/scheduler.d.ts +97 -0
  123. package/dist/subscriptions/scheduler.d.ts.map +1 -0
  124. package/dist/subscriptions/scheduler.js +332 -0
  125. package/dist/subscriptions/scheduler.js.map +1 -0
  126. package/dist/subscriptions/stream.d.ts +40 -0
  127. package/dist/subscriptions/stream.d.ts.map +1 -0
  128. package/dist/subscriptions/stream.js +38 -0
  129. package/dist/subscriptions/stream.js.map +1 -0
  130. package/dist/version.d.ts +3 -0
  131. package/dist/version.d.ts.map +1 -0
  132. package/dist/version.js +25 -0
  133. package/dist/version.js.map +1 -0
  134. package/dist/webhookSignature.d.ts +64 -0
  135. package/dist/webhookSignature.d.ts.map +1 -0
  136. package/dist/webhookSignature.js +91 -0
  137. package/dist/webhookSignature.js.map +1 -0
  138. package/llms.txt +39 -2
  139. package/package.json +17 -14
package/AGENTS.md CHANGED
@@ -17,7 +17,7 @@ and your own agents all read through this HTTP API, never the database.
17
17
 
18
18
  ```bash
19
19
  # 1. One-time setup: generate a visitor-hash secret, create + migrate the store,
20
- # mint a first project + API key, write a local .env.
20
+ # mint a first project + owner API key, write a local .env.
21
21
  npx -p @uptimizr/collector-server uptimizr init "My Project"
22
22
 
23
23
  # 2. Start the ingestion + query API (reads the generated .env; 0.0.0.0:4318).
@@ -25,19 +25,22 @@ npx -p @uptimizr/collector-server uptimizr serve
25
25
  ```
26
26
 
27
27
  `init` prints a **`projectId`** (public — give it to your client SDK along with this server's URL)
28
- and a one-time **API key** (secret — `x-api-key` for the query routes).
28
+ and a one-time **API key** (secret — `x-api-key` for the query routes). That key is the operator's
29
+ **owner** key: `query`, `query:raw` and `annotate`, labelled `owner`. Do not hand it to an agent —
30
+ mint one with `uptimizr new-key <projectId> --capabilities query` instead.
29
31
 
30
32
  ### CLI (ADR 0029)
31
33
 
32
34
  | Command | What it does |
33
35
  | ------------------------------ | -------------------------------------------------------------------- |
34
- | `uptimizr init [name]` | Secret + store + migrations + first project/key + `.env`. |
36
+ | `uptimizr init [name]` | Secret + store + migrations + first project/owner key + `.env`. |
35
37
  | `uptimizr serve` | Run the ingestion + query API. The default when no command is given. |
36
- | `uptimizr new-project <name>` | Mint an additional project + API key. |
38
+ | `uptimizr new-project <name>` | Mint an additional project + owner API key. |
37
39
  | `uptimizr new-key <projectId>` | Mint an additional key on an existing project (see the flags below). |
38
40
  | `uptimizr migrate` | Apply store migrations. |
39
41
  | `uptimizr regions set <scene>` | Replace a scene's named regions from `--file <regions.json>`. |
40
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). |
41
44
  | `uptimizr help` | Usage. |
42
45
 
43
46
  `new-key` flags: `--capabilities <list>` (comma-separated; default `query`), `--label <name>`,
@@ -58,16 +61,54 @@ project you mint is the one the running collector resolves.
58
61
  Installed as a dependency, the package exposes the `uptimizr` CLI plus the legacy
59
62
  `uptimizr-collector` bin (equivalent to `uptimizr serve`).
60
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
+
61
101
  ## API keys and capabilities (ADR 0051 §7)
62
102
 
63
- A key carries a **set of capabilities**, not a single role. Keys default to `query`.
103
+ A key carries a **set of capabilities**, not a single role. `new-key` defaults to `query`;
104
+ `init` / `new-project` mint their single key with `query`, `query:raw` and `annotate`.
64
105
 
65
- | Capability | Unlocks |
66
- | ----------- | --------------------------------------------------------------------------------------------------------- |
67
- | `query` | The aggregate analytics API, the scene registry, the live token exchange, and `GET /api/v1/audit`. |
68
- | `query:raw` | Raw per-session streams: `GET /api/v1/sessions/:id/events` and `GET /api/v1/live/sessions/:id`. |
69
- | `annotate` | The project **metadata** write path (annotations, glossary, saved analyses, panel specs). Never events. |
70
- | `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. |
71
112
 
72
113
  The raw endpoints are gated **twice**: the collector must run with `ENABLE_RAW_SESSION_RETENTION`
73
114
  **and** the key must hold `query:raw` — otherwise `403`. Retention alone is not enough.
@@ -97,8 +138,49 @@ Ingestion keeps its separate `COLLECTOR_INGEST_RATE_LIMIT_*` budget.
97
138
  - The read API: sessions, heatmaps (`pointer`, `camera`, `position`, `world`, `gaze`, `mesh-uv`,
98
139
  `click-rays`, `flow`, `perf`, `errors`), mesh/interaction insights, performance and diagnostics,
99
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.
100
143
  - `GET /health` — liveness probe, unauthenticated.
101
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
+
102
184
  ### Result envelopes: `format=full | table | summary` (ADR 0051 §2)
103
185
 
104
186
  Every aggregate endpoint accepts `format`. It **filters nothing** — it picks the result envelope:
@@ -111,6 +193,23 @@ Every aggregate endpoint accepts `format`. It **filters nothing** — it picks t
111
193
  the registry's `maxSummaryRows`. **This is what makes a 500-bin heatmap affordable for an LLM** —
112
194
  prefer it over `full` when feeding a model.
113
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
+
114
213
  ### Filters
115
214
 
116
215
  Common query params: `since`, `until` (epoch ms), `bins`, `limit`, `scene`, `session`,
@@ -126,6 +225,25 @@ set is carried **inside the signed token**, so `GET /api/v1/live/sessions/:id` c
126
225
  `query:raw` even though `EventSource` cannot send headers. `GET /api/v1/live/presence` and
127
226
  `/live/stream` use the same `?token=...`.
128
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
+
129
247
  ## Storage (`COLLECTOR_STORE`)
130
248
 
131
249
  | Value | Store |
@@ -139,6 +257,59 @@ set is carried **inside the signed token**, so `GET /api/v1/live/sessions/:id` c
139
257
  All four return **identical analytics** (the cross-engine parity suite). Aggregations are computed
140
258
  at **query time** in v1 — no materialized views.
141
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
+
142
313
  ## Other configuration
143
314
 
144
315
  - Server / browser access: `COLLECTOR_HOST` (`0.0.0.0`), `COLLECTOR_PORT` (`4318`),
@@ -148,9 +319,14 @@ at **query time** in v1 — no materialized views.
148
319
  `LIVE_MAX_CONNECTIONS`, `LIVE_PRESENCE_INTERVAL_MS`.
149
320
  - Rate limits: `COLLECTOR_RATE_LIMIT_MAX`, `COLLECTOR_RATE_LIMIT_WINDOW_MS`,
150
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).
151
325
  - Agent audit: **`AUDIT_RETENTION_DAYS`** (default `30`; `0` = keep forever),
152
326
  `AUDIT_DASHBOARD_REQUESTS` (default off — requests carrying `x-uptimizr-client: dashboard` are
153
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`).
154
330
  - All-in-one dashboard: `COLLECTOR_DASHBOARD_DIR` (point it at a static dashboard export and one
155
331
  process serves ingestion, queries and the UI), `COLLECTOR_CSP` (`strict` or `off`).
156
332
 
package/README.md CHANGED
@@ -13,8 +13,8 @@ created and migrated automatically.
13
13
 
14
14
  ```bash
15
15
  # 1. One-time setup: generates a visitor-hash secret, creates + migrates the
16
- # store (DuckDB by default), mints a first project + API key, and writes a
17
- # local .env.
16
+ # store (DuckDB by default), mints a first project + owner API key, and
17
+ # writes a local .env.
18
18
  npx -p @uptimizr/collector-server uptimizr init "My Project"
19
19
 
20
20
  # 2. Start the ingestion + query API (reads the generated .env; 0.0.0.0:4318).
@@ -24,7 +24,10 @@ npx -p @uptimizr/collector-server uptimizr serve
24
24
  `init` prints a **`projectId`** and a one-time **API key**. Give the `projectId`
25
25
  and this server's URL (the **`endpoint`**) to your client SDK (e.g.
26
26
  `@uptimizr/babylon`); use the **API key** (`x-api-key`) for the query routes /
27
- dashboard. Mint more projects later with
27
+ dashboard. That first key is the operator's **owner** key — `query`, `query:raw`
28
+ and `annotate` — so the dashboard, session replay, the live per-session follow
29
+ and scene regions all work off it; hand agents and MCP clients a narrower key of
30
+ their own with `uptimizr new-key`. Mint more projects later with
28
31
  `npx -p @uptimizr/collector-server uptimizr new-project "<name>"`, or add a key
29
32
  to an existing project with
30
33
  `npx -p @uptimizr/collector-server uptimizr new-key <projectId> [--capabilities …] [--label …]`
@@ -72,6 +75,52 @@ so it needs no API key. Over HTTP the same thing is
72
75
  an `annotate`-capable key, and from a client build `registerRegions` in
73
76
  `@uptimizr/sdk-core`.
74
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
+
75
124
  ### All-in-one: serve the dashboard too
76
125
 
77
126
  The collector can also serve a pre-built static dashboard from its own origin, so
@@ -204,6 +253,26 @@ what makes a 500-bin heatmap affordable for an LLM. See
204
253
 
205
254
  - `GET /health` — liveness probe.
206
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
+
207
276
  ## Security
208
277
 
209
278
  `@fastify/helmet`, `@fastify/cors` (restricted to `COLLECTOR_CORS_ORIGINS`), and
@@ -220,6 +289,7 @@ if `VISITOR_HASH_SECRET` is missing.
220
289
  | Live SSE routes (`/api/v1/live/*` `GET`s) | `?token=...` | Browser `EventSource` cannot attach custom headers, so live streams use short-lived bearer tokens. |
221
290
  | `GET /health` | None | Liveness probe. |
222
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. |
223
293
 
224
294
  ### API keys and capabilities
225
295
 
@@ -232,17 +302,29 @@ A key carries a **set of capabilities** (ADR 0051 §7), not a single role:
232
302
  | `annotate` | The project **metadata** write path (annotations, glossary, saved analyses, panel specs). Never events. |
233
303
  | `ingest` | Reserved for server-side write paths. Public ingestion is keyless, so issued keys are normally read keys. |
234
304
 
235
- Keys default to `query`, including those from `uptimizr init` / `uptimizr new-project`:
305
+ `uptimizr init` / `uptimizr new-project` mint the operator's **owner** key —
306
+ `query`, `query:raw` and `annotate`, labelled `owner` — because that key drives the dashboard,
307
+ session replay, the live follow and scene regions. `query:raw` is inert on its own: the raw routes
308
+ also need `ENABLE_RAW_SESSION_RETENTION`, so granting it up front turns nothing on and only saves
309
+ re-minting the key when retention is switched on later.
310
+
311
+ `uptimizr new-key` is how every **other** key is issued, and it still defaults to `query` alone —
312
+ what an agent or MCP client should hold:
236
313
 
237
314
  ```bash
238
- uptimizr new-key <projectId> --capabilities query,annotate \
239
- --label "weekly-report-agent" --rate-limit-max 120 --rate-limit-window-ms 60000
315
+ # An agent's own key: read-only aggregates (the default), labelled and budgeted
316
+ uptimizr new-key <projectId> --capabilities query --label "mcp-agent" \
317
+ --rate-limit-max 120 --rate-limit-window-ms 60000
318
+
319
+ # An agent that may also write metadata
320
+ uptimizr new-key <projectId> --capabilities query,annotate --label "weekly-report-agent"
240
321
  ```
241
322
 
242
323
  > **Breaking change.** `query:raw` is new, and the raw per-session endpoints now require **both**
243
324
  > `ENABLE_RAW_SESSION_RETENTION` **and** `query:raw` — previously retention alone was enough for
244
325
  > any `query` key. Existing keys keep working for every aggregate endpoint; a key that drives
245
- > session replay or live-follow must be re-minted with `--capabilities query,query:raw`.
326
+ > session replay or live-follow must be re-minted with `--capabilities query,query:raw`. Keys minted
327
+ > by `init` / `new-project` already carry it.
246
328
 
247
329
  `--rate-limit-max` / `--rate-limit-window-ms` give a key its own request budget, bucketed on the
248
330
  key id rather than the client IP; keys without one fall back to `COLLECTOR_RATE_LIMIT_*`.
@@ -291,6 +373,8 @@ Environment-driven (see [`.env.example`](../../../.env.example)):
291
373
  - Rate limits: `COLLECTOR_RATE_LIMIT_MAX`, `COLLECTOR_RATE_LIMIT_WINDOW_MS`,
292
374
  `COLLECTOR_INGEST_RATE_LIMIT_MAX`, `COLLECTOR_INGEST_RATE_LIMIT_WINDOW_MS`
293
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`).
294
378
  - Agent audit: `AUDIT_RETENTION_DAYS` (default `30`, `0` = keep forever),
295
379
  `AUDIT_DASHBOARD_REQUESTS` (default off).
296
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 and `steps` for the funnel.
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,eAqBzB,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;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAK3E,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"}
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 and `steps` for the funnel.
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":"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"}
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"}