Commit Graph

391 Commits

Author SHA1 Message Date
Katia Bulatova 2aeb32fa18 refactor(webapp): move dashboard-agent render UI to the UI PR 2026-08-11 14:11:52 +00:00
Katia Bulatova 32ea4b00cc test(webapp): add the recheck export to stale user-actor auth mocks 2026-08-11 13:27:33 +00:00
Katia Bulatova f7dfc905e3 fix(webapp): stop the project-wide user-actor route 500-ing for claimless/env-scoped tokens 2026-08-11 13:15:08 +00:00
Katia Bulatova bd5f8fd67f fix(dashboard-agent-db): scope softDeleteChat by organizationId 2026-08-10 18:54:21 +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 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 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 aef86b34e7 fix(webapp): reword the healthy-execution report line to not imply failure 2026-08-10 14:16:56 +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
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 730ded16fd test(webapp): a collapse to zero renders no arrow 2026-08-10 06:30:18 +00:00
Katia Bulatova 8f017ecd01 test(webapp): pin that the disabled org route never consults the token capability 2026-08-09 21:04:17 +00:00
Katia Bulatova d304c1a0c3 test(webapp): assert the org route never consults a plain PAT ability 2026-08-09 21:01:24 +00:00
Katia Bulatova 637a2bb02f test(webapp): pin that an ordinary PAT still passes the PAT-only helper 2026-08-09 20:46:19 +00:00
Katia Bulatova 8fbbcf06b4 test(webapp): pin 404-before-403 ordering for capped user-actor tokens 2026-08-09 18:24:58 +00:00
Katia Bulatova 61a32883c3 fix(webapp): scope the org-creation RBAC check to user-actor tokens 2026-08-09 18:19:34 +00:00
Katia Bulatova 20c8f9eb3c fix(webapp): stop an unreadable eval override reading as consent to judge
flag() ignores a per-org override that fails the schema and falls through to the global
default, which is on. For an entitlement that is right; for this flag it means an org that
tried to turn judging off with a stringified boolean kept being judged. The eval policy now
refuses on an override it cannot parse, matching what the flag's own comment already claims.
2026-08-09 14:44:59 +00:00
Katia Bulatova 9805c0adf5 fix(webapp): deny an unknown report key instead of authorizing it against every table
reportQueryTables fell back to the union of every report's tables, so a key naming no
report was checked against all of them. everyResource is a conjunction and checkAuth
denies an empty one, so that happened to be strict rather than permissive - but only by
accident, and the docstring justified it with a claim about empty lists that checkAuth
contradicts. An unknown key now declares no tables and is refused outright.
2026-08-09 14:44:56 +00:00
Katia Bulatova 8fd53691f3 revert(webapp): move the queue JWT read to the PR that uses it 2026-08-09 11:45:51 +00:00
Katia Bulatova 2aacd26c84 fix(webapp,dashboard-agent): keep investigation cards, and stop a broken request reading as an answer
An investigation card could vanish from a reply that contained step
separators: the winning revision was keyed by its position in the
untouched reply, while rendering walked the stripped parts. Both now
index the same stripped list.

A host-written `data-view` card counted towards winning a revision but
was never drawn as a card, so it could suppress the tool-rendered card
it competed with into nothing. Both carriers now render.

`apiGet` left `fetch` and `res.json()` unguarded, so a connection reset
threw out of the tool, and a transport failure that did land could read
as a definite 404. It now returns a transport failure the way the
sibling paths do.

Also: guard the card's Hypotheses section on length, as Evidence
already is, and move the reports auth resource out of the serializer so
rendering a report doesn't pull the route builder in.
2026-08-09 08:50:35 +00:00
Katia Bulatova 7e272cc973 chore(webapp): drop the unused waiting-run diagnosis 2026-08-08 23:28:43 +00:00
Katia Bulatova c3b7c10b4c refactor(dashboard-agent): move the watch data layer into the Watch PR
The watch tables and the 0002 migration stay here, so the committed drizzle
snapshot keeps describing them. The four contract types the schema annotates
are widened here and re-narrowed where the watch code lands.
2026-08-08 23:28:37 +00:00
Katia Bulatova 0cae30190a fix(dashboard-agent): pin the investigation cards the transcript actually holds
`collectDurableState` read `data-view` parts only, but `render_view` writes
every investigation card as a `tool-render_view` part, so compaction pinned
nothing and an open card could be summarised away.

