Commit Graph

4498 Commits

Author SHA1 Message Date
Katia Bulatova 37c56aaa15 test(webapp): pin the settlement failure window and the open panel
Covers the two halves the earlier pass left open. Against a real database, a
stale card that cannot be rendered now proves the sweep rolls the settle back
with it: the row stays `in_progress` at revision 0, the chat stays empty, and
the row is still in the next run's selection.

The panel half is covered over `liveProgress`, the code that decides whether
"Working…" is shown: a mounted panel holding the unconcluded card re-reads the
stored transcript, merges by stable id, and the progress line goes away without
a reload. Re-reading repeatedly cannot add a second copy of the card.
2026-08-06 12:42:27 +00:00
Katia Bulatova 5bf3612ecf fix(webapp): settle an investigation and its closing card in one write
The row settle, the transcript and the session state were three separate
operations. Once the row was terminal, a failed transcript write left a card
that read `in_progress` forever: the stale sweep only selects `in_progress`
rows, so nothing was left to repair it.

Both lanes now commit the pair atomically. The live turn hands its pending
settlements to `persistTurn`, which upserts the revisions, appends their cards
and writes the session in one transaction; the process-local entry survives
until that commits, so a retried `onTurnComplete` still settles. The sweep goes
through `settleInvestigationAndCloseCard`, whose rollback restores the
`in_progress` row the sweep already selects.

That also removes the last reader of the per-run `chatOwners` map, which had no
`delete` and grew for the life of the worker: the failure record now travels in
the transcript write, which needs no userId.

Separately, the consented watch investigation gets the rolling step cache. Its
ten-step `streamText` re-sent every accumulated tool output uncached; the
breakpoint helper and the per-step cache telemetry now live in `step-cache.ts`
and both lanes use them, wrapping any `prepareStep` the resolved options carry.
2026-08-06 12:42:27 +00:00
Katia Bulatova 29de34f912 style(webapp): drop the imports the merge left unused 2026-08-06 12:19:30 +00:00
Katia Bulatova 657f11daf0 docs(webapp): say which UAT flow the project-wide answer is preserved for 2026-08-06 12:18:36 +00:00
Katia Bulatova 1759618b7f fix(webapp): keep the project-wide answer for an environment-agnostic user-actor token
Binding a delegated token to its environment claim on the project-wide
routes also refused any token that carries no claim at all, which the
public PAT exchange used by MCP and the CLI is allowed to issue. Line the
project-wide helper up with its neighbours: a claimless dashboard-agent
token is still refused, everything else stays project-wide.
2026-08-06 12:18:35 +00:00
Katia Bulatova e62d8c66f3 style(webapp): format the route image CSP audit 2026-08-06 12:18:15 +00:00
Katia Bulatova 60a958414b test(webapp): fail if a route sets an over-broad image CSP 2026-08-06 12:18:14 +00:00
Katia Bulatova 26f93da1a9 fix(webapp): have the stale-investigation sweep close the card it settles
The sweep settled the row and appended nothing, so it visibly fixed nothing: the
chat kept rendering the last card it had, which was still "Working…". The settle
now returns the state and revision it wrote, and the sweep appends that as the
closing card revision on the chat — id-deduped on
`investigation-settlement:{id}:{revision}`, so a retried run can neither stack a
second card nor open a second investigation.

