Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
eb7f34b
feat(backend): call any OpenAI-compatible endpoint from the AI adapt …
dawid-aksamski Sep 1, 2026
7a50877
feat(execution-worker): boot without an LLM key; AI nodes fail at cal…
dawid-aksamski Sep 1, 2026
f11e3f4
feat(backend): Temporal namespace and TLS/mTLS/API-key connection config
dawid-aksamski Sep 1, 2026
552a823
feat(execution-worker): Temporal namespace and TLS/mTLS/API-key conne…
dawid-aksamski Sep 1, 2026
d824d39
build(deps): put the AI SDK packages in the catalog
dawid-aksamski Sep 1, 2026
dc01fa5
feat(deploy): pass LLM endpoint and Temporal connection config throug…
dawid-aksamski Sep 1, 2026
9b468ed
fix(config): drop the OpenRouter key alias and built-in LLM defaults
dawid-aksamski Sep 4, 2026
be9bf5b
fix(execution-worker): make ai_not_configured a permanent failure
dawid-aksamski Sep 4, 2026
9a336bc
feat(deploy): pass Temporal TLS paths through compose and mount ./tls
dawid-aksamski Sep 4, 2026
e947785
feat(deploy): let an external Temporal retire the bundled cluster
dawid-aksamski Sep 4, 2026
1d5c39b
test(config): isolate env tests from the runner's environment
dawid-aksamski Sep 4, 2026
168c5d9
docs(site): document secured and external Temporal configuration
dawid-aksamski Sep 4, 2026
0173914
fix(deploy): run compose from the project dir and ship both files to …
dawid-aksamski Sep 4, 2026
b88c929
fix(deploy): keep certificate files out of the image build context
dawid-aksamski Sep 4, 2026
e0123d9
fix(config): ship .env.example with an empty AI_API_KEY
dawid-aksamski Sep 4, 2026
9337b8b
docs: align the root README with the no-default LLM configuration
dawid-aksamski Sep 4, 2026
f86f26f
fix(deploy): refuse to start while OPENROUTER_API_KEY is still set
dawid-aksamski Sep 4, 2026
f3ef683
docs: correct the Temporal failure mode and remove default-wording drift
dawid-aksamski Sep 4, 2026
984097f
fix(deploy): keep custom TEMPORAL_TLS_DIR out of the image build context
dawid-aksamski Sep 8, 2026
e434d87
fix(ci): persist deployed image tags in the VM's .env
dawid-aksamski Sep 8, 2026
db829b7
test(backend,execution-worker): prove TLS, mTLS and API-key transport…
dawid-aksamski Sep 8, 2026
e04466b
refactor(temporal-connection): one copy of the TEMPORAL_* rules
dawid-aksamski Sep 8, 2026
0b21add
refactor(ai-config): share the AI_* contract, keep the runtime reacti…
dawid-aksamski Sep 8, 2026
e9ccc5b
docs: limit the "no external traffic" promise to model requests
dawid-aksamski Sep 8, 2026
d6bc032
refactor: drop a header that restated the function, name the PEM reader
dawid-aksamski Sep 8, 2026
1dbca05
test(temporal-connection): remove the TLS test's temp PKI directories
dawid-aksamski Sep 8, 2026
108e998
docs(deploy): the backend calls the LLM too, for the visualize route
dawid-aksamski Sep 8, 2026
df6a93b
fix(deploy): check the retired key in the deploy script, not in compose
dawid-aksamski Sep 15, 2026
170ae70
fix(backend): validate TEMPORAL_* at boot, like the worker
dawid-aksamski Sep 15, 2026
ce77282
feat(ai-config): name a retired AI variable that is still set
dawid-aksamski Sep 15, 2026
8f24384
test(backend): pin the env defaults the deploy depends on
dawid-aksamski Sep 15, 2026
a9ba20b
test(temporal-connection): prove the plaintext default connects
dawid-aksamski Sep 15, 2026
30b3510
fix(ai-studio): drop the provider name from the disclaimer
dawid-aksamski Sep 15, 2026
f19347a
build(icons): pin @svgr/cli and drop the npx shell-out
dawid-aksamski Sep 1, 2026
05d759e
feat(deploy): strict-offline image builds and air-gapped install docs
dawid-aksamski Sep 2, 2026
2166e6d
fix(deploy): cut network from every post-fetch image build step
dawid-aksamski Sep 4, 2026
d4b4379
fix(deploy): pack a verifiable air-gap bundle and pin Postgres to a m…
dawid-aksamski Sep 4, 2026
ac3de7f
docs(deploy): state the supported air-gap model and the platform rule
dawid-aksamski Sep 4, 2026
e264065
fix(ai-studio): drop the Google Fonts CDN and state the exact zero-eg…
dawid-aksamski Sep 4, 2026
119c2b8
fix(deploy): keep the air-gap bundle out of the checkout and the buil…
dawid-aksamski Sep 4, 2026
f385e8b
fix(deploy): stop pulling the BuildKit frontend from Docker Hub
dawid-aksamski Sep 7, 2026
f2da3a6
docs(deploy): mark the runtime image slimming as a follow-up
dawid-aksamski Sep 7, 2026
ec4f5b7
fix(deploy): close guard false negatives and isolate the pack script …
dawid-aksamski Sep 7, 2026
9d64ef4
docs(deploy): align the air-gap docs with the code and tighten the bu…
dawid-aksamski Sep 7, 2026
33821e7
feat(tools): warn in preflight when a local .env still uses the retir…
dawid-aksamski Sep 7, 2026
b04b6d7
fix(deploy): close the offline guard to ADD/COPY egress and isolate p…
dawid-aksamski Sep 8, 2026
ab74f64
docs: the deploy workflow guards the retired key, not compose
dawid-aksamski Sep 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ node_modules
**/node_modules

