Compare commits

...

5 Commits

Author SHA1 Message Date
claude-ceo-assistant 9d50a6dae4 feat(local-dev): air-based hot-reload for workspace-server
Closes core#116. Brings local-dev iteration parity with the canvas's
Turbopack HMR — edit a Go file, see the platform restart in <5s
instead of running 'docker compose up --build' (~30s) per change.

USAGE
  make dev   # docker compose with air-driven live reload
  make up    # production-shape stack (no air, normal Dockerfile)

WHAT THIS ADDS
  workspace-server/.air.toml      — air watch config
  workspace-server/Dockerfile.dev — air-on-golang:1.25-alpine, dev-only
  docker-compose.dev.yml          — overlay swapping platform service
                                    to Dockerfile.dev + bind-mounting
                                    workspace-server/ source
  Makefile                        — make {dev,up,down,logs,build,test}

WHAT THIS DOES NOT TOUCH
  workspace-server/Dockerfile (production multi-stage build)
  docker-compose.yml          (prod-shape stack)
  CI workflows                (build prod image directly)
  Tenant deployment / SaaS    (image swap stays the model)

Pure additive. Existing 'docker compose up' path unchanged; production
stays on the static binary. Air install pinned via go install at image
build time so the dev image is reproducible-enough for local use (we
don't pin air to a SHA — the dev image is rebuilt locally and updates
opportunistically).

PHASE 4 SELF-REVIEW (FIVE-AXIS)
  Correctness: No finding — additive change, no existing path modified.
    .air.toml watches .go + .yaml under workspace-server/, excludes
    _test.go and tests dir so test edits don't trigger rebuild.
    Dockerfile.dev mirrors prod's 'go mod download' so first rebuild
    is fast.
  Readability: No finding — three small files plus a Makefile, each
    with header comments explaining the WHY, not just the WHAT. The
    Makefile uses the standard ## help-target pattern.
  Architecture: No finding — overlay pattern (docker-compose.dev.yml
    on top of docker-compose.yml) is the standard compose convention
    for env-specific overrides. Doesn't fork the prod path.
  Security: No finding because no production code path; dev-only image
    isn't built in CI and isn't published to ECR.
  Performance: No finding — air debounce=500ms, exclude_unchanged=true
    so a save that doesn't change content is a no-op rebuild.

REFS
  core#116 — this issue
  Companion: core#117 (workspace-side config-watcher for hot-reload of
  config.yaml) — different scope; this issue is platform-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 08:10:50 -07:00
