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.
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.
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.
A settled investigation now lands as one more revision of the same card, so the
in-progress card and its working line end. An empty narration writes nothing and
says so, and only an acknowledged append marks a wake delivered.
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.
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.
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.
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.
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.
The agent's run_query path compiles TRQL, which has no write statements, so a
mutation cannot parse. Cover the deny-list evasions a keyword filter would miss
(casing, comments, a statement smuggled after a semicolon) and keep positive
controls so the negatives can't pass by rejecting everything.
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.
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.
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.
The page load reported unread wakes only, so a fresh browser whose watch was
created elsewhere and hasn't fired yet never started polling: the wake landed
without a toast or a dot until a reload. The loader now returns the active-watch
presence too, in one read per page load.
The batch took the 500 soonest-expiring watches of a group every tick, so a group
larger than the cap could leave the rest unchecked until the first 500 expired.
The group is now ordered least-recently-checked first, with a generated
cadence_minutes column and an index so the due predicate no longer re-parses the
spec JSON per tick.
The chip factory and slot vocabulary, the evergreen docs chips, the per-page slot
table and the signal chips each get their own module; registry.ts is now the
public face that re-exports them. A pure move.
dashboardAgentWatchChecks.ts keeps the dispatcher, the failure envelope and the
previous-facts reader, and re-exports the run, queue, error and health families
plus the shared reader contract. A pure move; every import path is unchanged.
watch-tick.ts keeps the two task definitions and the per-watch check; the wake
delivery, the condition lifecycle, the group tick and the db/session/callback
adapters each move to their own module. A pure move: watch-tick.ts re-exports
everything it exported before.
tools.ts now only assembles ready adapters, in the same frozen key order: the
HTTP/JWT client, result curation, the docs client, the source-read ledger,
evidence canonicalisation, investigation persistence, and the api/navigation/
watch/alert tool groups each own their own module. A pure move.
The prompt-prefix fingerprints are unchanged, which is what keeps the head-start
and agent prefixes byte-identical.
The docs claimed every turn is judged; the sampled rate, the redaction rule, the code-mode
skip, the opt-out and the retention period are now written down in one place.
Payloads, outputs, query rows and file contents are replaced by their shape before a turn
reaches the judge, and the row now keeps the derived verdict only.