# docker files
docker-compose*.yml
**/docker-compose*.yml
**/Dockerfile*

# build artifacts
Expand All @@ -21,7 +21,6 @@ coverage/
# not needed files
README.md
tools/
!tools/deployment/nginx
.gitignore

# examples
Expand All @@ -31,3 +30,23 @@ examples/
**/.env
**/.env.*
!**/.env.example

# registry auth must not end up in a layer
**/.npmrc
**/.yarnrc*

# certificate material is mounted at runtime — never built into an image. The
# Dockerfile needs one file from deploy/, so the rest stays out of the context:
# a TEMPORAL_TLS_DIR under deploy/ cannot reach COPY . . whatever it is named.
deploy/
!deploy/ai-studio/nginx
**/*.pem
**/*.key
**/*.crt
**/*.cer
**/*.p12
**/*.pfx

# air-gap bundle and image tarballs — a build must never copy them into an image
**/ai-studio-offline
**/*.tar
47 changes: 40 additions & 7 deletions .github/workflows/deploy-ai-studio.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,25 +77,58 @@ jobs:
needs: build-and-push

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Log in to Azure
uses: azure/login@v2
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}

# The VM runs the repo's compose files, shipped here on every deploy (base64,
# so the script stays free of quoting). Compose is run from the project
# directory, not with -f: that is what applies docker-compose.override.yml
# by default and honours COMPOSE_FILE from the VM's .env.
#
# The retired-key check runs before anything is written, so a refused deploy
# leaves the VM exactly as it was. It lives here rather than in the compose
# file because Compose 2.21 and older evaluate a nested `${A:+${B:?}}` guard
# eagerly and fail on every command, key set or not.
#
# The image tags are written into that .env rather than exported: an export
# dies with this shell, and the next `docker compose up -d worker` on the VM
# would fall back to the local ai-studio-* names. Only the two image lines
# are replaced; the rest of .env is the VM's own and stays untouched.
- name: Refresh docker compose on Azure VM
env:
IMAGE: ${{ env.REGISTRY }}/${{ env.APP }}:${{ needs.build-and-push.outputs.image_tag }}
run: |
COMPOSE_B64=$(base64 -w0 deploy/ai-studio/docker-compose.yml)
OVERRIDE_B64=$(base64 -w0 deploy/ai-studio/docker-compose.override.yml)
SCRIPT=$(cat <<EOF
set -e
cd /app/ai-studio
if [ -f .env ] && grep -Eq '^OPENROUTER_API_KEY=.+' .env; then
echo "OPENROUTER_API_KEY is still set in the VM's .env. It was renamed to AI_API_KEY and is no longer read; rename it and set AI_BASE_URL and AI_MODEL too (values in deploy/ai-studio/.env.example), then deploy again."
exit 1
fi
echo "$COMPOSE_B64" | base64 -d > docker-compose.yml
echo "$OVERRIDE_B64" | base64 -d > docker-compose.override.yml
touch .env
{ grep -vE '^(RUNTIME_IMAGE|WEB_IMAGE)=' .env || true; printf 'RUNTIME_IMAGE=%s\nWEB_IMAGE=%s\n' "$IMAGE-runtime" "$IMAGE-web"; } > .env.tmp
chmod --reference=.env .env.tmp && chown --reference=.env .env.tmp && mv .env.tmp .env
az acr login --name synergycodes
docker compose pull
docker compose up -d --no-build --force-recreate --remove-orphans
echo DEPLOY_SCRIPT_SUCCEEDED
EOF
)
OUTPUT=$(az vm run-command invoke \
--name ${{ vars.AI_STUDIO_VM_NAME }} \
--resource-group ${{ vars.AI_STUDIO_VM_RG }} \
--command-id RunShellScript \
--scripts '
set -e
az acr login --name synergycodes
docker compose -f /app/ai-studio/docker-compose.yml pull
docker compose -f /app/ai-studio/docker-compose.yml up -d --no-build --force-recreate
echo DEPLOY_SCRIPT_SUCCEEDED
')
--scripts "$SCRIPT")
echo "$OUTPUT"
echo "$OUTPUT" | grep -q DEPLOY_SCRIPT_SUCCEEDED
56 changes: 45 additions & 11 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ name: PR Check
# @workflowbuilder/ui-tokens build) and @workflowbuilder/temporal, and the
# execution pipeline (execution-core, backend, execution-worker) — whose
# determinism tests guard Temporal replay safety and so must not be able to
# regress silently. Plus
# global format consistency. apps/docs has its own path-filtered workflow
# (pr-check-docs.yml); demo and ai-studio are not checked here — they're
# internal and have their own broken-state tolerances.
# regress silently. Plus the deploy compose files, which ship to the demo VM on
# every deploy, the deploy Dockerfile's air-gap boundary (every RUN after
# `pnpm fetch` must be --network=none), and global format consistency.
# apps/docs has its own path-filtered workflow (pr-check-docs.yml); demo and
# ai-studio are not checked here — they're internal and have their own
# broken-state tolerances.

on:
pull_request:
Expand Down Expand Up @@ -46,6 +48,9 @@ jobs:
- name: Prettier --check
run: pnpm exec prettier --check "**/*.+(css|ts|tsx|json|md|mdx|astro)" --log-level=warn

