Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
71 changes: 71 additions & 0 deletions .github/workflows/helm-chart.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Helm chart

on:
workflow_dispatch:
pull_request:
branches:
- main
paths:
- 'deploy/activepieces-helm/**'
- '.github/workflows/helm-chart.yml'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
install:
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: deploy/activepieces-helm
steps:
- uses: actions/checkout@v5

- uses: azure/setup-helm@v4

- name: Lint and render with default values
run: |
helm repo add mittwald https://helm.mittwald.de
helm repo add bitnami https://charts.bitnami.com/bitnami
helm dependency build
helm lint .
helm template ap . | grep -q '^kind: Rollout'
helm template ap . --api-versions apps/v1/StatefulSet | grep -q '^kind: StatefulSet'
helm template ap . --set autoscaling.enabled=true --api-versions apps/v1/StatefulSet --api-versions argoproj.io/v1alpha1/Rollout > "$RUNNER_TEMP/rollout.yaml"
grep -q '^kind: Rollout' "$RUNNER_TEMP/rollout.yaml"
grep -q 'name: ap-activepieces-preview' "$RUNNER_TEMP/rollout.yaml"
grep -A2 'scaleTargetRef:' "$RUNNER_TEMP/rollout.yaml" | grep -q 'kind: Rollout'
NOTES=$(helm install ap . --dry-run=client --set activepieces.frontendUrl=x)
grep -q 'ignores these values' <<< "$NOTES"
NOTES=$(helm install ap . --dry-run=client)
! grep -q 'ignores these values' <<< "$NOTES"

- uses: helm/kind-action@v1

- name: Create connection secrets
run: |
kubectl create secret generic activepieces-config-secrets --from-literal=AP_FRONTEND_URL=http://ap-activepieces.default.svc.cluster.local
kubectl create secret generic activepieces-db-secrets --from-literal=AP_POSTGRES_HOST=ap-postgresql --from-literal=AP_POSTGRES_PORT=5432 --from-literal=AP_POSTGRES_DATABASE=postgres --from-literal=AP_POSTGRES_USERNAME=postgres --from-literal=AP_POSTGRES_PASSWORD=ci-password
kubectl create secret generic activepieces-redis-secrets --from-literal=AP_REDIS_HOST=ap-redis-master --from-literal=AP_REDIS_PORT=6379 --from-literal=AP_REDIS_PASSWORD=ci-password

- name: Install
run: |
helm install ap . --wait --timeout 10m \
--set image.tag=latest \
--set persistence.storageClassName=standard \
--set postgresql.enabled=true,postgresql.auth.postgresPassword=ci-password \
--set redis.enabled=true,redis.architecture=standalone,redis.auth.password=ci-password

- name: Test
run: |
kubectl get statefulset ap-activepieces
helm test ap

- name: Debug
if: failure()
run: |
kubectl get pods
kubectl describe pods
kubectl logs statefulset/ap-activepieces --tail 200 || true
8 changes: 7 additions & 1 deletion .github/workflows/release-self-hosted.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ jobs:
exit 1
fi
echo "Release tag base '$TAG_BASE' matches package.json version '$PACKAGE_VERSION'."
CHART_APP_VERSION="$(sed -n 's/^appVersion: "\(.*\)"/\1/p' deploy/activepieces-helm/Chart.yaml)"
if [ "$CHART_APP_VERSION" != "$PACKAGE_VERSION" ]; then
echo "::error::Helm chart appVersion '$CHART_APP_VERSION' does not match package.json version '$PACKAGE_VERSION'. Bump deploy/activepieces-helm/Chart.yaml."
exit 1
fi

- name: Fail if tag already exists
run: '! docker manifest inspect activepieces/activepieces:${{ inputs.tag }}'
Expand Down Expand Up @@ -166,8 +171,9 @@ jobs:
npm pkg set version="$TAG_BASE"
sed -i -E "s|(image: ghcr.io/activepieces/activepieces:)[0-9][0-9a-zA-Z.-]*|\1$TAG|" docker-compose.yml
grep -q "image: ghcr.io/activepieces/activepieces:$TAG" docker-compose.yml
sed -i -E "s|^appVersion: .*|appVersion: \"$TAG_BASE\"|" deploy/activepieces-helm/Chart.yaml
git checkout -b "automated/sync-version-$TAG_BASE"
git add package.json docker-compose.yml
git add package.json docker-compose.yml deploy/activepieces-helm/Chart.yaml
git commit -m "chore(release): sync version to $TAG_BASE after off-cycle release"
git push -f origin "automated/sync-version-$TAG_BASE"

