Skip to content

Commit 1e131f2

Browse files
committed
Merge remote-tracking branch 'origin/main' into draft-notifications-prompt
# Conflicts: # apps/webapp/app/services/platformNotifications.server.ts
2 parents 11a77b5 + 1034b61 commit 1e131f2

1,229 files changed

Lines changed: 91191 additions & 18401 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/REVIEW.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ Every PR gets a performance pass — not just the ones that look perf-sensitive.
5656
## Always check
5757

5858
- **Tests use testcontainers, not mocks.** Vitest with `redisTest` / `postgresTest` / `containerTest` from `@internal/testcontainers`. Any new `vi.mock(...)` on Redis, Postgres, BullMQ, or other infra is wrong here — 🔴 if added in production-path tests, 🟡 if isolated unit test.
59-
- **Public-package changes have a changeset.** `pnpm run changeset:add` produces `.changeset/*.md`. Required for any edit under `packages/*`. Missing → 🟡; missing on a breaking change → 🔴.
60-
- **Server-only changes have `.server-changes/*.md`.** Required for `apps/webapp/`, `apps/supervisor/` edits with no public-package change. Body should be 1-2 sentences (it has to fit as one bullet in a future changelog). Missing → 🟡.
59+
- **User-facing public-package changes have a changeset.** `pnpm run changeset:add` produces `.changeset/*.md`. Changesets are user-facing release notes, not a catalog of every change: required when a `packages/*` or `integrations/*` change is something a user would notice or act on, skipped for internal-only changes, refactors, chores, and packages not consumed independently (e.g. `@trigger.dev/redis-worker`). Missing on a user-facing change → 🟡; missing on a breaking change → 🔴. Do not flag a missing note when the change is not user-facing.
60+
- **User-facing server-only changes have `.server-changes/*.md`.** Required for user-facing `apps/webapp/`, `apps/supervisor/` edits in a PR with no package or integration change that requires a changeset; skip internal-only or admin-only changes, refactors, and chores. Body should be 1-2 sentences (it has to fit as one bullet in a future changelog). Missing on a user-facing change → 🟡.
6161
- **Lua script naming.** Coexisting scripts use behavior-descriptive suffixes (`Tracked`), never `V2`. Old name must keep working until the next deploy clears it.
6262
- **RunQueue payload shape.** V2 run-queue payload's `projectId` is consumed by `workerQueueResolver` for override matching. If a PR drops it from the payload, 🔴.
6363
- **`safeSend` scope.** Defensive IPC wrappers belong on loop / interval / handler contexts, not one-shot terminal sends. If the PR adds `safeSend` to a single terminal call for consistency, 🟡 with a "remove this" suggestion.

.claude/rules/server-apps.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ paths:
55

66
# Server App Changes
77

8-
When modifying server apps (webapp, supervisor, etc.) with **no package changes**, add a `.server-changes/` file instead of a changeset:
8+
`.server-changes/` files are user-facing release notes, not a catalog of every change. When a user-facing server app change (webapp, supervisor, etc.) is in a PR with **no package or integration change that requires a changeset**, add a `.server-changes/` file instead of a changeset. Skip it for internal-only or admin-only changes, refactors, and chores:
99

