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
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,30 @@ jobs:
- name: Build web
run: pnpm --filter @different-ai/openwork build:web

build-den-control-plane:
name: Build Den control plane
runs-on: ubuntu-latest

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

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.27.0

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build den control plane
run: pnpm --filter @openwork/den-control-plane build

build-orchestrator-binary:
name: Build openwork orchestrator binary
runs-on: ubuntu-latest
Expand Down
131 changes: 131 additions & 0 deletions .github/workflows/deploy-den-control-plane.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
name: Deploy Den Control Plane

on:
push:
branches:
- dev
paths:
- "services/den-control-plane/**"
- "services/den-worker-runtime/**"
- ".github/workflows/deploy-den-control-plane.yml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: deploy-den-control-plane-${{ github.ref }}
cancel-in-progress: true

jobs:
deploy:
runs-on: ubuntu-latest
if: github.repository == 'different-ai/openwork'
steps:
- name: Validate required secrets
env:
RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }}
RENDER_DEN_CONTROL_PLANE_SERVICE_ID: ${{ secrets.RENDER_DEN_CONTROL_PLANE_SERVICE_ID }}
RENDER_OWNER_ID: ${{ secrets.RENDER_OWNER_ID }}
DEN_DATABASE_URL: ${{ secrets.DEN_DATABASE_URL }}
DEN_BETTER_AUTH_SECRET: ${{ secrets.DEN_BETTER_AUTH_SECRET }}
run: |
missing=0
for key in RENDER_API_KEY RENDER_DEN_CONTROL_PLANE_SERVICE_ID RENDER_OWNER_ID DEN_DATABASE_URL DEN_BETTER_AUTH_SECRET; do
if [ -z "${!key}" ]; then
echo "::error::Missing required secret: $key"
missing=1
fi
done
if [ "$missing" -ne 0 ]; then
exit 1
fi

- name: Sync Render env vars and deploy latest commit
env:
RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }}
RENDER_DEN_CONTROL_PLANE_SERVICE_ID: ${{ secrets.RENDER_DEN_CONTROL_PLANE_SERVICE_ID }}
RENDER_OWNER_ID: ${{ secrets.RENDER_OWNER_ID }}
DEN_DATABASE_URL: ${{ secrets.DEN_DATABASE_URL }}
DEN_BETTER_AUTH_SECRET: ${{ secrets.DEN_BETTER_AUTH_SECRET }}
DEN_RENDER_WORKER_OPENWORK_VERSION: ${{ vars.DEN_RENDER_WORKER_OPENWORK_VERSION }}
run: |
python3 <<'PY'
import json
import os
import time
import urllib.error
import urllib.request

api_key = os.environ["RENDER_API_KEY"]
service_id = os.environ["RENDER_DEN_CONTROL_PLANE_SERVICE_ID"]
owner_id = os.environ["RENDER_OWNER_ID"]
openwork_version = os.environ.get("DEN_RENDER_WORKER_OPENWORK_VERSION") or "0.11.113"

headers = {
"Authorization": f"Bearer {api_key}",
"Accept": "application/json",
"Content-Type": "application/json",
}

def request(method: str, path: str, body=None):
url = f"https://api.render.com/v1{path}"
data = None
if body is not None:
data = json.dumps(body).encode("utf-8")
req = urllib.request.Request(url, data=data, method=method, headers=headers)
try:
with urllib.request.urlopen(req, timeout=60) as resp:
text = resp.read().decode("utf-8")
return resp.status, json.loads(text) if text else None
except urllib.error.HTTPError as err:
text = err.read().decode("utf-8", "replace")
raise RuntimeError(f"{method} {path} failed ({err.code}): {text[:600]}")

_, service = request("GET", f"/services/{service_id}")
service_url = (service.get("serviceDetails") or {}).get("url")
if not service_url:
raise RuntimeError(f"Render service {service_id} has no public URL")