- name: Air-gap boundary of the deploy Dockerfile
run: pnpm check:offline-build

sdk:
name: SDK lint + typecheck + test + build
runs-on: ubuntu-latest
Expand Down Expand Up @@ -170,6 +175,34 @@ jobs:
fi
fi

deploy-compose:
name: Deploy compose files parse
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Parse both compose modes on the runner's Compose and on the oldest supported one
# These files reach the demo VM on every deploy, so a parse error is only
# discovered there, with the stack already down. 2.21 is the floor: it
# interpolates a nested `${A:+${B:?}}` default eagerly where newer
# compose-go is lazy, so a file that parses on the runner can still fail
# on a VM. Both modes are covered because COMPOSE_FILE in the VM's .env
# decides whether the override file is applied at all.
working-directory: deploy/ai-studio
run: |
cp .env.example .env
# An empty COMPOSE_FILE is not the same as an unset one — compose then
# reads the working directory as a file — so the default mode runs with
# the variable absent and `-e` forwards it only once it is exported.
parse() {
docker compose config --quiet
docker run --rm -v "$PWD:/w" -w /w -e COMPOSE_FILE docker:24.0.5-cli docker compose config --quiet
}
parse
export COMPOSE_FILE=docker-compose.yml
parse

ui:
name: UI + UI tokens lint + typecheck + test + build
runs-on: ubuntu-latest
Expand Down Expand Up @@ -219,10 +252,11 @@ jobs:
execution:
name: Execution pipeline lint + typecheck + test
runs-on: ubuntu-latest
# No `services:` block: all three suites are pure unit tests against
# in-memory fakes — no Postgres, no Temporal, no API keys. If a suite here
# ever needs real infra, give it its own job rather than adding services
# to this one.
# No `services:` block: the suites run against in-memory fakes — no Postgres,
# no API keys. The one exception is temporal-connection's TLS test, which
# starts Temporal's dev server itself (@temporalio/testing downloads the CLI
# on first run). If a suite here ever needs infra it cannot start itself,
# give it its own job rather than adding services to this one.
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -244,10 +278,10 @@ jobs:
run: pnpm install --frozen-lockfile