claude-ceo-assistant ae2d9eabf6 Merge pull request 'chore(workflows): drop staging-branch triggers (Phase 3b of internal#81)' (#109) from chore/trunk-based-drop-staging-from-workflow-triggers into main 2026-05-08 13:30:24 +00:00
claude-ceo-assistant 2fac4b61b4 chore(workflows): drop staging-branch triggers (Phase 3b of internal#81)
Trunk-based migration: main is the only branch. Update 4 workflows
that fired on staging-branch pushes to fire on main instead.

  - e2e-staging-canvas.yml: drop staging from push + pull_request
  - e2e-staging-external.yml: drop staging from push + pull_request
  - e2e-staging-saas.yml: drop staging from push + pull_request,
    update header comment that references the (now-obsolete)
    staging→main auto-promote flow
  - redeploy-tenants-on-staging.yml: workflow_run.branches changes
    from [staging] to [main] so the tenant redeploy fires when
    publish-workspace-server-image runs on main

Workflows that target the staging tenant FLEET (canary-staging.yml,
e2e-staging-sanity.yml) are not changed — they fire on cron, the word
"staging" in their filenames refers to the deployment target environ-
ment, not the git branch.

Lands as Phase 3b after #108 promotes the 5 staging-only feature PRs
(Phase 3a). Phase 3c deletes the obsolete promote/sync workflows
(auto-promote-staging, auto-sync-main-to-staging, etc.) plus the
staging branch itself, after we no-op-verify both Phase 3a and 3b
green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 13:08:51 +00:00
claude-ceo-assistant 8798b316f6 chore: promote accumulated staging fixes to main (vitest, postgres, e2e-api, eic, plugins) (#99)
Brings staging tip b11044f8 to main. Includes #97 (vitest) + #98 (handlers-postgres) + #100 (e2e-api) + EIC race fix + #84 (SaaS plugin EIC) + accumulated staging commits. Triggers Vercel + Railway + ECR production deploy chain. Approved by security-auditor.
2026-05-08 02:46:39 +00:00
claude-ceo-assistant b11044f885 fix(plugins): SaaS (EC2-per-workspace) install/uninstall via EIC SSH (#84)
Closes docker-only row in backends.md. Approved by security-auditor.
2026-05-08 02:15:49 +00:00
8 changed files with 169 additions and 12 deletions
+2 -2
View File
@@ -22,9 +22,9 @@ on:
# spending CI cycles. See e2e-api.yml for the rationale on why this
# is a single job rather than two-jobs-sharing-name.
push:
branches: [main, staging]
branches: [main]
pull_request:
branches: [main, staging]
branches: [main]
workflow_dispatch:
schedule:
# Weekly on Sunday 08:00 UTC — catches Chrome / Playwright / Next.js
+2 -2
View File
@@ -32,7 +32,7 @@ name: E2E Staging External Runtime
on:
push:
branches: [staging, main]
branches: [main]
paths:
- 'workspace-server/internal/handlers/workspace.go'
- 'workspace-server/internal/handlers/registry.go'
@@ -44,7 +44,7 @@ on:
- 'tests/e2e/test_staging_external_runtime.sh'
- '.github/workflows/e2e-staging-external.yml'
pull_request:
branches: [staging, main]
branches: [main]
paths:
- 'workspace-server/internal/handlers/workspace.go'
- 'workspace-server/internal/handlers/registry.go'
+6 -7
View File
@@ -20,13 +20,12 @@ name: E2E Staging SaaS (full lifecycle)
# via the same paths watcher that e2e-api.yml uses)
on:
# Fire on staging push too — previously this only ran on main, which
# meant the most thorough end-to-end test caught regressions AFTER
# they shipped to staging (and then to the auto-promote PR). Running
# on staging push catches them BEFORE the staging→main promotion
# opens, so a green canary into auto-promote is more meaningful.
# Trunk-based (Phase 3 of internal#81): main is the only branch.
# Previously this fired on staging push too because staging was a
# superset of main and ran the gate ahead of auto-promote; with no
# staging branch, main is where E2E gates the deploy.
push:
branches: [staging, main]
branches: [main]
paths:
- 'workspace-server/internal/handlers/registry.go'
- 'workspace-server/internal/handlers/workspace_provision.go'
@@ -36,7 +35,7 @@ on:
- 'tests/e2e/test_staging_full_saas.sh'
- '.github/workflows/e2e-staging-saas.yml'
pull_request:
branches: [staging, main]
branches: [main]
paths:
- 'workspace-server/internal/handlers/registry.go'
- 'workspace-server/internal/handlers/workspace_provision.go'
@@ -36,7 +36,7 @@ on:
workflow_run:
workflows: ['publish-workspace-server-image']
types: [completed]
branches: [staging]
branches: [main]
workflow_dispatch:
inputs:
target_tag:
+28
View File
@@ -0,0 +1,28 @@
# Top-level Makefile — convenience wrappers around docker compose.
#
# Most molecule-core dev work happens via these shortcuts. CI doesn't
# use this Makefile; CI calls docker compose / go test directly so the
# Makefile can evolve without breaking the build.
.PHONY: help dev up down logs build test
help: ## Show this help.
@grep -E '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-12s\033[0m %s\n", $$1, $$2}'
dev: ## Start the full stack with air hot-reload for the platform service.
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
up: ## Start the full stack in production-shape mode (no air, normal Dockerfile).
docker compose up
down: ## Stop the stack and remove containers (volumes preserved).
docker compose down
logs: ## Tail logs from all services (Ctrl-C to detach).
docker compose logs -f
build: ## Force a fresh build of the platform image (no cache).
docker compose build --no-cache platform
test: ## Run Go unit tests in workspace-server/.
cd workspace-server && go test -race ./...
+43
View File
@@ -0,0 +1,43 @@
# docker-compose.dev.yml — overlay over docker-compose.yml for local dev
# with air-driven live reload of the platform (workspace-server) service.
#
# Usage:
# docker compose -f docker-compose.yml -f docker-compose.dev.yml up
# (or `make dev` shorthand from repo root)
#
# What this overlay changes vs docker-compose.yml alone:
# - Platform service uses workspace-server/Dockerfile.dev (air on top of
# golang:1.25-alpine) instead of the multi-stage prod Dockerfile.
# - Platform service bind-mounts the host's workspace-server/ source
# into /app/workspace-server so air sees source edits live.
# - Other services (postgres, redis, langfuse, etc.) inherit unchanged
# from docker-compose.yml.
#
# What stays the same:
# - All env vars, volumes, depends_on, healthchecks from docker-compose.yml.
# - Network topology + ports.
# - Postgres/Redis as service containers (no in-process replacements).
services:
platform:
build:
context: .
dockerfile: workspace-server/Dockerfile.dev
# Rebind source: edits under host's workspace-server/ propagate live.
# The named volume on go-build-cache speeds up first build per container.
volumes:
- ./workspace-server:/app/workspace-server
- go-build-cache:/root/.cache/go-build
- go-mod-cache:/go/pkg/mod
# Air signals the running binary on rebuild; ensure shell stops cleanly.
init: true
# Mark the service as dev-mode so the platform can short-circuit any
# behavior that's incompatible with hot-reload (e.g. background
# cron-style watchers that don't survive process restart). No-op
# today; reserved for future flag use.
environment:
MOLECULE_DEV_HOT_RELOAD: "1"
volumes:
go-build-cache:
go-mod-cache:
+49
View File
@@ -0,0 +1,49 @@
# air.toml — live-reload config for local docker-compose dev mode.
#
# Active when the platform service runs from workspace-server/Dockerfile.dev
# (selected via docker-compose.dev.yml overlay). In production, the regular
# Dockerfile builds a static binary; air is dev-only.
#
# Reference: https://github.com/air-verse/air
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
# Same build invocation as Dockerfile's builder stage minus the
# CGO_ENABLED=0 toggle (CGO ok in dev for richer race detector output).
cmd = "go build -o ./tmp/server ./cmd/server"
bin = "tmp/server"
full_bin = ""
args_bin = []
# Watch every .go and .yaml file under workspace-server/.
include_ext = ["go", "yaml", "tmpl"]
# Don't watch tests, build artifacts, vendored deps, or migration .sql
# (migrations need a clean DB anyway — handled by docker-compose down/up).
exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules"]
exclude_file = []
# _test.go and *_mock.go shouldn't trigger a rebuild — saves cycles.
exclude_regex = ["_test\\.go$", "_mock\\.go$"]
exclude_unchanged = true
follow_symlink = false
log = "build-errors.log"
# Kill running binary 1s before starting new one.
kill_delay = "1s"
send_interrupt = true
stop_on_error = true
# Debounce: wait this long after last change before triggering rebuild.
delay = 500
[log]
time = false
[color]
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"
[misc]
# Don't keep the tmp/ dir around between runs.
clean_on_exit = true
+38
View File
@@ -0,0 +1,38 @@
# Dockerfile.dev — local-development image with air-driven live reload.
#
# Selected by docker-compose.dev.yml (overlay over docker-compose.yml).
# Production stays on workspace-server/Dockerfile (static binary, no air).
#
# Workflow:
# 1. docker compose -f docker-compose.yml -f docker-compose.dev.yml up
# 2. Edit any .go file under workspace-server/
# 3. air detects, rebuilds, kills old binary, starts new one (~3-5s)
# 4. No `docker compose up --build` needed
#
# Templates + plugins are NOT pre-cloned here — air-mode assumes the
# developer's filesystem has the workspace-configs-templates/ + plugins/
# dirs available, mounted at runtime via docker-compose.dev.yml.
FROM golang:1.25-alpine
# air + git (for go mod) + ca-certs (for TLS) + tzdata (for time-zone DB).
RUN apk add --no-cache git ca-certificates tzdata wget \
&& go install github.com/air-verse/air@latest
WORKDIR /app/workspace-server
# Pre-fetch deps so the first `air` rebuild on a fresh container is fast.
# These are bind-mount-overridden at runtime, so the COPY here is just
# to warm the module cache.
COPY workspace-server/go.mod workspace-server/go.sum ./
RUN go mod download
# Source is bind-mounted at runtime (see docker-compose.dev.yml volumes
# block) so the Dockerfile doesn't need to COPY it. air watches the
# bind-mounted dir for changes.
ENV CGO_ENABLED=1
ENV GOFLAGS="-buildvcs=false"
# Run air with the .air.toml in the bind-mounted source dir.
CMD ["air", "-c", ".air.toml"]