env_vars = [
{"key": "DATABASE_URL", "value": os.environ["DEN_DATABASE_URL"]},
{"key": "BETTER_AUTH_SECRET", "value": os.environ["DEN_BETTER_AUTH_SECRET"]},
{"key": "BETTER_AUTH_URL", "value": service_url},
{"key": "PROVISIONER_MODE", "value": "render"},
{"key": "RENDER_API_BASE", "value": "https://api.render.com/v1"},
{"key": "RENDER_API_KEY", "value": api_key},
{"key": "RENDER_OWNER_ID", "value": owner_id},
{"key": "RENDER_WORKER_REPO", "value": "https://github.com/different-ai/openwork"},
{"key": "RENDER_WORKER_BRANCH", "value": "dev"},
{"key": "RENDER_WORKER_ROOT_DIR", "value": "services/den-worker-runtime"},
{"key": "RENDER_WORKER_PLAN", "value": "starter"},
{"key": "RENDER_WORKER_REGION", "value": "oregon"},
{"key": "RENDER_WORKER_OPENWORK_VERSION", "value": openwork_version},
{"key": "RENDER_WORKER_NAME_PREFIX", "value": "den-worker-openwork"},
{"key": "RENDER_PROVISION_TIMEOUT_MS", "value": "900000"},
{"key": "RENDER_HEALTHCHECK_TIMEOUT_MS", "value": "180000"},
{"key": "RENDER_POLL_INTERVAL_MS", "value": "5000"},
]

request("PUT", f"/services/{service_id}/env-vars", env_vars)
_, deploy = request("POST", f"/services/{service_id}/deploys", {})
deploy_id = deploy.get("id") or (deploy.get("deploy") or {}).get("id")
if not deploy_id:
raise RuntimeError(f"Unexpected deploy response: {deploy}")

terminal = {"live", "update_failed", "build_failed", "canceled"}
started = time.time()

while time.time() - started < 1800:
_, deploys = request("GET", f"/services/{service_id}/deploys?limit=1")
latest = deploys[0]["deploy"] if deploys else None
if latest and latest.get("id") == deploy_id and latest.get("status") in terminal:
status = latest.get("status")
if status != "live":
raise RuntimeError(f"Render deploy {deploy_id} ended with {status}")
print(f"Render deploy {deploy_id} is live at {service_url}")
break
time.sleep(10)
else:
raise RuntimeError(f"Timed out waiting for deploy {deploy_id}")
PY
1 change: 1 addition & 0 deletions evidence/den-e2e-openwork/07-worker-health.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"ok": true, "version": "0.11.113", "uptimeMs": 21378}
1 change: 1 addition & 0 deletions evidence/den-e2e-openwork/08-web-worker-health.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"ok":true,"version":"0.11.113","uptimeMs":43538}
34 changes: 34 additions & 0 deletions evidence/den-e2e-openwork/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# OpenWork Repo Non-Stub Render E2E Proof

This directory captures end-to-end proof for the OpenWork-repo implementation of the Den control plane and worker provisioning flow.

## Control-plane deployment proof

- `render-control-plane.json`

Includes service id, repo/branch/rootDir, public URL, and latest live deploy metadata.

## API/CLI auth + worker provisioning flow

- `report.json`
- `07-worker-health.json`

`report.json` shows:

- `POST /api/auth/sign-up/email` succeeds (`status: 200`)
- `GET /v1/me` via cookie session succeeds (`status: 200`)
- `GET /v1/me` via bearer token succeeds (`status: 200`)
- `POST /v1/workers` succeeds (`status: 201`)
- created instance is real Render (`instance.provider: "render"`)
- returned worker health endpoint succeeds (`worker_health.status: 200`)

Sensitive token/session fields are redacted.

## Web flow proof (Chrome MCP)

- `webapp/01-web-signup-form.png`
- `webapp/02-web-signup-success.png`
- `webapp/03-web-worker-created-render.png`
- `08-web-worker-health.json`