The append is scoped by chat id: a sweep runs off any session and has no user in
context, unlike the turn lane.
2026-08-06 11:34:59 +00:00
Katia Bulatova 49e8fb578c fix(webapp): tell a watch's creator about their own email alerts, not a colleague's
The create-watch response matched any watch-alert channel in the project, so a
second member was told they were subscribed while the mail went to the first.
The channel's deduplication key is the only record of whose it is, so state
resolution, subscribe and unsubscribe now share one owner lookup.
2026-08-06 11:27:43 +00:00
Katia Bulatova c4d3f585e3 fix(webapp): replay a watch submission's recorded email outcome instead of re-deciding it
replay() re-ran subscribe() for an already-recorded `created` submission. A retry that
succeeded could flip the ledger to `enabled`, but the confirmation in the transcript is
append-once, so the user kept being told email was unavailable while the system believed
it was on. The replay now reads recordedExternalNotification() and takes no external
decision. An attempt that dies before subscribing or before its outcome is recorded
leaves the row `pending`, and the normal creation path subscribes on the retry.
2026-08-06 11:27:42 +00:00
Katia Bulatova 79482dea37 chore(webapp): drop the comments the tests already say 2026-08-06 10:49:05 +00:00
Katia Bulatova 7a645e5149 chore(webapp): drop the comments the code already says 2026-08-06 10:49:04 +00:00
Katia Bulatova 3336a47d1e test(webapp): give the container-backed user-actor scope tests their own timeout 2026-08-06 10:32:58 +00:00
Katia Bulatova f700174b44 fix(webapp): ceiling an exchanged environment JWT by what the delegated token can do
A user-actor token declaring no scope cap could mint an environment JWT with any
scopes it asked for. The exchange now clamps the minted scopes to the actor's own
ability, so a capless token is read-only, and only mints for its claimed environment.
2026-08-06 10:32:56 +00:00
Katia Bulatova a7fc406781 fix(webapp): keep a delegated token's claims on the authenticated identity
The direct PAT authentication path returned identity only, so a user-actor token
reaching it (admin routes and other direct callers) lost its environment scope. The
claims now ride on the authentication result itself.
2026-08-06 10:32:55 +00:00
Katia Bulatova d00b1266e4 fix(webapp): bind a delegated token's environment claim on the project-wide routes
The environments and runs listings are project-wide, so an environment-scoped
user-actor token could read every environment its user can reach. Both routes now
resolve the claim into a mandatory filter, and a conflicting request filter is
refused rather than overridden.
2026-08-06 10:32:54 +00:00
Katia Bulatova 4e00b7952f style(webapp): format the sweep's authorization helper 2026-08-06 10:29:14 +00:00
Katia Bulatova 210863ea76 fix(webapp): separate a watch's last look from its last check in the batch order
A tick that could read nothing now moves the group's fairness key only, so a watch
with a permanently broken reader stops crowding out the rest of an over-cap group.
Dueness and the streak facts still follow the last real check.
2026-08-06 10:28:05 +00:00
Katia Bulatova 22bf6d919a fix(webapp): cancel the watch a losing submit's winner does not name
A refusal that won the race kept the reserved watch id, so the user was told
nothing was created while that watch stayed active.
2026-08-06 10:28:04 +00:00
Katia Bulatova 09d1b55e22 fix(webapp): require the watch card's request id instead of falling back to its condition
A per-condition fallback identified the condition rather than the submit, so a
re-watch could replay a stale terminal outcome from the retention window.
2026-08-06 10:28:03 +00:00
Katia Bulatova cd112ba2a3 test(webapp): assert the document img-src has no wildcard host 2026-08-06 10:27:59 +00:00
Katia Bulatova 48d0d4a707 fix(webapp): drop the img-src wildcard host for a validated origin allowlist
Replaces https://*.googleusercontent.com (a host with public write access)
with CSP_IMG_SRC_ALLOWLIST: exact origins only, https outside development,
deduplicated, bad entries warned about instead of failing the boot.
2026-08-06 10:27:58 +00:00
Katia Bulatova 05b3472801 Merge branch 'main' into feat/dashboard-agent-flows
Route conflicts were the tab-title work meeting the agent page-context handle: both
sides kept, duplicate meta exports resolved to pageMeta, duplicate imports merged
with unused bindings dropped. Lockfile regenerated from the merged manifests.
2026-08-06 10:27:43 +00:00
Katia Bulatova 933aeb9ea4 test(webapp): stub the agent proxy's environment lookup, not a Prisma row
The hand-built row stopped satisfying every column the authenticated-environment
mapper reads, so both proxy cases failed. Stubbing the lookup keeps the fixture
independent of the row's columns.
2026-08-06 09:44:50 +00:00
Katia Bulatova 82f4a73b9d style(webapp): drop a needless escape in the image regex 2026-08-06 09:42:47 +00:00
Katia Bulatova 32553b903d style(webapp): format the agent markdown test 2026-08-06 09:42:46 +00:00
Katia Bulatova 94ec67fd53 perf(webapp): keep the agent transcript's investigation winners stable
The winners map was rebuilt on every streamed token and handed to the memoized turns as a fresh reference, re-rendering the transcript. The reference is now reused while the winners hold.
2026-08-06 09:42:44 +00:00
Katia Bulatova dd90b6e968 fix(webapp): never render an image the agent's model authored
A remote image in model output fetches on render, so its URL is an exfiltration channel. No image is rendered at all, and the document CSP now names the hosts images may come from.
2026-08-06 09:42:43 +00:00
Katia Bulatova a0640ba248 style(webapp): format the watch tenancy changes 2026-08-06 09:40:30 +00:00
Katia Bulatova a63708c6e6 test(webapp): assert the derived chat id before the submit outcome 2026-08-06 09:40:29 +00:00
Katia Bulatova 5176e3e8c4 test(webapp): pin the watch ledger's tenancy, the once-only alert and the owner-scoped unsubscribe 2026-08-06 09:40:29 +00:00
Katia Bulatova 5f2f5fe241 fix(webapp): scope the watch submission ledger and its alerts to one tenant
The chat id a fresh submit derives, the transcript appends, the create's chat read
and the ledger replay are all owner-scoped now, the fire callback alerts once per
terminal outcome, and a failed email subscription is stated instead of dropped.
2026-08-06 09:40:28 +00:00
Katia Bulatova 012ce0de53 test(webapp): drop an unsafe optional chain from the sweep boundary test 2026-08-06 09:39:17 +00:00
Katia Bulatova bd2559a7a8 fix(webapp): record only a real evaluation from a batch tick
An unreadable check is not an observation, so writing it moved the watch down the
rotation for a whole cadence and overwrote the facts a streak lives in.
2026-08-06 09:39:16 +00:00
Katia Bulatova e4852090aa perf(webapp): resolve the sweep's authorization and readers once per group
An incident expires a whole group at once, which is the worst time to repeat the
same authorization read per row. Bounded concurrency caps one tenant's share.
2026-08-06 09:39:15 +00:00
Katia Bulatova d2511bd264 fix(webapp): hand the sweep's final check the previous facts
A stateful condition is a transition across checks, so a boundary evaluation
without the last observation can never see a stall and records a reset.
2026-08-06 09:39:15 +00:00
Katia Bulatova d12e6a85ca style(webapp): merge duplicate imports on the user-actor auth path 2026-08-06 09:24:09 +00:00
Katia Bulatova f58a9dcc89 fix(webapp): only let the browser set the agent's page context
The chat turn's metadata is now a whitelist: the page the user is on is all the browser can set, and every identity, tenancy and token field is filled in by the server.
2026-08-06 09:24:09 +00:00
Katia Bulatova d51cb9a736 fix(webapp): stop the OSS fallback handing a delegated token blanket access
Without an RBAC plugin, a user-actor token got the same permissive ability a personal access token gets. It now gets only what its own scope cap allows, and reads only when it declares no cap. Personal access tokens are unchanged.
2026-08-06 09:24:08 +00:00
Katia Bulatova 9d45284112 fix(webapp): keep the agent token's environment claim on the actor through every route builder
The claim rode beside the authentication result, so a route builder that only forwarded the user id lost it. It now travels on the authenticated actor itself, and a route's resolved org/project/environment is checked against it — a mismatch fails closed.
2026-08-06 09:24:07 +00:00
Katia Bulatova f5a146b30c fix(webapp): cap the agent's body on any method and on a mixed-case path 2026-08-06 08:50:11 +00:00
Katia Bulatova 337dda1e97 feat(webapp): name of the page in tab titles (#4517)
Adds a shared `pageMeta()` helper and 74 route declarations, so a title
reads `run_abc | Runs | Trigger.dev` — the specific thing first, then
the page. Org pages also carry the organization: `Team | Acme |
Trigger.dev`. Inside a project no scope is added, because the dashboard
switches projects in every tab at once.

Page names are unchanged; what's new is that a page says which one it is
at all. Three wording changes on purpose: the queue page now names the
queue, the model page names the model, and entity pages carry their
section.
2026-08-06 10:34:30 +02:00
Katia Bulatova 85b98ab8dc fix(webapp): mint the agent's token for the caller's own environment, or not at all 2026-08-06 08:20:52 +00:00
Katia Bulatova 62b598da27 docs(webapp): say which layer refuses an oversized agent body
The route-level check only sees a declared content-length; a body without one is
already ended mid-stream by the ingress cap.
2026-08-06 07:14:41 +00:00
Katia Bulatova 1cdf1f087d refactor(webapp): split the dashboard agent's maintenance cron along the watch seam
dashboardAgent.sweepWatches ran the watch sweep, the investigation sweep, the
batch re-arm and eval retention together. Watch is about to ship as its own
stacked PR, so the job is split where the cut will be: dashboardAgent.maintenance
keeps the investigation sweep and eval retention, dashboardAgent.watchMaintenance
takes watch expiry, delivery and the batch re-arm. Same cadence, and each backstop
keeps its own try/catch.
2026-08-06 07:14:41 +00:00
Katia Bulatova 50fd10bf1b fix(webapp): replay a watch submission's recorded outcome instead of re-running it
A retried card submit was only repairable while the first attempt's watch was
still active. Once it had fired, expired, or answered in one shot, the retry
re-evaluated the condition and created a second operation.

A watch_submissions ledger, keyed (chat_id, client_request_id), is now written
before the condition is read and carries the outcome once there is one. A retry
looks it up first: a recorded outcome is replayed, a different draft under the
same id conflicts, and only a pending row proceeds - converging on the watch id
reserved up front rather than creating another.
2026-08-06 07:14:40 +00:00
Katia Bulatova 4ff8859e1d fix(webapp): keep the agent's alert delete to channels the watch type is on 2026-08-06 01:54:46 +00:00
Katia Bulatova cbc2edc879 test(webapp): pin the watch submit's ordering, its retry repair and its refusal record 2026-08-06 01:54:45 +00:00
Katia Bulatova 49a953871a fix(webapp): record a watch request before the watch starts, and repair a retried submit 2026-08-06 01:54:44 +00:00
Katia Bulatova 28b1f3896c fix(webapp): cap the agent's request body while it streams, not after
The message-size checks ran after the body had been read, so a request without a
content-length was buffered and parsed in full before being refused. An ingress
cap on the agent's paths now counts the bytes as they arrive, and the chat proxy
reads its body with a ceiling instead of reading it whole first.
2026-08-06 01:53:17 +00:00