- name: Lint
run: pnpm --filter @workflow-builder/execution-core --filter @workflow-builder/backend --filter @workflow-builder/execution-worker lint
run: pnpm --filter @workflow-builder/execution-core --filter @workflow-builder/ai-config --filter @workflow-builder/temporal-connection --filter @workflow-builder/backend --filter @workflow-builder/execution-worker lint

- name: Typecheck
run: pnpm --filter @workflow-builder/execution-core --filter @workflow-builder/backend --filter @workflow-builder/execution-worker typecheck
run: pnpm --filter @workflow-builder/execution-core --filter @workflow-builder/ai-config --filter @workflow-builder/temporal-connection --filter @workflow-builder/backend --filter @workflow-builder/execution-worker typecheck

- name: Test
run: pnpm --filter @workflow-builder/execution-core --filter @workflow-builder/backend --filter @workflow-builder/execution-worker test
run: pnpm --filter @workflow-builder/execution-core --filter @workflow-builder/ai-config --filter @workflow-builder/temporal-connection --filter @workflow-builder/backend --filter @workflow-builder/execution-worker test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ tmp
# pnpm pack / npm pack artefacts (local tarballs for smoke tests + publish dry-runs)
*.tgz

# air-gap bundle from deploy/ai-studio/pack-offline.sh, if written into the checkout
ai-studio-offline/

# Emitted declarations from `pnpm --filter @workflow-builder/icons build`
# (prepare hook). Root + src/ locations are outside `dist/` so need explicit
# patterns. `global.d.ts` is a source file — don't ignore.
Expand Down
38 changes: 24 additions & 14 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Three onboarding paths (A installs from npm; B, C run the repo locally). README
| `pnpm build:temporal` | - | Build `@workflowbuilder/temporal` (also built on install via its `prepare`) |
| `pnpm build` | - | Build the demo app |
| `pnpm test` | - | Run tests in every workspace that defines a `test` script (`pnpm -r test`) |
| `pnpm check` | - | Lint + typecheck + format + knip |
| `pnpm check` | - | Lint + typecheck + format + deploy Dockerfile air-gap guard |

Path B is UI-only and does not need Docker. Path C requires `pnpm infra:up` before backend/worker can start; the backend applies pending migrations automatically at boot.

Expand Down Expand Up @@ -60,11 +60,13 @@ apps/
icons/ - Icon generation pipeline
tools/ - @workflow-builder/tools workspace (decision-log collector, lint-staged config)
packages/
ai-config/ - Private, source-only: the AI_API_KEY / AI_BASE_URL / AI_MODEL contract, one copy shared by backend and worker
sdk/ - @workflowbuilder/sdk public package (WorkflowBuilder compound component, plugin API, components)
ui/ - @workflowbuilder/ui published component library (Base UI), consumed by sdk/demo/ai-studio
tokens/ - @workflowbuilder/ui-tokens private design-token build (style-dictionary), feeds packages/ui
execution-core/ - Pure topological graph runner + node executor registry
temporal/ - @workflowbuilder/temporal published Temporal Plugin (activities + workflow runner); bundles execution-core + types into its dist
temporal-connection/ - Private, source-only: TEMPORAL_* env -> validated connection options + namespace, one copy shared by backend and worker
types/ - Shared TypeScript types
```

Expand All @@ -74,21 +76,25 @@ Where to put a new script: root `tools/` for pure-Node bootstrap (runs before an

Each workspace has its own context. Read the relevant file before extending a workspace.

| Workspace | Authoritative docs |
| ------------------------- | ------------------------------------------------------- |
| `packages/sdk` | `packages/sdk/README.md` |
| `packages/ui` | `packages/ui/README.md` (+ `packages/ui/css-layers.md`) |
| `packages/tokens` | `packages/tokens/README.md` |
| `packages/execution-core` | `packages/execution-core/README.md` |
| `packages/temporal` | `packages/temporal/README.md` |
| `apps/demo` | `apps/demo/CLAUDE.md` |
| `apps/ai-studio` | `apps/ai-studio/README.md` |
| `apps/backend` | `apps/backend/README.md` |
| `apps/execution-worker` | `apps/execution-worker/README.md` |
| Workspace | Authoritative docs |
| ------------------------------ | ------------------------------------------------------- |
| `packages/sdk` | `packages/sdk/README.md` |
| `packages/ui` | `packages/ui/README.md` (+ `packages/ui/css-layers.md`) |
| `packages/tokens` | `packages/tokens/README.md` |
| `packages/ai-config` | `packages/ai-config/README.md` |
| `packages/execution-core` | `packages/execution-core/README.md` |
| `packages/temporal` | `packages/temporal/README.md` |
| `packages/temporal-connection` | `packages/temporal-connection/README.md` |
| `apps/demo` | `apps/demo/CLAUDE.md` |
| `apps/ai-studio` | `apps/ai-studio/README.md` |
| `apps/backend` | `apps/backend/README.md` |
| `apps/execution-worker` | `apps/execution-worker/README.md` |

## Types & Aliases

Shared types: `packages/types/` (imported as `@workflow-builder/types/*`).
AI configuration contract: `packages/ai-config/` (imported as `@workflow-builder/ai-config`; `aiConfig()` tells backend and worker whether the LLM is configured and what is missing).
Temporal connection config: `packages/temporal-connection/` (imported as `@workflow-builder/temporal-connection`; `temporalConfig()` gives backend and worker their connect options and namespace).
Icons: `apps/icons/` (imported as `@workflow-builder/icons`).
SDK: `packages/sdk/` (imported as `@workflowbuilder/sdk`).
UI: `packages/ui/` (imported as `@workflowbuilder/ui`; styles via `@workflowbuilder/ui/styles.css`, `/index.css`, `/tokens.css`).
Expand All @@ -102,7 +108,11 @@ UI: `packages/ui/` (imported as `@workflowbuilder/ui`; styles via `@workflowbuil
- Temporal server on `7233` (gRPC)
- Temporal UI on http://localhost:8233

Backend reads `DATABASE_URL` and `TEMPORAL_ADDRESS`; defaults work out of the box. `pnpm infra:down` stops everything.
Backend reads `DATABASE_URL` and `TEMPORAL_ADDRESS`; defaults work out of the box. Pointing either app at a secured cluster or Temporal Cloud is env-only (`TEMPORAL_NAMESPACE`, `TEMPORAL_TLS`, `TEMPORAL_API_KEY`, `TEMPORAL_TLS_*_PATH`) - see `apps/backend/README.md` "Connecting to a secured Temporal cluster". `pnpm infra:down` stops everything.

### Migrating a local `.env` after pulling

`OPENROUTER_API_KEY` was renamed to `AI_API_KEY`, and `AI_BASE_URL` is now required alongside `AI_MODEL` for AI Agent nodes (September 2026; no alias, no built-in default). A stale `apps/backend/.env` or `apps/execution-worker/.env` does not fail at boot: the apps start with AI silently off and AI nodes fail with `ai_not_configured`. `pnpm preflight` warns on both files, so run it after pulling and before `pnpm dev:backend`, `pnpm dev:worker` or `pnpm dev:ai-studio`. When it warns, offer the user this migration and apply it only with their go-ahead, since the files hold their key: rename the `OPENROUTER_API_KEY` line to `AI_API_KEY` keeping the value, add `AI_BASE_URL=https://openrouter.ai/api/v1` (or their own OpenAI-compatible endpoint), leave `AI_MODEL` as is. Never print the key value. The deploy stack has its own guard: the deploy workflow refuses to run while `OPENROUTER_API_KEY` is set in the VM's `.env`, before it writes anything.

## Code Quality

Expand All @@ -111,7 +121,7 @@ Backend reads `DATABASE_URL` and `TEMPORAL_ADDRESS`; defaults work out of the bo
| ESLint | `pnpm lint` / `pnpm lint:fix` | Per-workspace configs |
| Prettier | `pnpm format` | Sorts imports via `@trivago/prettier-plugin-sort-imports` |
| TypeScript | `pnpm typecheck` | Per-workspace `tsconfig.json` |
| Knip | Part of `pnpm check` | Detects unused exports/dependencies |
| Knip | `pnpm exec knip` | Detects unused exports/dependencies (not part of `pnpm check`) |
| Vitest | `pnpm test` | Runs in every workspace with a `test` script — recursive, so a new workspace is picked up automatically |
| Full check | `pnpm check` | Run before PR |

Expand Down
Loading