1010
```bash
1111
cat > .server-changes/descriptive-name.md << 'EOF'
@@ -20,6 +20,6 @@ EOF
2020

2121
- **area**: `webapp` | `supervisor`
2222
- **type**: `feature` | `fix` | `improvement` | `breaking`
23-
- If the PR also touches `packages/`, just the changeset is sufficient (no `.server-changes/` needed).
23+
- If the PR also touches `packages/` or `integrations/` and that change needs a changeset, the changeset covers it (no `.server-changes/` needed). If the package or integration change is internal and needs no changeset, still add a `.server-changes/` file for the user-facing server change.
2424

2525
The body ships **verbatim in user-facing release notes**. Keep it to 1–2 short sentences, non-technical, written for a dashboard user: describe what changed for them, never the implementation (no header names, endpoints, middleware, storage mechanisms, internal tools). See `.server-changes/README.md` for full guidance.

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ NODE_ENV=development
2323
CLICKHOUSE_URL=http://default:password@localhost:8123
2424
RUN_REPLICATION_CLICKHOUSE_URL=http://default:password@localhost:8123
2525
RUN_REPLICATION_ENABLED=1
26+
# LOGS_SEARCH_PROJECTOR_ENABLED=1
27+
# LOGS_SEARCH_PROJECTOR_PREVIEW_ENABLED=1
2628
# Store task run spans/traces in ClickHouse so the dashboard trace view is
2729
# populated in local dev. The local stack is ClickHouse-backed (see above), so
2830
# leaving this unset falls back to the "postgres" store and dev run traces show

.github/VOUCHED.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ brentshulman-silkline
2828
Leafgard
2929
Rohan170603
3030
NERLOE
31-
Jakub-Vacek
31+
Jakub-Vacek
32+
gtremper

.github/workflows/base-images.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ jobs:
3939
build_packages: ${{ steps.config.outputs.build_packages }}
4040
suite: ${{ steps.config.outputs.suite }}
4141
snapshot: ${{ steps.config.outputs.snapshot }}
42+
publish_id: ${{ steps.config.outputs.publish_id }}
4243
source_date_epoch: ${{ steps.config.outputs.source_date_epoch }}
4344
push: ${{ steps.config.outputs.push }}
4445
steps:
@@ -52,6 +53,7 @@ jobs:
5253
SNAPSHOT_INPUT: ${{ inputs.debian_snapshot }}
5354
EVENT_NAME: ${{ github.event_name }}
5455
REF: ${{ github.ref }}
56+
SHA: ${{ github.sha }}
5557
run: |
5658
PACKAGES="$(jq -er '.packages' base-images/images.json)"
5759
BUILD_PACKAGES="$(jq -er '.buildPackages' base-images/images.json)"
@@ -89,6 +91,7 @@ jobs:
8991
echo "build_packages=$BUILD_PACKAGES"
9092
echo "suite=$SUITE"
9193
echo "snapshot=$SNAPSHOT"
94+
echo "publish_id=${SNAPSHOT:0:8}-${SNAPSHOT:9:6}-${SHA:0:7}"
9295
echo "source_date_epoch=$EPOCH"
9396
echo "push=$PUSH"
9497
} >> "$GITHUB_OUTPUT"
@@ -164,7 +167,11 @@ jobs:
164167
platforms: linux/amd64,linux/arm64
165168
provenance: false
166169
outputs: type=image,push=true,rewrite-timestamp=true
167-
tags: triggerdotdev/${{ matrix.image.repo }}:${{ matrix.image.tag }}
170+
# The dated tag is immutable and keeps every published digest
171+
# tag-referenced forever; shipped CLI releases pin these digests
172+
tags: |
173+
triggerdotdev/${{ matrix.image.repo }}:${{ matrix.image.tag }}
174+
triggerdotdev/${{ matrix.image.repo }}:${{ matrix.image.tag }}-${{ needs.setup.outputs.publish_id }}
168175
build-args: |
169176
BASE_IMAGE=${{ matrix.image.base }}
170177
DEBIAN_SNAPSHOT=${{ needs.setup.outputs.snapshot }}
@@ -187,7 +194,9 @@ jobs:
187194
platforms: linux/amd64,linux/arm64
188195
provenance: false
189196
outputs: type=image,push=true,rewrite-timestamp=true
190-
tags: triggerdotdev/${{ matrix.image.repo }}:${{ matrix.image.tag }}-build
197+
tags: |
198+
triggerdotdev/${{ matrix.image.repo }}:${{ matrix.image.tag }}-build
199+
triggerdotdev/${{ matrix.image.repo }}:${{ matrix.image.tag }}-build-${{ needs.setup.outputs.publish_id }}
191200
build-args: |
192201
BASE_IMAGE=${{ matrix.image.base }}
193202
DEBIAN_SNAPSHOT=${{ needs.setup.outputs.snapshot }}

.github/workflows/code-quality.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,6 @@ jobs:
3636

3737
- name: 🔎 Lint
3838
run: pnpm exec oxlint .
39+
40+
- name: ✂️ Check unused code and dependencies
41+
run: pnpm run knip

.github/workflows/dashboard-agent-deploy.yml

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,19 @@ name: "🤖 Deploy dashboard agent"
33
# Deploys the @internal/dashboard-agent chat.agent to its Trigger.dev project
44
# with --skip-promotion, so a deploy never becomes "current" on its own. The
55
# consuming app cuts over by pinning DASHBOARD_AGENT_VERSION to the new version.
6-
# Runs a leg per environment (staging + prod), each gated by its own environment;
7-
# a push to main that touches the agent or its store triggers both. Version
8-
# numbers are per-environment, so pin each environment to its own leg's version.
6+
# Runs a leg per environment (staging + prod); a push to main that touches the
7+
# agent or its store deploys both. Version numbers are per-environment, so pin
8+
# each environment to its own leg's version.
9+
#
10+
# The deploy lands dormant, so it doesn't need a reviewer gate: nothing goes live
11+
# until DASHBOARD_AGENT_VERSION is flipped. The `environment:` below is kept only
12+
# to scope the deploy token per environment; its required-reviewers rule is
13+
# removed in repo settings so pushes deploy unattended. workflow_dispatch takes an
14+
# optional ref (SHA, branch, or tag) to deploy a specific commit instead of head.
15+
#
16+
# The deployed ref must be an ancestor of main, so only reviewed, merged code ever
17+
# runs with the deploy token (the checked-out build + trigger.config.ts execute
18+
# with it). A push is always on main; a dispatched ref is checked before deploy.
919

1020
on:
1121
push:
@@ -14,6 +24,11 @@ on:
1424
- "internal-packages/dashboard-agent/**"
1525
- "internal-packages/dashboard-agent-db/**"
1626
workflow_dispatch:
27+
inputs:
28+
ref:
29+
description: "Commit SHA, branch, or tag to deploy. Defaults to the ref the workflow runs from."
30+
required: false
31+
type: string
1732

1833
permissions: {}
1934

@@ -27,9 +42,15 @@ jobs:
2742
max-parallel: 1
2843
matrix:
2944
environment: [staging, prod]
30-
# Per-environment reviewer gate + source of the scoped deploy PAT.
45+
# Kept to scope the deploy token per environment. The required-reviewers rule
46+
# on these environments is removed in repo settings, so this no longer gates.
3147
environment: dashboard-agent-${{ matrix.environment }}
3248
concurrency:
49+
# Queue a superseding deploy behind an in-flight one; do NOT cancel it.
50+
# Cancelling the runner wouldn't stop the remote build (it finishes
51+
# server-side), and a second concurrent deploy of the same project would
52+
# race the indexer. Deploys are short now the gate is gone, so a brief queue
53+
# is fine and can't pile up.
3354
group: dashboard-agent-deploy-${{ matrix.environment }}
3455
cancel-in-progress: false
3556
permissions:
@@ -41,8 +62,34 @@ jobs:
4162
- name: Checkout
4263
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
4364
with:
65+
# push: the pushed commit. workflow_dispatch: the input ref if given,
66+
# otherwise the head of the ref the run was launched from.
67+
ref: ${{ github.event.inputs.ref || github.sha }}
68+
# Full history so the ancestor-of-main check below can find a merge base.
69+
fetch-depth: 0
4470
persist-credentials: false
4571

72+
- name: Require the ref to be an ancestor of main
73+
# The deploy token runs the checked-out code, so refuse anything that
74+
# hasn't landed on main. A push is main's tip (ancestor of itself); this
75+
# only ever rejects a dispatched, unmerged ref.
76+
#
77+
# NOTE: this in-file check only constrains WHICH commit is deployed. It
78+
# can't protect the token on its own, because workflow_dispatch runs the
79+
# workflow file from the selected ref. The real guard is the deployment
80+
# branch policy on the dashboard-agent-* environments (main only), set in
81+
# repo settings, which GitHub enforces server-side against GITHUB_REF.
82+
run: |
83+
set -euo pipefail
84+
# An explicit `ref:` checkout doesn't create remote-tracking branches,
85+
# so fetch main before comparing against it.
86+
git fetch --no-tags --quiet origin +refs/heads/main:refs/remotes/origin/main
87+
if ! git merge-base --is-ancestor HEAD origin/main; then
88+
echo "::error::Refusing to deploy $(git rev-parse HEAD): not an ancestor of origin/main. Only merged code can be deployed."
89+
exit 1
90+
fi
91+
echo "$(git rev-parse --short HEAD) is an ancestor of origin/main"
92+
4693
- name: Setup pnpm
4794
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
4895
with:

.github/workflows/e2e-webapp-auth-full.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,10 @@ jobs:
9999
run: echo "DockerHub login skipped because secrets are not available."
100100

101101
- name: 🐳 Pre-pull testcontainer images
102-
if: ${{ env.DOCKERHUB_USERNAME }}
103102
run: |
104103
docker pull postgres:14
105104
docker pull redis:7.2
106-
docker pull testcontainers/ryuk:0.11.0
105+
docker pull testcontainers/ryuk:0.14.0
107106
108107
- name: 📥 Download deps
109108
run: pnpm install --frozen-lockfile

.github/workflows/e2e-webapp.yml

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,15 @@ jobs:
1616
name: "🧪 E2E Tests: Webapp"
1717
runs-on: warp-ubuntu-latest-x64-16x
1818
timeout-minutes: 30
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
shardIndex: [1, 2]
23+
shardTotal: [2]
1924
env:
2025
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
26+
SHARD_INDEX: ${{ matrix.shardIndex }}
27+
SHARD_TOTAL: ${{ matrix.shardTotal }}
2128
steps:
2229
- name: 🔧 Disable IPv6
2330
run: |
@@ -57,7 +64,7 @@ jobs:
5764
version: 10.33.2
5865

5966
- name: ⎔ Setup node
60-
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
67+
uses: WarpBuilds/setup-node@bc639b444d583175926b588962199c247d23e8d3 # v6
6168
with:
6269
node-version: 24.18.0
6370
cache: "pnpm"
@@ -73,19 +80,52 @@ jobs:
7380
if: ${{ !env.DOCKERHUB_USERNAME }}
7481
run: echo "DockerHub login skipped because secrets are not available."
7582

76-
- name: 🐳 Pre-pull testcontainer images
77-
if: ${{ env.DOCKERHUB_USERNAME }}
83+
- name: 📥 Prepare deps and testcontainer images
7884
run: |
79-
echo "Pre-pulling Docker images with authenticated session..."
80-
docker pull postgres:14
81-
docker pull redis:7.2
82-
docker pull testcontainers/ryuk:0.11.0
83-
docker pull ghcr.io/s2-streamstore/s2:0.40.0@sha256:b26249e2ede0949755f5af8028185dc2bcfc3aa2db21eb9610543d144eb6ee9d
84-
docker pull minio/minio:latest
85-
echo "Image pre-pull complete"
85+
# Pull images concurrently with dependency installation. Retry each pull because
86+
# registry timeouts are a recurring transient CI flake.
87+
pull() {
88+
for attempt in 1 2 3; do
89+
docker pull "$1" && return 0
90+
echo "::warning::docker pull $1 failed (attempt ${attempt}/3); retrying in 10s"
91+
sleep 10
92+
done
93+
echo "::error::docker pull $1 failed after 3 attempts"
94+
return 1
95+
}
96+
97+
pull_images() {
98+
local pids=()
99+
local failed=0
100+
for image in \
101+
postgres:14 \
102+
redis:7.2 \
103+
testcontainers/ryuk:0.14.0 \
104+
ghcr.io/s2-streamstore/s2:0.40.0@sha256:b26249e2ede0949755f5af8028185dc2bcfc3aa2db21eb9610543d144eb6ee9d \
105+
minio/minio:latest
106+
do
107+
pull "$image" &
108+
pids+=("$!")
109+
done
110+
for pid in "${pids[@]}"; do
111+
if ! wait "$pid"; then
112+
failed=1
113+
fi
114+
done
115+
return "$failed"
116+
}
86117
87-
- name: 📥 Download deps
88-
run: pnpm install --frozen-lockfile
118+
echo "Installing dependencies and pre-pulling Docker images..."
119+
pull_images &
120+
pull_pid=$!
121+
install_status=0
122+
pnpm install --frozen-lockfile || install_status=$?
123+
pull_status=0
124+
wait "$pull_pid" || pull_status=$?
125+
if (( install_status != 0 || pull_status != 0 )); then
126+
exit 1
127+
fi
128+
echo "Dependency install and image pre-pull complete"
89129
90130
- name: 📀 Generate Prisma Client
91131
run: pnpm run generate
@@ -97,6 +137,6 @@ jobs:
97137
run: cd apps/webapp && pnpm exec playwright install chromium
98138

99139
- name: 🧪 Run Webapp E2E Tests
100-
run: cd apps/webapp && pnpm exec vitest run --config vitest.e2e.config.ts --reporter=default
140+
run: cd apps/webapp && pnpm exec vitest run --config vitest.e2e.config.ts --reporter=default --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
101141
env:
102142
WEBAPP_TEST_VERBOSE: "1"
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: "🛡️ FK Cascade Index Guard"
2+
3+
on:
4+
workflow_call:
5+
6+
permissions:
7+
contents: read
8+
9+
jobs:
10+
fk-cascade-guard:
11+
runs-on: warp-ubuntu-latest-x64-16x
12+
13+
steps:
14+
- name: ⬇️ Checkout repo
15+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
16+
with:
17+
fetch-depth: 0
18+
persist-credentials: false
19+
20+
- name: ⎔ Setup pnpm
21+
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
22+
with:
23+
version: 10.33.2
24+
25+
- name: ⎔ Setup node
26+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
27+
with:
28+
node-version: 24.18.0
29+
cache: "pnpm"
30+
31+
- name: 📥 Download deps
32+
run: pnpm install --frozen-lockfile
33+
34+
- name: 🛡️ FK cascade index guard
35+
run: pnpm --filter webapp run guard:fk-cascade-index -- --check

0 commit comments

Comments
 (0)