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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .agents/skills/dex-developer/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
name: dex-developer
description: Develop, debug, test, and operate this Superdurable Dex application.
---

# Dex Developer

Read `upstream/plugins/dex/skills/dex-developer/SKILL.md` completely and follow
it as the authoritative Dex application-development skill. Resolve every
relative reference from that upstream skill's directory.
1 change: 1 addition & 0 deletions .agents/skills/dex-developer/upstream
Submodule upstream added at 3ccc47
50 changes: 50 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Template CI

on:
pull_request:
push:
branches: [main]

jobs:
check:
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache-dependency-path: |
go.sum
tools/openapi/go.sum
- uses: actions/setup-node@v4
with:
node-version: 22.22.0
cache: npm
cache-dependency-path: web/package-lock.json
- name: Install Temporal CLI 1.8.1
run: |
curl --fail --silent --show-error --location \
https://github.com/temporalio/cli/releases/download/v1.8.1/temporal_cli_1.8.1_linux_amd64.tar.gz \
--output /tmp/temporal-cli.tar.gz
echo "b94417b9a8760b30217f4b881dabce4b16a76a38b5e99e2eca3ce358b8030f06 /tmp/temporal-cli.tar.gz" | sha256sum --check
mkdir -p "$HOME/.local/bin"
tar -xzf /tmp/temporal-cli.tar.gz -C "$HOME/.local/bin" temporal
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
"$HOME/.local/bin/temporal" --version
- name: Install dexcli
run: |
curl --fail --silent --show-error --location \
https://github.com/superdurable/dex/releases/download/cli-v0.10.0/dexcli_v0.10.0_linux_amd64.tar.gz \
--output /tmp/dexcli.tar.gz
echo "0cee3b0795147b581c45d2258b0c2d581cd35ce75c515027e2d9b294ce364e2d /tmp/dexcli.tar.gz" | sha256sum --check
mkdir -p "$HOME/.local/bin"
tar -xzf /tmp/dexcli.tar.gz -C "$HOME/.local/bin" dexcli
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Install Playwright Chromium
working-directory: web
run: npx playwright install --with-deps chromium
- name: Check template
run: make check
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/bin/
/tmp/
/.local/
/web/dist/
/web/node_modules/
/web/playwright-report/
/web/test-results/
*.log
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule ".agents/skills/dex-developer/upstream"]
path = .agents/skills/dex-developer/upstream
url = https://github.com/superdurable/skill-dex-developer.git
20 changes: 20 additions & 0 deletions .superverse/template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"schemaVersion": 1,
"buildProfile": "go-react-v1",
"templateVersion": "1.0.0",
"minimumSandboxRuntimeContractRevision": 2,
"openapiSpec": "openapi/openapi.yaml",
"agentInstructions": "AGENTS.md",
"dexSkill": ".agents/skills/dex-developer/SKILL.md",
"commands": {
"bootstrap": "make bootstrap",
"generate": "make generate",
"checkGenerated": "make check-generated",
"testUnit": "make test-unit",
"testIntegration": "make test-integration",
"testE2E": "make test-e2e",
"build": "make build",
"dev": "make dev",
"check": "make check"
}
}
29 changes: 29 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Basic Process Template Instructions

This is a complete Superverse `go-react-v1` application. Read
`.superverse/template.json`, `openapi/openapi.yaml`, and the local
`dex-developer` skill before changing Dex behavior.

`openapi/openapi.yaml` is the only HTTP contract source. Never edit files below
`internal/api/generated` or `web/src/api/generated` by hand. Change the spec,
run `make generate`, and update server, UI, integration, and E2E coverage in the
same change.

The Dex Flow has stable Step, Attribute, Channel, Timer, and RPC identities.
Keep external effects in `Execute`; `WaitForApproval.WaitFor` only declares the
approval Channel and reminder Timer. Register every durable primitive in the
Flow persistence schema. Preserve open-Flow compatibility unless the user
explicitly requests a migration.

After each edit batch, run the narrowest relevant Make target. Before calling
`commit_and_push`, run `make check` successfully and include it in verification.
If `make check` fails or cannot run, report `blocked=true`. Do not weaken, skip,
or delete a failing check.

Stable commands are `make bootstrap`, `make generate`, `make check-generated`,
`make test-unit`, `make test-integration`, `make test-e2e`, `make build`,
`make dev`, and `make check`.

When structure, commands, or required tooling changes, update this file,
`.superverse/template.json`, `README.md`, and contract tests together. Do not
maintain a separate static repository map.
32 changes: 32 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
SHELL := /usr/bin/env bash

.PHONY: bootstrap generate check-generated test-unit test-integration test-e2e build dev check
bootstrap:
go mod download
go -C tools/openapi mod download
npm --prefix web ci
generate:
go -C tools/openapi tool ogen --target ../../internal/api/generated --package generated ../../openapi/openapi.yaml
npm --prefix web run generate
check-generated:
./scripts/check-generated.sh
test-unit:
go test ./...
npm --prefix web test
test-integration:
./scripts/with-dex.sh go test -tags=integration ./...
test-e2e:
./scripts/with-dex.sh ./scripts/run-e2e.sh
build:
npm --prefix web run build
go build -o bin/basic-process ./cmd/server
dev:
./scripts/with-dex.sh bash -c 'npm --prefix web run build && go run ./cmd/server'
check: bootstrap check-generated
@test -z "$$(gofmt -l $$(find . -name '*.go' -not -path './.agents/*'))" || { gofmt -d $$(gofmt -l $$(find . -name '*.go' -not -path './.agents/*')); exit 1; }
go mod tidy -diff
go vet ./...
$(MAKE) test-unit
$(MAKE) test-integration
$(MAKE) test-e2e
$(MAKE) build
77 changes: 75 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,75 @@
# dex-template-basic-process
The most basic dex application as a basic automation process with human approval and reminder
# Dex Basic Process Template