`03-web-worker-created-render.png` shows successful cloud worker creation with Render provider and healthy status, with tokens redacted in UI output.
24 changes: 24 additions & 0 deletions evidence/den-e2e-openwork/render-control-plane.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"service": {
"id": "srv-d6ck5utm5p6s73er2av0",
"name": "den-control-plane-openwork-1771651835",
"repo": "https://github.com/different-ai/openwork",
"branch": "feat/den-cloud-worker-openwork-repo",
"rootDir": "services/den-control-plane",
"url": "https://den-control-plane-openwork-1771651835.onrender.com"
},
"latestDeploy": {
"id": "dep-d6ckfgjh46gs73cjgq9g",
"commit": {
"id": "9ce8ec57806dfff085f49946b228bec8e37ad40c",
"message": "fix: redact sensitive tokens in demo output",
"createdAt": "2026-02-21T05:50:51Z"
},
"status": "live",
"trigger": "api",
"createdAt": "2026-02-21T05:50:58.801414Z",
"updatedAt": "2026-02-21T05:53:34.758793Z",
"startedAt": "2026-02-21T05:50:58.770331Z",
"finishedAt": "2026-02-21T05:53:02.333454Z"
}
}
127 changes: 127 additions & 0 deletions evidence/den-e2e-openwork/report.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{
"signup": {
"status": 200,
"body": {
"token": "REDACTED_SESSION_TOKEN",
"user": {
"name": "Den OpenWork",
"email": "den-openwork-1771653211@example.com",
"emailVerified": false,
"image": null,
"createdAt": "2026-02-21T05:53:32.550Z",
"updatedAt": "2026-02-21T05:53:32.550Z",
"id": "jhaaa6Gw9nqIL0KMdRicskHnYlCuBMNI"
}
}
},
"bearerTokenPresent": true,
"me_cookie": {
"status": 200,
"body": {
"session": {
"expiresAt": "2026-02-28T05:53:32.639Z",
"token": "REDACTED_SESSION_TOKEN",
"createdAt": "2026-02-21T05:53:32.639Z",
"updatedAt": "2026-02-21T05:53:32.639Z",
"ipAddress": "REDACTED_IP",
"userAgent": "Python-urllib/3.9",
"userId": "jhaaa6Gw9nqIL0KMdRicskHnYlCuBMNI",
"id": "REDACTED_SESSION_ID"
},
"user": {
"name": "Den OpenWork",
"email": "den-openwork-1771653211@example.com",
"emailVerified": false,
"image": null,
"createdAt": "2026-02-21T05:53:32.550Z",
"updatedAt": "2026-02-21T05:53:32.550Z",
"id": "jhaaa6Gw9nqIL0KMdRicskHnYlCuBMNI"
}
}
},
"me_bearer": {
"status": 200,
"body": {
"session": {
"expiresAt": "2026-02-28T05:53:32.639Z",
"token": "REDACTED_SESSION_TOKEN",
"createdAt": "2026-02-21T05:53:32.639Z",
"updatedAt": "2026-02-21T05:53:32.639Z",
"ipAddress": "REDACTED_IP",
"userAgent": "Python-urllib/3.9",
"userId": "jhaaa6Gw9nqIL0KMdRicskHnYlCuBMNI",
"id": "REDACTED_SESSION_ID"
},
"user": {
"name": "Den OpenWork",
"email": "den-openwork-1771653211@example.com",
"emailVerified": false,
"image": null,
"createdAt": "2026-02-21T05:53:32.550Z",
"updatedAt": "2026-02-21T05:53:32.550Z",
"id": "jhaaa6Gw9nqIL0KMdRicskHnYlCuBMNI"
}
}
},
"create_worker": {
"status": 201,
"body": {
"worker": {
"id": "92500aab-e73a-437e-96a6-13210a555c39",
"orgId": "6187652b-cd56-49c5-82a3-78f3c4731b41",
"name": "openwork-nonstub-1771653211",
"description": "openwork repo render e2e worker",
"destination": "cloud",
"status": "healthy",
"imageVersion": "den-worker-v1",
"workspacePath": null,
"sandboxBackend": null
},
"tokens": {
"host": "REDACTED_HOST_TOKEN",
"client": "REDACTED_CLIENT_TOKEN"
},
"instance": {
"provider": "render",
"url": "https://den-worker-openwork-openwork-nonstub-wnpt.onrender.com",
"status": "healthy",
"region": "oregon"
}
},
"payload": {
"name": "openwork-nonstub-1771653211",
"description": "openwork repo render e2e worker",
"destination": "cloud",
"imageVersion": "den-worker-v1"
}
},
"worker_url": "https://den-worker-openwork-openwork-nonstub-wnpt.onrender.com",
"worker_id": "92500aab-e73a-437e-96a6-13210a555c39",
"get_worker": {
"status": 200,
"body": {
"worker": {
"id": "92500aab-e73a-437e-96a6-13210a555c39",
"orgId": "6187652b-cd56-49c5-82a3-78f3c4731b41",
"name": "openwork-nonstub-1771653211",
"description": "openwork repo render e2e worker",
"destination": "cloud",
"status": "healthy",
"imageVersion": "den-worker-v1",
"workspacePath": null,
"sandboxBackend": null,
"createdAt": "2026-02-21T05:53:33.000Z",
"updatedAt": "2026-02-21T05:55:00.546Z"
}
}
},
"worker_health": {
"status": 200,
"body": {
"ok": true,
"version": "0.11.113",
"uptimeMs": 21378
},
"url": "https://den-worker-openwork-openwork-nonstub-wnpt.onrender.com/health"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading