Skip to content
Closed
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/temporal-workflows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Temporal workflow sandbox

on:
pull_request:
paths:
- 'apps/worker/**'
- 'packages/contracts/**'
- 'packages/runtime/**'
- 'packages/memory-graph/**'
- 'package-lock.json'
- '.github/workflows/temporal-workflows.yml'
push:
branches: [main, 'test/temporal-sandbox-replay']
workflow_dispatch:

permissions:
contents: read

jobs:
sandbox-and-replay:
runs-on: ubuntu-latest
timeout-minutes: 8
env:
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version-file: .nvmrc
cache: npm
- name: Install locked dependencies
run: npm ci --ignore-scripts
- name: Build workflow and persisted-fixture dependencies
run: npm run build -w @space/contracts && npm run build -w @space/memory-graph
- name: Real sandbox, recorded replay and negative determinism control
working-directory: apps/worker
run: |
start=$SECONDS
npm test -- tests/temporal-sandbox.test.ts
elapsed=$((SECONDS - start))
echo "Temporal workflow suite completed in ${elapsed}s"
test "$elapsed" -lt 180
68 changes: 35 additions & 33 deletions apps/worker/package.json
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
{
"name": "@space/worker",
"version": "0.2.5",
"private": true,
"type": "module",
"main": "dist/worker.js",
"types": "dist/worker.d.ts",
"scripts": {
"dev": "npm run build && node dist/worker.js",
"start": "node dist/worker.js",
"build": "tsc -p tsconfig.json",
"check": "tsc -p tsconfig.json --noEmit",
"test": "vitest run"
},
"dependencies": {
"@space/codex-app-server": "0.2.5",
"@space/contracts": "0.2.5",
"@space/db": "0.2.5",
"@space/memory-graph": "0.2.5",
"@space/opencode-control": "0.2.5",
"@space/streaming": "0.2.5",
"@temporalio/activity": "1.24.0",
"@temporalio/client": "1.24.0",
"@temporalio/worker": "1.24.0",
"@temporalio/workflow": "1.24.0",
"undici": "8.12.3"
},
"devDependencies": {
"typescript": "^5.9.3",
"vitest": "^4.1.12"
},
"license": "MIT"
}
{
"name": "@space/worker",
"version": "0.2.5",
"private": true,
"type": "module",
"main": "dist/worker.js",
"types": "dist/worker.d.ts",
"scripts": {
"dev": "npm run build && node dist/worker.js",
"start": "node dist/worker.js",
"build": "tsc -p tsconfig.json",
"check": "tsc -p tsconfig.json --noEmit",
"test": "vitest run",
"temporal:record-histories": "node tests/support/record-workflow-histories.mjs"
},
"dependencies": {
"@space/codex-app-server": "0.2.5",
"@space/contracts": "0.2.5",
"@space/db": "0.2.5",
"@space/memory-graph": "0.2.5",
"@space/opencode-control": "0.2.5",
"@space/streaming": "0.2.5",
"@temporalio/activity": "1.24.0",
"@temporalio/client": "1.24.0",
"@temporalio/worker": "1.24.0",
"@temporalio/workflow": "1.24.0",
"undici": "8.12.3"
},
"devDependencies": {
"@temporalio/testing": "1.24.0",
"typescript": "^5.9.3",
"vitest": "^4.1.12"
},
"license": "MIT"
}
48 changes: 48 additions & 0 deletions apps/worker/tests/fixtures/workflow-histories/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Synthetic workflow replay baselines

These histories come from the production workflow code running on
`TestWorkflowEnvironment.createTimeSkipping()` with stubbed activities. They
contain fictional rooms, turns, goals and memory records. Worker identities and
failure stack traces are replaced with synthetic values before serialization.
No installed SpaceApp, database, provider account or production history is used.

Run the suite after the internal packages have been built (`npm run check`):

```sh
npm test -w @space/worker
```

The SDK downloads and caches its test-server binary on first use; subsequent
runs use that executable. Workflows and activities do not call external services.
The tests run serially because virtual time is global to each test server. The
250-poll scenarios use dedicated servers to isolate their clock locks.

## Recording

```sh
npm run temporal:record-histories -w @space/worker
```

Ordinary tests only read the committed baselines. The explicit recording command
runs the same scenarios and writes protobuf JSON using `History.toObject` with
string longs/base64 bytes, then the replay test decodes with `History.fromObject`.
This avoids dependency on protobuf reflection across the test runner's module
boundaries. Each workflow has a baseline; additional files cover wake-up,
timeout, continue-as-new, bounded carried state, action failure, and both
supervisor branches.

`tests/support/prepatch-workflows.ts` freezes the pre-continuation supervisor
implementation so its histories contain no `room-background-continuation-v1`
marker. The replay gate always uses **current production** workflow code for
these histories. The historical entrypoint is never registered in production.
The negative entrypoint inserts a timer before the first goal activity and must
produce `DeterminismViolationError` against the committed goal history.

Re-recording is allowed only together with a reviewed, replay-safe `patched()`
workflow change. Keep the old histories for the old patch branch and add new
histories for the new branch. Never replace a failing baseline merely to make
replay pass, and never edit recorded event order, activity arguments or patch
markers by hand. Initial baseline creation does not change workflow behavior.

The tests are discovered by the existing worker `vitest run` script and thus by
`npm run test:public` and the public CI workflow.
Loading