It now resolves cards through `latestCards`, the resolver the panel and the
watch actions already use: highest revision per id wins whatever order the
renders arrive in, so a stale `in_progress` render landing after the settling
one no longer reopens a closed card. `latestCards` reads host-written
`data-view` blocks too, matching the panel.
2026-08-08 17:03:54 +00:00
Katia Bulatova 643ea3907f fix(dashboard-agent): read the report's untrustworthy reason under its current name
`curateReport` still read `facts.staleReason`, renamed to `untrustworthyReason`
in dc3b50260 and split into telemetry_stale / telemetry_absent / flow_unmeasured.
The read had been undefined since, so the agent got "untrustworthy" with no why,
and the prompt still told it every such case was stale telemetry.

`facts` is `z.record(z.unknown())`, so nothing typechecked the key. The new test
goes presenter -> reports route JSON -> curateReport without naming a facts key
on the way in, and asserts curation carries every key the presenter emits.
2026-08-08 17:03:53 +00:00
Katia Bulatova 3355b6813e fix(webapp): stop an unmeasured queue depth from silencing a measured flow finding
An unmeasurable depth made the whole flow finding unassessable, so a
critical start latency measured off runs showed as crit in format=json
while the rendered report said 'Flow unknown ... nothing to do' and
dropped the row that earned it. The depth still buys no cause, no
attribution and no drain ETA, but the measured symptom is now reported.
2026-08-08 16:23:04 +00:00
Katia Bulatova a66034930c fix(webapp): make each report caveat discount only the input it names
A missing telemetry feed said the run aggregates were a point-in-time
snapshot; they are measured over the window either way, and what is
actually unknown is how current they are. An unmeasured queue depth
blamed throughput, which the report does measure, and contradicted its
own headline. Tests pin the claim rather than the wording.
2026-08-08 16:23:03 +00:00
Katia Bulatova d0f06d5c5e fix(webapp,dashboard-agent): address a branch environment by name and branch
The agent could not read anything on a preview or dev branch. Its environment name
is derived from the environment's type, and every branch shares its parent's type —
so the name identifies a family, not a row. The API's env routes are name-addressed,
so a bare "preview" or "dev" resolved to the parent, and the delegated token's
`environmentId` claim then correctly refused it. The guard was the detector, not the
defect: the exchange was never given enough identity to resolve the environment the
dashboard had selected.

Name and branch are now resolved together into one address, so no caller can take
the name without the branch, and both mint sites share the one type map instead of
keeping a copy each. The address travels to the JWT exchange and to the three
delegated-token reads that resolve by name (list_tasks, correlate_version, the repo
snapshot). The env-JWT reads address the environment by id and are untouched.

