Commit Graph

4659 Commits

Author SHA1 Message Date
Katia Bulatova c0a93b015f merge: propagate review-comment fixes from feat/dashboard-agent-flows
📦 Preview packages (pkg.pr.new) / Build and publish previews (push) Has been cancelled
2026-08-10 19:10:19 +00:00
Katia Bulatova bd5f8fd67f fix(dashboard-agent-db): scope softDeleteChat by organizationId 2026-08-10 18:54:21 +00:00
Katia Bulatova 70c10923c9 fix(webapp): keep the stored chat pointer when opening a chat fails transiently 2026-08-10 18:52:55 +00:00
Katia Bulatova 2bcd7eb16d merge: propagate org-purge best-effort from feat/dashboard-agent-flows 2026-08-10 16:00:43 +00:00
Katia Bulatova 18379a40c9 fix(webapp): don't let a failed agent-purge enqueue break org deletion 2026-08-10 15:59:38 +00:00
Katia Bulatova 8506877708 merge: propagate wave-2 review fixes from feat/dashboard-agent-flows 2026-08-10 15:37:32 +00:00
Katia Bulatova 876ed564da feat(webapp,dashboard-agent-db): retain and purge deleted agent chats
Soft-deleted chats are hard-deleted with their children after a 30d
window by the maintenance sweep, and organization deletion enqueues a
job that soft-deletes the org's chats so the same sweep removes them.
The FK-free cascade lives in a single reusable deleteChatsByIds helper.
2026-08-10 15:36:04 +00:00
Katia Bulatova a97ada590f fix(webapp): recheck the source PAT against the token's own claim, not the plugin's
At the apiBuilder verify site the user-actor claims can come from the RBAC plugin; a plugin image predating the pat claim would deliver pat-less claims and no-op revocation there. Re-verify the bearer locally so the recheck reads the token's authoritative pat. Add recheck tests for the apiBuilder and UAT-preamble sites.
2026-08-10 15:34:03 +00:00
Katia Bulatova 1e97712304 test(webapp): cover the jwt-actor rate-limit branch, not just the helper
Extract the limiter override into an exported function so the actor?.sub decision is testable without redis, and assert both branches (act present -> jwt-actor key; no act -> hashed-token fallback).
2026-08-10 15:33:52 +00:00
Katia Bulatova c18a0737bf fix(webapp): give the agent's env JWT a stable rate-limit key
A delegated env JWT rotates its token value every turn, so keying the limiter on the token handed each turn a fresh bucket. Key on env+acting-user instead, namespaced so it can't collide with private-key buckets.
2026-08-10 15:18:57 +00:00
Katia Bulatova 1acb6b8d20 fix(webapp): bind user-actor tokens to their source PAT
Stamp the source PAT id on a minted user-actor token and recheck it is still live at every verify site, so revoking the PAT invalidates any token created from it. Tokens with no source PAT (e.g. the dashboard agent's) skip the recheck.
2026-08-10 15:18:48 +00:00
Katia Bulatova 2e58b360cb fix(webapp,cli): cap user-actor token lifetime at 7 days
The mint route rejected TTLs only above 365 days; shorten the ceiling to 7 days so a delegated token can't be minted long-lived. Update the CLI --ttl help to match.
2026-08-10 15:18:31 +00:00
Katia Bulatova 99bbc07c88 merge: propagate review fixes from feat/dashboard-agent-flows 2026-08-10 14:34:38 +00:00
Katia Bulatova 94c3a47d02 merge: CSP review fixes (B2 block remote model images, B4 reject CSP delimiters) 2026-08-10 14:32:04 +00:00
Katia Bulatova b962949205 test(webapp): assert StreamdownRenderer drops remote image src end-to-end 2026-08-10 14:31:33 +00:00
Katia Bulatova ca2bacd960 merge: base-misc review fixes (M2, M10a, H8, H13, M10r, ai-help, sweep-cron gate) 2026-08-10 14:29:45 +00:00
Katia Bulatova 237f01a6a5 test(webapp): assert report cache TTL stays within the liveness fresh window 2026-08-10 14:21:19 +00:00
Katia Bulatova 65c60dfbba fix(webapp): reject CSP-delimiter characters in the img-src allowlist
A CSP_IMG_SRC_ALLOWLIST entry containing ';' passed URL parsing and landed verbatim in the space-joined img-src directive, injecting or truncating a directive. Reject entries containing ';' or ',' alongside the existing wildcard and whitespace checks.
2026-08-10 14:21:10 +00:00
Katia Bulatova 502e1bde07 fix(webapp): block remote images in model-authored markdown
Model-authored markdown could emit a remote image whose URL the browser fetches on render — a zero-click data beacon. Constrain the shared markdown renderer with a urlTransform that strips the src of any non-local image and limits links to safe schemes.
2026-08-10 14:20:58 +00:00
Katia Bulatova e367280d9c fix(webapp): gate the agent maintenance cron on the agent being configured 2026-08-10 14:19:18 +00:00
Katia Bulatova 6cb06314f5 fix(webapp): clamp an env-JWT's lifetime to the delegated token's expiry
A user-actor token could be exchanged for an env JWT that outlived it by
requesting a longer expirationTime. Surface the token's exp as expiresAt
and clamp the minted JWT to it. Non-UAT exchanges are unchanged.
2026-08-10 14:19:12 +00:00
Katia Bulatova 35ab0c51fc fix(webapp): ceiling a capless user-actor token to read-only on env-JWT exchange
A delegated user-actor token with no scope cap could exchange for an env
JWT carrying any requested scopes (admin included). Ceiling every UAT
exchange by its cap, or read-only when it has none, projecting requested
scopes through the scope grammar so read:all still admits reads.
2026-08-10 14:18:24 +00:00
Katia Bulatova 64eaf2f4a5 fix(webapp): return 400 for ai-help with a missing query param 2026-08-10 14:16:57 +00:00
Katia Bulatova aef86b34e7 fix(webapp): reword the healthy-execution report line to not imply failure 2026-08-10 14:16:56 +00:00
Katia Bulatova 28c28e6935 fix(webapp): cap report cache TTL to the liveness fresh window 2026-08-10 14:15:44 +00:00
Katia Bulatova fb4f0b3a22 fix(webapp): stop the agent body cap matching a task named dashboard-agent 2026-08-10 14:12:15 +00:00
Katia Bulatova f93f8d6c26 fix(webapp): detect tables inside a JOIN…ON subquery for query-scope auth 2026-08-10 14:11:46 +00:00
Katia Bulatova a6ae4199ae merge: origin/main into dashboard-agent base (resolve report-health)
Resolve conflicts in report-health: keep our richer fallback/trust model
(telemetry_absent trust, snapshot fallback, finished/completed throughput)
and adopt main's start-latency-unknown behavior so an absent measurement
reports as unknown instead of a confident 0ms. renderMarkdown keeps our
layout rewrite; main's compactFact change is covered by report-layout's
metricValue. Adapt main's two render assertions to our current output.
2026-08-10 14:04:30 +00:00
Eric Allam 951d8e8d7b feat(webapp): per-client database pool metrics that survive the driver adapter (#4541)
## What

Follow-up to #4539. The driver-adapter work is inert until a client
flips to the pg driver adapter, but the moment one does, our database
observability degrades: the OTel metrics pipeline reads pool stats from
Prisma's `$metrics`, which is owned by the Rust engine's `quaint` pool.
Under the adapter, `pg.Pool` owns the pool, so those gauges read zero.
The pipeline also only ever scraped a single client (the control-plane
writer singleton).

This PR makes database metrics driver-agnostic and per-client:

- Every configured client registers a metrics source: control-plane
writer/replica, run-ops writer/replica, legacy writer/replica.
Previously only the control-plane writer singleton was scraped.
- Each OTel instrument is observed per client with `db_client` and
`db_driver` (`quaint` | `pg-adapter`) attributes. `db_client` uses our
canonical datasource-role labels (`control-plane-writer`,
`control-plane-replica`, `run-ops-writer`, `run-ops-replica`,
`legacy-run-ops-writer`, `legacy-run-ops-replica`) — the same strings
used for the `db.datasource` span attribute, so a metric and a trace
point at the same pool.
- Pool figures come from the authoritative source per driver:
- **pg-adapter**: `pg.Pool` (`totalCount`/`idleCount`/`waitingCount`,
plus cumulative opened/closed from `connect`/`remove` events).
- **quaint**: the Rust engine's `$metrics` pool gauges/counters, exactly
as before.
- Query counters and duration histograms still come from `$metrics` for
both drivers (the Rust engine executes queries in both cases).
- New `db.pool.connections.waiting` gauge (pg.Pool exposes this; quaint
reports 0).
- Stops exporting Prisma metrics from the Prometheus `/metrics` route.
Pool observability now lives entirely in the OTel pipeline, per driver,
per client.

## Why

So we can flip any client (including the control-plane writer, the
primary desync-fix target) to the driver adapter without losing pool
visibility. Existing dashboards keyed on the same metric names keep
working; they gain a per-client dimension.

## Testing

Unit (`apps/webapp/app/utils/databaseMetrics.server.test.ts`): the pure
normalizer — quaint reads pool from `$metrics`; adapter reads pool from
`pg.Pool` and keeps engine query metrics; `busy` never goes negative;
graceful zeroing when `$metrics` is unavailable (adapter still reports
live pool figures).

Live smoke test against a prod-shaped local stack: three
physically-distinct Postgres DBs (control-plane, run-ops, legacy) behind
dual PgBouncers, split mode on, with a mix of adapter and quaint
clients. Reading the actual emitted OTel metrics, every pool shows up as
its own series:

```
db.pool.connections.total{db_client="control-plane-writer",  db_driver="pg-adapter"} = 1
db.pool.connections.total{db_client="control-plane-replica", db_driver="quaint"}     = 1
db.pool.connections.total{db_client="run-ops-writer",        db_driver="pg-adapter"} = 1
db.pool.connections.total{db_client="run-ops-replica",       db_driver="quaint"}     = 1
db.pool.connections.total{db_client="legacy-run-ops-writer", db_driver="quaint"}     = 1
db.pool.connections.total{db_client="legacy-run-ops-replica",db_driver="quaint"}     = 1
db.client.queries.total{db_client="control-plane-writer",db_driver="pg-adapter"} = incrementing
db.client.queries.duration.count{db_client="control-plane-writer",db_driver="pg-adapter"} = incrementing
```

Confirms: metrics are attributed per pool with the correct driver;
adapter pools' figures come from `pg.Pool`; and query counters/duration
histograms keep incrementing under the pg adapter. Also verified
`/metrics` (Prometheus) now returns zero `prisma_*` series while still
serving the app's own metrics.

`pnpm run typecheck --filter webapp` passes.

## Notes

- `/metrics` (Prometheus) no longer includes `prisma_*` series. Anything
scraping that endpoint for Prisma metrics should read the equivalent
`db.*` metrics from the OTel exporter instead.
- **PgBouncer + `?schema=` gotcha (separate from this PR, worth flagging
for rollout):** since #4539 parses `?schema=` from the DSN and passes `{
schema }` to the adapter, node-postgres sends `search_path` as a startup
parameter. A transaction-mode PgBouncer rejects that with `FATAL:
unsupported startup parameter: search_path`. Our prod control-plane DSNs
use the default `public` schema with no `?schema=` param, so this is
latent, but any client we flip to the adapter must not carry `?schema=`
in its DSN (or the pooler needs `ignore_startup_parameters =
search_path`).

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-10 13:54:06 +01:00
Katia Bulatova c00fb9c36c fix(webapp): report start latency as unknown when there is no data (#4544)
When the health report had no start-latency measurement for the window,
it printed a confident "p95 0ms" and graded it healthy. It now shows
"unknown" for that metric and skips grading it, so an absent measurement
can't read as a green signal.

A genuinely measured 0ms is still shown as 0ms: the loader keeps "no
measurement" distinct from a measured zero instead of coercing both to
0.
2026-08-10 13:53:23 +02:00
Katia Bulatova 69c7b02c2f test(webapp): report parity matches the de-duplicated untrustworthy layout 2026-08-10 10:29:47 +00:00
Katia Bulatova 01ef8fddbf fix(webapp): navigation filters speak the run page's language 2026-08-10 09:21:37 +00:00
Katia Bulatova b73599b2ce fix(webapp): action rows render at the end of the turn 2026-08-10 09:05:27 +00:00
Katia Bulatova 7789d1f6b3 Merge remote-tracking branch 'origin/feat/dashboard-agent-flows' into feat/dashboard-agent-ui 2026-08-10 06:42:56 +00:00
Katia Bulatova d1840a2d06 fix(webapp): revive the code block styling streamdown 2.5 renamed away 2026-08-10 06:41:26 +00:00
Katia Bulatova 94f786cb90 fix(webapp): chat code blocks render at 12px 2026-08-10 06:38:26 +00:00
Katia Bulatova 8e675a1489 Merge remote-tracking branch 'origin/feat/dashboard-agent-flows' into feat/dashboard-agent-ui 2026-08-10 06:34:48 +00:00
Katia Bulatova 730ded16fd test(webapp): a collapse to zero renders no arrow 2026-08-10 06:30:18 +00:00
Katia Bulatova b603cfb206 Merge remote-tracking branch 'origin/feat/dashboard-agent-flows' into feat/dashboard-agent-ui 2026-08-10 00:06:12 +00:00
Katia Bulatova 709065a128 fix(webapp): a delta arrow without a magnitude renders nothing 2026-08-10 00:01:59 +00:00
Katia Bulatova c40b25d479 Merge remote-tracking branch 'origin/feat/dashboard-agent-flows' into feat/dashboard-agent-ui
# Conflicts:
#	apps/webapp/vitest.config.ts
2026-08-09 23:52:36 +00:00
Katia Bulatova 6fcf1cf35d fix(webapp): only the warning headline icon sits lower 2026-08-09 23:51:08 +00:00
Katia Bulatova d6e1b4e62f fix(webapp): a self-evident finding keeps its line and drops the echo row 2026-08-09 23:47:38 +00:00
Katia Bulatova df020b16dc fix(webapp): finding text sits 2px up, not 4 2026-08-09 23:46:58 +00:00
Katia Bulatova cd08d08b33 Merge remote-tracking branch 'origin/feat/dashboard-agent-flows' into feat/dashboard-agent-ui 2026-08-09 23:39:57 +00:00
Katia Bulatova 87f1d119e5 fix(webapp): drop the headline icon 4px 2026-08-09 23:38:54 +00:00
Katia Bulatova be99614f10 fix(webapp): the finding text lifts alone, the label stays with the icon 2026-08-09 23:36:50 +00:00
Katia Bulatova 0d2760edad Merge remote-tracking branch 'origin/feat/dashboard-agent-flows' into feat/dashboard-agent-ui 2026-08-09 23:24:24 +00:00
Katia Bulatova a2642139ea fix(webapp): lift the finding line text 2px 2026-08-09 23:23:13 +00:00
Katia Bulatova 2f3b2ec21e fix(webapp): the last-chat key stays org-true and a foreign chat heals instead of poisoning it 2026-08-09 23:19:52 +00:00