A complete Superverse `go-react-v1` template for a durable approval automation.
The Go backend and React TypeScript UI share one OpenAPI contract. The Dex Flow
validates a request, waits durably for approval, emits recurring reminders, runs
the approved automation, and completes with a typed result.

The durable lifecycle is:

1. `StartProcess`
2. `ValidateRequest`
3. `WaitForApproval`
4. `EmitReminder`
5. `ExecuteApprovedAutomation`
6. `CompleteProcess`

`WaitForApproval` races an Approval Channel against a durable 15-minute Timer.
When the Timer fires, `EmitReminder` increments the durable reminder count and
returns to the waiting step. Approval advances to the execution and completion
steps.

## Start locally

```bash
make bootstrap
make dev
```

Open <http://127.0.0.1:8080>. `make dev` owns a local `dexcli dev` process and
cleans it up on exit.

## Contract and generated code

`openapi/openapi.yaml` is authoritative. Ogen creates the Go server contract in
`internal/api/generated`; Hey API creates the TypeScript client in
`web/src/api/generated`.

```bash
make generate
make check-generated
```

Generated files are committed so a checkout is immediately understandable.
Never edit them manually.

## Verification

```bash
make test-unit
make test-integration
make test-e2e
make check
```

Integration tests start a real Dex Server with `dexcli dev`. Playwright drives
the production UI and uses `dexcli flow skip-timer` to exercise the reminder
branch without waiting fifteen minutes. Every poll has a deadline.

`make check` is the required completion gate for coding agents and CI.

The supported sandbox runtime is contract revision 2. It provides Go, Node.js,
npm, Python 3, `dexcli`, Ogen's cached module dependencies, and Chromium
Headless Shell. JavaScript packages remain pinned by `web/package-lock.json`.

## Dex skill

The local skill entry delegates to the pinned public
`skill-dex-developer` submodule. Initialize it with:

```bash
git submodule update --init --recursive
```

Template maintainers update the pin explicitly; generated applications never
follow the skill repository's `main` branch implicitly.
85 changes: 85 additions & 0 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package main

import (
"context"
"errors"
"fmt"
"log/slog"
"net/http"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"

api "github.com/superdurable/dex-template-basic-process/internal/api"
appRuntime "github.com/superdurable/dex-template-basic-process/internal/runtime"
)

func main() {
if err := run(); err != nil {
slog.Error("application stopped", "error", err)
os.Exit(1)
}
}

func run() error {
logger := slog.New(slog.NewJSONHandler(os.Stderr, nil))
runtime, err := appRuntime.New(logger)
if err != nil {
return err
}
defer runtime.Close()
apiHandler, err := api.NewHandler(runtime.Processes)
if err != nil {
return fmt.Errorf("create OpenAPI handler: %w", err)
}
mux := http.NewServeMux()
mux.Handle("/api/", apiHandler)
mux.Handle("/", staticHandler("web/dist"))
server := &http.Server{Addr: ":" + environment("PORT", "8080"), Handler: mux, ReadHeaderTimeout: 5 * time.Second}
workerResult := runtime.StartWorker()
serverResult := make(chan error, 1)
go func() { serverResult <- server.ListenAndServe() }()
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
select {
case <-ctx.Done():
case err := <-workerResult:
if err != nil {
return fmt.Errorf("run Dex Worker: %w", err)
}
case err := <-serverResult:
if err != nil && !errors.Is(err, http.ErrServerClosed) {
return fmt.Errorf("run HTTP server: %w", err)
}
}
shutdown, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
return server.Shutdown(shutdown)
}

func staticHandler(root string) http.Handler {
files := http.FileServer(http.Dir(root))
return http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) {
if request.Method != http.MethodGet && request.Method != http.MethodHead {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
asset := filepath.Join(root, filepath.Clean(request.URL.Path))
if request.URL.Path != "/" {
if info, err := os.Stat(asset); err == nil && !info.IsDir() {
files.ServeHTTP(w, request)
return
}
}
http.ServeFile(w, request, filepath.Join(root, "index.html"))
})
}

func environment(name, fallback string) string {
if value := os.Getenv(name); value != "" {
return value
}
return fallback
}
43 changes: 43 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module github.com/superdurable/dex-template-basic-process

go 1.25.0

require (
github.com/go-faster/errors v0.8.0
github.com/go-faster/jx v1.2.0
github.com/google/uuid v1.6.0
github.com/ogen-go/ogen v1.24.0
github.com/superdurable/dex/blob-cache-go v0.1.0
github.com/superdurable/dex/sdk-go v0.10.0
go.opentelemetry.io/otel v1.46.0
go.opentelemetry.io/otel/metric v1.46.0
go.opentelemetry.io/otel/trace v1.46.0
)

require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgraph-io/ristretto/v2 v2.4.2 // indirect
github.com/dlclark/regexp2 v1.12.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-faster/yaml v0.4.6 // indirect
github.com/go-logr/logr v1.4.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.24 // indirect
github.com/segmentio/asm v1.2.1 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.28.0 // indirect
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
golang.org/x/net v0.57.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/text v0.40.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
Loading
Loading