Second, and why nobody saw the first: the exchange reported the same "no
environment" for a genuine absence and for any failure, and cached the failure for
the whole turn. Following the queue live-read precedent, only a missing environment
is stated as one; anything else says the read didn't land and carries its status.
2026-08-08 16:21:48 +00:00
Katia Bulatova a4ba0271b0 fix(webapp): refuse a delegated token at the entrance of the PAT-only auth helper
The helper checks no scopes and no capability context, so a read-only user-actor token
reached an alert-channel write and every admin route behind requireAdminApiRequest.
Actor-aware routes are unaffected: they authenticate the token through the route
builders, which enforce its claims.
2026-08-08 16:19:12 +00:00
Katia Bulatova 314a1d795d perf(webapp): skip the global feature-flag query when a per-org override resolves
flag() queried the FeatureFlag row before looking at the caller-supplied overrides, so a
per-org hit still paid a round-trip. Check the override first and only fall through to the
query when it fails the schema, which keeps today's resolution order intact.
2026-08-08 13:59:06 +00:00
Katia Bulatova c3f0d62d62 fix(webapp): undo a new agent chat only when its start never got anywhere
A start that rejects dispatched no handover and sent no message, so the chat
row is taken back. A failed access-token mint is left alone: the session is
live by then and removing the chat would hide a running agent.
2026-08-08 13:46:01 +00:00
Katia Bulatova e2704eaa84 fix(webapp): keep identityOnly off action routes in the type
identityOnly waives the contextless refusal, which is only sound for reads.
The action options intersected the loader options, so the type allowed it.
2026-08-08 11:52:37 +00:00
Katia Bulatova de4cdad61b fix(webapp): list the preview branch an agent token is scoped to
A token signed for a branch child listed nothing: the scope filter and the
base-environments-only filter could never hold together. Unscoped callers
still see base environments only.
2026-08-08 11:52:36 +00:00
Katia Bulatova b38c5186ac fix(webapp): stop a failed agent chat start leaving an empty chat behind
The environment lookup, repo lookup and token mint now all run before the chat
row is created, so a 404 or a mint failure can't orphan a chat in the history.
2026-08-08 11:52:35 +00:00
Katia Bulatova f598f96142 fix(webapp): refuse a dashboard agent turn whose token mint failed
The catch around the turn body tolerated a JSON.parse failure, but it also
swallowed a rejected mint and forwarded the turn with no credential.
2026-08-08 11:52:34 +00:00
Katia Bulatova 4c422300c8 fix(webapp): stop treating a ClickHouse unknown identifier as a rollout gap 2026-08-08 09:22:48 +00:00
Katia Bulatova d0be659457 test(webapp): assert the queue depth trend fills its bucket grid 2026-08-08 09:22:47 +00:00
Katia Bulatova 0db1cf0d23 fix(webapp): say why a report's numbers can't be trusted
Absent telemetry and an unmeasured flow were both labelled stale data, so every
snapshot-based report claimed staleness it could not have measured. Choose the
badge and caveat from the reason instead.
2026-08-08 08:29:47 +00:00
Katia Bulatova 1ee6704d53 fix(webapp): describe how far a report metric fell
A fall's multiplier rounds to 0 or 1, so every drop rendered as "flat" — a metric
that collapsed from 100 to 5 read as unchanged. Measure the fall against the
baseline instead, and show a bare arrow when it collapsed to nothing.
2026-08-08 08:29:46 +00:00
Katia Bulatova 05505d5741 fix(webapp): carry a queue's depth forward across empty buckets
The per-queue metrics route mapped ClickHouse rows straight to an array, so a
bucket with no sample shortened the trend and shifted every later point in time.
Fill a fixed-width grid the way the two sibling callers do.
2026-08-08 08:29:46 +00:00
Katia Bulatova 2d23ddec17 fix(webapp): keep the retired chats.messages column, and allow Google SSO avatars
Dropping the column was irreversible and blocked on a production row count; retiring it is not. The avatar host is an exact origin the app already knows, like the GitHub one.
2026-08-08 08:29:43 +00:00
Katia Bulatova 5126bae044 fix(webapp): gate a run's commit metadata on reading deployments
The route served a deployment's git blob — commit message, author, branch,
PR title — with no ability check, while the deployments list serves the same
blob behind read on deployments. Apply that check here too.
2026-08-07 23:50:04 +00:00
Katia Bulatova cb0bcc92b9 fix(webapp): refuse an environment-scoped token on a route that names nothing
assertUserActorScope returned early whenever the passed scope carried no
org, project or environment, and the route builder passes {} for any route
that declares no context — so the guard was a no-op there. api.v1.orgs's
action is such a route and has no authorization block either, letting a
read-only agent token create an organization.

Fail closed instead, with an explicit identityOnly opt-in for the two
contextless loaders that answer with the caller's own identity, and give
org creation the gate its siblings have.
2026-08-07 23:50:02 +00:00