Expand Down
6 changes: 3 additions & 3 deletions brain/knowledge/ai-intelligence/ai-providers.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion brain/knowledge/ai-intelligence/mcp-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Exposes an Activepieces project as an MCP server so AI clients (Claude Desktop,
- **A row carries no payload; the input and output go to the `file` table as `MCP_CALL_PAYLOAD`.** That is what V1 got wrong — `mcp_run` had two non-null JSONB columns written synchronously per call. Keeping the fat bytes in a table the list query never reads also buys the retention for free: `MCP_CALL_PAYLOAD` is in `isExecutionDataFileThatExpires`, so it routes to S3 when configured and is swept by the hourly `FILE_CLEANUP_TRIGGER` already running. Row retention rides the same job via `mcpActivityRetention.deleteStale()`, in bounded passes modelled on `agentRetention`.
- **`FileType` is declared twice and the two copies must stay in lockstep** — `core/shared/src/lib/core/file/index.ts` and `core-piece-types/src/lib/execution-contracts.ts`. Adding a member to only one breaks assignability at the seam (`sample-data.service.ts` is the first thing to fail to compile), with an error that points at the *consumer*, not at the enum you edited.

- **The Activity feed renders on two surfaces from one component, so editing its columns or filters edits a platform admin settings page too.** `ActivityFeed` (`app/routes/mcp-server/activity/`) is consumed by the project MCP page's Activity tab *and* by the third tab on `platform/setup/mcp` — a platform admin already sees the whole platform's rows there, because `GET /v1/mcp-activity` is platform-scoped and `resolveUserIdFilter` only narrows a *non*-privileged caller. The platform page is a `CenteredPage`, whose default `max-w-[40rem]` cannot hold a six-column table, so it is widened to the `max-w-[1198px]` band `PageBand` uses on the project page — which also widens its Connection and Tools tabs. The import is `app` → `app`, which the `import/no-restricted-paths` zone allows; had the feed lived in `src/features`, it could not have reached back for `useMcpNav` or `PageBand`.
- **The Activity feed renders on two surfaces from one component, so editing its columns or filters edits a platform admin settings page too.** `ActivityFeed` (`app/routes/mcp-server/activity/`) is consumed by the project MCP page's Activity tab *and* by `/platform/mcp/activity`, a sidebar child of the platform MCP page — a platform admin already sees the whole platform's rows there, because `GET /v1/mcp-activity` is platform-scoped and `resolveUserIdFilter` only narrows a *non*-privileged caller. The platform page is a `CenteredPage`, whose default `max-w-[40rem]` cannot hold a six-column table, so it is widened to the `max-w-[1198px]` band `PageBand` uses on the project page — which also widens its sibling Connection and Tools sections, since all three render through the same `CenteredPage`. The import is `app` → `app`, which the `import/no-restricted-paths` zone allows; had the feed lived in `src/features`, it could not have reached back for `useMcpNav` or `PageBand`.

- **Filter options on a platform-scoped feed must be sourced by privilege, or an admin sees rows they cannot filter by.** `projectCollectionUtils.useAll()` is the member-scoped hook: it keeps TEAM projects plus only the *current user's* PERSONAL project, so on a platform-wide feed an admin gets rows from other members' personal projects with no matching entry in the Project dropdown. Use `useAllPlatformProjects()` when `useIsPlatformPrivileged()` — `/v1/projects` already scopes itself by `isUserPrivileged`, so the wider hook returns nothing extra to a member. The columns are unaffected either way: the server sends `projectName` on each row, so only the *filter* goes blind. Same shape as the platform Connections page, which sources its project filter the same way.

Expand Down
2 changes: 1 addition & 1 deletion brain/knowledge/data-storage-observability/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ All structured logging goes through **evlog** — one wide event per unit of wor

### Product Telemetry

Anonymous product analytics to PostHog, from both the browser and the app container. Gated per platform by `platform_configuration.isProductTelemetryEnabled`, edited at Platform Admin > Infrastructure > Configurations; `AP_TELEMETRY_ENABLED` survives only as the value a platform's row is *born* with. See [000033](../decisions/000033-platform-configuration-rows-are-authoritative-and-created-on-first-read.md).
Anonymous product analytics to PostHog, from both the browser and the app container. Gated per platform by `platform_configuration.isProductTelemetryEnabled`, edited at Platform Admin > Account > Configurations; `AP_TELEMETRY_ENABLED` survives only as the value a platform's row is *born* with. See [000033](../decisions/000033-platform-configuration-rows-are-authoritative-and-created-on-first-read.md).

Gotchas:
- **Two independent PostHog paths exist and they are gated differently.** Product analytics obeys the per-platform switch; the license-key events (`total_runs_per_day`, `ai_usage_per_run`, `chat_message`) go through `captureLicenseKeyEvent` and deliberately do **not**, because PostHog is also the billing transport and a customer must not be able to switch off their own meter. Never "consistency-fix" that by routing a billing event through the gate.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ status: accepted

# Platform admin is fifteen pages derived from one registry

> **Partly superseded** by *platform-admin-sections-are-sidebar-sub-items-each-with-its-own-route* (#15727).
> The registry (`admin-pages.ts`), the overview pages and `?tab=` sections are gone. The gating-at-the-action
> and sample-mode sections below still hold.

## Decision

The platform admin nav collapses from **22 items in 5 labelled groups to 15 items in 3 unlabelled blocks**,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
icon: 🧭
status: accepted
---

# Platform admin sections are sidebar sub-items, each with its own route

## Decision

The platform admin nav is six labelled groups (Platform, Catalogue, Security, Developers, Operations,
Account) written inline in `components/sidebar/platform`, and every section of a page is a sidebar sub-item
with a path of its own (`/platform/ai/capabilities`, `/platform/health/runs`), never a `?tab=`. URLs are
flat and never carry the group, so regrouping is a sidebar edit. There is no registry and there are no
overview pages. The full rule set is *platform-admin-structure*. This replaces the registry, overview and
`?tab=` parts of *platform-admin-is-fifteen-pages-derived-from-one-registry*; its gating and sample-mode
sections still hold.

## Context

PR #15563 merged the admin into fewer pages with tabs, derived from `routes/platform/admin-pages.ts`, and
gave six section parents an overview page. PR #15727 was moving in-page tabs into the sidebar at the same
time, and the two collided. The overviews repeated what the child pages already show, and a section that
lives on `?tab=` is invisible to the sidebar's path-based active state.

## Why

A path per section makes the sidebar's active state, deep links and the browser Back button work without a
tab-aware special case, and the nav stays a literal a reader can scan top to bottom. The registry was the
rejected alternative: it removed drift between nav, routes and gates, but needed `isHidden`, `hideInNav`,
`activeTabId` and `isCrowned` to describe a shape that a path per page expresses directly.

## Consequences

- Nav, routes and gates live in three places again (`sidebar/platform/index.tsx`, `platform-routes.tsx`,
and the page), so a new admin page has to be added to all three.
- Old `?tab=` links are rewritten to the new paths by `LegacyTabRedirect`, one `*_TAB_PATHS` map per
promoted page, and every retired `/platform/setup|security|infrastructure/*` URL by `LegacyPathRedirect`. The `?tab=` URLs #15563 introduced (`/platform/users?tab=sso`, `/platform/security?tab=…`)
were live for a day and are not redirected.
- The sample-data overlay that the registry applied per page now sits on the route, via
`routes/platform/plan-feature-sample.tsx`. A sampled page that is not wrapped there renders unlocked,
because the nine sampled pages no longer carry a `LockedFeatureGuard` of their own.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
status: accepted
---

# Helm chart keys are generated in-cluster, and user secrets override them

## Decision

The chart gets `AP_ENCRYPTION_KEY` and `AP_JWT_SECRET` from the mittwald `secret-generator` subchart, which fills `<fullname>-secrets` and `<fullname>-jwt-secret` in-cluster. The pod reads them through `envFrom`. A user-created `activepieces-auth-secrets` is mapped through `env`, so it wins at runtime.

## Context

GIT-1923: after worker v2 the generated secrets were never wired in, so a default install crashlooped. Users who worked around it had already created `activepieces-auth-secrets` with their own keys, and their stored data is encrypted with those keys.

## Why

`lookup` + `randAlphaNum` regenerates the key on every `helm template` / ArgoCD render, which would silently rotate the encryption key. Rendering one env name twice to get precedence gets rejected by Helm 4's server-side apply. `env` over `envFrom` gives the runtime fallback without either problem, and upgrades change nothing for existing users.

## Consequences

Renaming the generated keys or secrets orphans every install's key. A user key present at render time is pinned (`optional: false`), so the fallback only ever serves installs that never had their own key. The chart keeps a cluster-scoped operator as a dependency.
Loading
Loading