chore: final open-source cleanup — binary, stale paths, private refs
- Remove compiled workspace-server/server binary from git - Fix .gitignore, .gitattributes, .githooks/pre-commit for renamed dirs - Fix CI workflow path filters (workspace-template → workspace) - Replace real EC2 IP and personal slug in test_saas_tenant.sh - Scrub molecule-controlplane references in docs - Fix stale workspace-template/ paths in provisioner, handlers, tests - Clean tracked Python cache files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a79dec0a86
commit
39074cc4ae
4
.gitattributes
vendored
4
.gitattributes
vendored
@ -1,11 +1,11 @@
|
||||
# Shell scripts must stay LF so they execute inside Linux containers
|
||||
# when cloned on Windows with core.autocrlf=true.
|
||||
*.sh text eol=lf
|
||||
workspace-template/entrypoint.sh text eol=lf
|
||||
workspace/entrypoint.sh text eol=lf
|
||||
|
||||
# Python hook files are invoked by .sh hooks with path substitution;
|
||||
# CRLF in either the .sh OR the .py file breaks the hook dispatch.
|
||||
# See Molecule-AI/molecule-core#507 — SessionStart hook failed silently,
|
||||
# See #507 — SessionStart hook failed silently,
|
||||
# agents returned "(no response generated)" on every A2A call.
|
||||
*.py text eol=lf
|
||||
|
||||
|
||||
@ -64,7 +64,7 @@ fi
|
||||
# 3. Python: No bare except pass (silent swallowing)
|
||||
# ──────────────────────────────────────────────────────────
|
||||
|
||||
STAGED_PY=$(git diff --cached --name-only --diff-filter=ACM | grep '\.py$' | grep 'workspace-template/' || true)
|
||||
STAGED_PY=$(git diff --cached --name-only --diff-filter=ACM | grep '\.py$' | grep 'workspace/' || true)
|
||||
|
||||
if [ -n "$STAGED_PY" ]; then
|
||||
for f in $STAGED_PY; do
|
||||
@ -82,7 +82,7 @@ fi
|
||||
# 4. Go: No string-concatenated SQL
|
||||
# ──────────────────────────────────────────────────────────
|
||||
|
||||
STAGED_GO=$(git diff --cached --name-only --diff-filter=ACM | grep '\.go$' | grep 'platform/' || true)
|
||||
STAGED_GO=$(git diff --cached --name-only --diff-filter=ACM | grep '\.go$' | grep 'workspace-server/' || true)
|
||||
|
||||
if [ -n "$STAGED_GO" ]; then
|
||||
for f in $STAGED_GO; do
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -42,7 +42,7 @@ jobs:
|
||||
DIFF=$(git diff --name-only "$BASE" HEAD 2>/dev/null || echo ".github/workflows/ci.yml")
|
||||
echo "platform=$(echo "$DIFF" | grep -qE '^workspace-server/|^\.github/workflows/ci\.yml$' && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
||||
echo "canvas=$(echo "$DIFF" | grep -qE '^canvas/|^\.github/workflows/ci\.yml$' && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
||||
echo "python=$(echo "$DIFF" | grep -qE '^workspace-template/|^\.github/workflows/ci\.yml$' && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
||||
echo "python=$(echo "$DIFF" | grep -qE '^workspace/|^\.github/workflows/ci\.yml$' && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
||||
echo "scripts=$(echo "$DIFF" | grep -qE '^tests/e2e/|^scripts/|^\.github/workflows/ci\.yml$' && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
platform-build:
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,6 +1,6 @@
|
||||
# Binaries
|
||||
platform/server
|
||||
platform/molecli
|
||||
workspace-server/server
|
||||
workspace-server/molecli
|
||||
*.exe
|
||||
*.out
|
||||
*.bin
|
||||
@ -87,7 +87,7 @@ redis_data/
|
||||
workspace-configs-templates/ws-*
|
||||
|
||||
# Local dev cruft — provisioner writes here at runtime; templates live at repo root
|
||||
platform/workspace-configs-templates/
|
||||
workspace-server/workspace-configs-templates/
|
||||
|
||||
# Codex/Gemini agent skill cache (local only, not authoritative)
|
||||
.agents/
|
||||
|
||||
@ -218,10 +218,10 @@ continues to work as the primary flow (redirect after org creation).
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `molecule-controlplane/internal/provisioner/ec2.go` | Remove Cloudflare DNS creation, remove Caddy from user-data |
|
||||
| `molecule-controlplane/internal/cloudflareapi/dns.go` | Eventually removable (Worker replaces it) |
|
||||
| `molecule-controlplane/internal/handlers/orgs.go` | Add `GET /cp/orgs/:slug/instance` endpoint |
|
||||
| New: `infra/cloudflare-worker/` | Worker source + wrangler.toml |
|
||||
| `the private control-plane repo/internal/provisioner/ec2.go` | Remove Cloudflare DNS creation, remove Caddy from user-data |
|
||||
| `the private control-plane repo/internal/cloudflareapi/dns.go` | Eventually removable (Worker replaces it) |
|
||||
| `the private control-plane repo/internal/handlers/orgs.go` | Add `GET /cp/orgs/:slug/instance` endpoint |
|
||||
| New: `Molecule-AI/molecule-tenant-proxy (separate repo)` | Worker source + wrangler.toml |
|
||||
| `docs/runbooks/saas-secrets.md` | Add Worker secrets (CF account ID, API token) |
|
||||
| `.github/workflows/deploy-worker.yml` | CI/CD for Worker deploys |
|
||||
|
||||
|
||||
@ -485,10 +485,10 @@ Merge commit `57a05686`. Noteworthy: saas-foundation / auth-adjacent.
|
||||
allowlist-bypass, allowlist-is-exact-match.
|
||||
- CLAUDE.md: test count 740 → 746; new `MOLECULE_ORG_ID` env var documented.
|
||||
|
||||
### Paired work — private `molecule-controlplane` repo scaffolded
|
||||
### Paired work — private `the private control-plane repo` repo scaffolded
|
||||
(Outside this monorepo; logged here because it anchors the open-core split.)
|
||||
|
||||
- Initial commit `1bab493` on new private repo `Molecule-AI/molecule-controlplane`.
|
||||
- Initial commit `1bab493` on new private repo `Molecule-AI/the private control-plane repo`.
|
||||
- Migrations 001 (organizations), 002 (org_instances), 003 (org_members).
|
||||
- HTTP server: `/health`, `/cp/orgs` CRUD, subdomain + `X-Molecule-Org-Slug`
|
||||
header fallback → `fly-replay: app=<tenant>;instance=<machine_id>` header,
|
||||
|
||||
@ -21,7 +21,7 @@ Adds `.github/workflows/publish-platform-image.yml`:
|
||||
using the built-in `GITHUB_TOKEN`, no extra secrets.
|
||||
- OCI labels propagate source URL + commit SHA for provenance.
|
||||
|
||||
Purpose: pairs with the private `molecule-controlplane` Fly + Neon
|
||||
Purpose: pairs with the private `the private control-plane repo` Fly + Neon
|
||||
provisioner (PR #3 there, merged `2e85d5ad`) which reads
|
||||
`TENANT_IMAGE=ghcr.io/molecule-ai/platform:<tag>` from env and spawns
|
||||
each tenant Fly Machine from this image.
|
||||
|
||||
@ -13,14 +13,14 @@
|
||||
|
||||
| Change | Repo | Status |
|
||||
|--------|------|--------|
|
||||
| Railway deployment for control plane | molecule-controlplane | Deployed, auto-deploy on push |
|
||||
| EC2 provisioner for tenants (Postgres + Redis + Platform in Docker) | molecule-controlplane | Deployed |
|
||||
| EC2 provisioner for workspaces (pip install runtime at boot) | molecule-controlplane | Deployed, 9 min cold start |
|
||||
| Railway deployment for control plane | the private control-plane repo | Deployed, auto-deploy on push |
|
||||
| EC2 provisioner for tenants (Postgres + Redis + Platform in Docker) | the private control-plane repo | Deployed |
|
||||
| EC2 provisioner for workspaces (pip install runtime at boot) | the private control-plane repo | Deployed, 9 min cold start |
|
||||
| Cloudflare Worker for wildcard subdomain routing | molecule-tenant-proxy (new repo) | Deployed |
|
||||
| Wildcard DNS `*.moleculesai.app` → Worker | Cloudflare dashboard | Done |
|
||||
| Per-tenant ADMIN_TOKEN for Worker auth injection | molecule-controlplane | Deployed |
|
||||
| Auto-updater cron on tenant EC2s (Option B) | molecule-controlplane | Deployed |
|
||||
| Phase 33.2: stop creating per-tenant DNS records | molecule-controlplane | Deployed |
|
||||
| Per-tenant ADMIN_TOKEN for Worker auth injection | the private control-plane repo | Deployed |
|
||||
| Auto-updater cron on tenant EC2s (Option B) | the private control-plane repo | Deployed |
|
||||
| Phase 33.2: stop creating per-tenant DNS records | the private control-plane repo | Deployed |
|
||||
| Provisioning status page (progress bar + ETA) | molecule-app | Deployed to Vercel |
|
||||
| Delete org button with type-to-confirm | molecule-app | Deployed to Vercel |
|
||||
| Remove admin section from SaaS app | molecule-app | Deployed to Vercel |
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# GDPR Art. 17 hard-delete cascade
|
||||
|
||||
Operational reference for the "delete my org" flow in `molecule-controlplane`.
|
||||
Operational reference for the "delete my org" flow in `the private control-plane repo`.
|
||||
Skim this before replying to an erasure request, answering a DPA (Data
|
||||
Processing Addendum) audit, or debugging a failed purge.
|
||||
|
||||
@ -89,7 +89,7 @@ any purge row is older than 48h without hitting `completed`.
|
||||
|
||||
## Testing the cascade
|
||||
|
||||
See the test plan in [PR #29](https://github.com/Molecule-AI/molecule-controlplane/pull/29)
|
||||
See the test plan in [PR #29](https://github.com/Molecule-AI/the private control-plane repo/pull/29)
|
||||
for the staging checklist. The unit tests cover the orchestrator logic
|
||||
(happy path, resume-from-step, Stripe failure, no-customer); end-to-end
|
||||
proof requires a real Stripe test-mode customer + provisioned Fly Machine
|
||||
@ -102,5 +102,5 @@ because the failure modes that matter are transport errors, not logic.
|
||||
- `docs/runbooks/admin-auth.md` — `DELETE /cp/orgs/:slug` is behind
|
||||
session-cookie auth in controlplane, not the workspace bearer-token
|
||||
middleware documented there
|
||||
- `molecule-controlplane/internal/handlers/purge.go` — the orchestrator
|
||||
- `molecule-controlplane/migrations/006_org_purges.*.sql` — audit schema
|
||||
- `the private control-plane repo/internal/handlers/purge.go` — the orchestrator
|
||||
- `the private control-plane repo/migrations/006_org_purges.*.sql` — audit schema
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"_comment": "TODO: pin refs to release tags (v1.0.0) before first customer deploy for reproducible builds. 'main' is OK while all repos are internal.",
|
||||
"_comment": "Pin refs to release tags for reproducible builds. 'main' is OK while all repos are internal.",
|
||||
"version": 1,
|
||||
"plugins": [
|
||||
{"name": "browser-automation", "repo": "Molecule-AI/molecule-ai-plugin-browser-automation", "ref": "main"},
|
||||
|
||||
@ -9,7 +9,7 @@ This repo uses the standard monorepo testing convention: **unit tests live with
|
||||
| Go unit + integration (platform, CLI, handlers) | `workspace-server/**/*_test.go` — run with `cd workspace-server && go test -race ./...` |
|
||||
| TypeScript unit (canvas components, hooks, store) | `canvas/src/**/__tests__/` — run with `cd canvas && npm test -- --run` |
|
||||
| TypeScript unit (MCP server handlers) | `mcp-server/src/__tests__/` — run with `cd mcp-server && npx jest` |
|
||||
| Python unit (workspace runtime, adapters) | `workspace-template/tests/` — run with `cd workspace-template && python3 -m pytest` |
|
||||
| Python unit (workspace runtime, adapters) | `workspace/tests/` — run with `cd workspace && python3 -m pytest` |
|
||||
| Python unit (SDK: plugin + remote agent) | `sdk/python/tests/` — run with `cd sdk/python && python3 -m pytest` |
|
||||
| **Cross-component E2E** (spans platform + runtime + HTTP) | `tests/e2e/` ← **you are here** |
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
#!/bin/bash
|
||||
# Full E2E test for Claude Code workspace runtime
|
||||
# Run from repo root after: docker compose up -d && docker build -t workspace-template:latest workspace-template/
|
||||
# Run from repo root after: docker compose up -d && docker build -t workspace:latest workspace/
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Platform running on localhost:8080
|
||||
# - workspace-template:latest image built
|
||||
# - workspace:latest image built
|
||||
# - .auth-token in workspace-configs-templates/claude-code-default/
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# test_saas_tenant.sh — smoke test a live SaaS tenant through the Cloudflare Worker
|
||||
#
|
||||
# Usage: TENANT_SLUG=hongming2 bash tests/e2e/test_saas_tenant.sh
|
||||
# TENANT_SLUG=hongming2 DIRECT_IP=3.144.193.40 bash tests/e2e/test_saas_tenant.sh
|
||||
# Usage: TENANT_SLUG=example-org bash tests/e2e/test_saas_tenant.sh
|
||||
# TENANT_SLUG=example-org DIRECT_IP=<EC2_IP> bash tests/e2e/test_saas_tenant.sh
|
||||
#
|
||||
# Tests both Worker-proxied routes and (optionally) direct EC2 access.
|
||||
# Exits 0 if all critical tests pass, 1 otherwise.
|
||||
|
||||
1
workspace-server/.gitignore
vendored
Normal file
1
workspace-server/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
server
|
||||
@ -23,7 +23,7 @@
|
||||
// AdminAuth — any valid workspace bearer token can call it.
|
||||
//
|
||||
// 2. Workspace side: a shell credential helper
|
||||
// (workspace-template/scripts/molecule-git-token-helper.sh) configured
|
||||
// (workspace/scripts/molecule-git-token-helper.sh) configured
|
||||
// as the git credential helper. git calls it on every push/fetch;
|
||||
// it hits this endpoint and emits the fresh token to stdout. A 30-min
|
||||
// cron also runs `gh auth login --with-token` using the same helper.
|
||||
|
||||
@ -2,7 +2,7 @@ package handlers
|
||||
|
||||
// Package handlers — MCP bridge for opencode integration (#800, #809, #810).
|
||||
//
|
||||
// Exposes the same 8 A2A tools as workspace-template/a2a_mcp_server.py but
|
||||
// Exposes the same 8 A2A tools as workspace/a2a_mcp_server.py but
|
||||
// served directly from the platform over HTTP so CLI runtimes running
|
||||
// OUTSIDE workspace containers (opencode, Claude Code on the developer's
|
||||
// machine) can participate in the A2A mesh.
|
||||
@ -96,7 +96,7 @@ func NewMCPHandler(database *sql.DB, broadcaster *events.Broadcaster) *MCPHandle
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tool definitions (mirrors workspace-template/a2a_mcp_server.py TOOLS list)
|
||||
// Tool definitions (mirrors workspace/a2a_mcp_server.py TOOLS list)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
var mcpAllTools = []mcpTool{
|
||||
|
||||
@ -93,7 +93,7 @@ type OrgDefaults struct {
|
||||
// when both are set.
|
||||
InitialPromptFile string `yaml:"initial_prompt_file" json:"initial_prompt_file"`
|
||||
// IdlePrompt / IdleIntervalSeconds are the workspace-default idle-loop
|
||||
// body and cadence (see workspace-template/heartbeat.py). They were
|
||||
// body and cadence (see workspace/heartbeat.py). They were
|
||||
// previously dropped by the org importer because the struct didn't
|
||||
// declare them — causing live configs to boot without idle_prompts
|
||||
// even when org.yaml had them. Phase 1 scalability work adds both
|
||||
@ -539,7 +539,7 @@ func (h *OrgHandler) createWorkspaceTree(ws OrgWorkspace, parentID *string, defa
|
||||
// Resolve idle_prompt — same precedence (ws inline → ws file → defaults).
|
||||
// Inject into config.yaml alongside idle_interval_seconds so the
|
||||
// workspace's heartbeat loop picks up the idle-reflection cadence on
|
||||
// boot (see workspace-template/heartbeat.py + config.py).
|
||||
// boot (see workspace/heartbeat.py + config.py).
|
||||
idlePrompt, err := resolvePromptRef(ws.IdlePrompt, ws.IdlePromptFile, orgBaseDir, ws.FilesDir)
|
||||
if err != nil {
|
||||
log.Printf("Org import: failed to resolve idle_prompt for %s: %v", ws.Name, err)
|
||||
@ -569,7 +569,7 @@ func (h *OrgHandler) createWorkspaceTree(ws OrgWorkspace, parentID *string, defa
|
||||
// means the idle loop never fires regardless of interval, so we
|
||||
// only emit interval when there's a body to go with it.
|
||||
if idleInterval <= 0 {
|
||||
idleInterval = 600 // same default as workspace-template/config.py
|
||||
idleInterval = 600 // same default as workspace/config.py
|
||||
}
|
||||
block := fmt.Sprintf("idle_interval_seconds: %d\nidle_prompt: |\n %s\n", idleInterval, indented)
|
||||
configFiles["config.yaml"] = appendYAMLBlock(configFiles["config.yaml"], block)
|
||||
|
||||
@ -358,7 +358,7 @@ func configDirName(workspaceID string) string {
|
||||
// string, and the path-traversal oracle where `runtime: ../../sensitive`
|
||||
// probed host directories for existence.
|
||||
//
|
||||
// Keep in sync with workspace-template/build-all.sh — adding a new
|
||||
// Keep in sync with workspace/build-all.sh — adding a new
|
||||
// runtime means bumping both this list and the Docker image tags.
|
||||
var knownRuntimes = map[string]struct{}{
|
||||
"langgraph": {},
|
||||
|
||||
@ -24,7 +24,7 @@ import (
|
||||
// RuntimeImages maps runtime names to their Docker image tags.
|
||||
// Each adapter has its own pre-built image extending workspace-template:base,
|
||||
// with runtime-specific deps pre-installed for fast startup.
|
||||
// Build all: workspace-template/Dockerfile (base), then each adapters/*/Dockerfile.
|
||||
// Build all: workspace/Dockerfile (base), then each adapters/*/Dockerfile.
|
||||
var RuntimeImages = map[string]string{
|
||||
"langgraph": "workspace-template:langgraph",
|
||||
"claude-code": "workspace-template:claude-code",
|
||||
@ -244,7 +244,7 @@ func (p *Provisioner) Start(ctx context.Context, cfg WorkspaceConfig) (string, e
|
||||
if err != nil {
|
||||
if isImageNotFoundErr(err) {
|
||||
return "", fmt.Errorf(
|
||||
"docker image %q not found — run 'bash workspace-template/build-all.sh %s' to build it (underlying error: %w)",
|
||||
"docker image %q not found — run 'bash workspace/build-all.sh %s' to build it (underlying error: %w)",
|
||||
image, runtimeTagFromImage(image), err,
|
||||
)
|
||||
}
|
||||
|
||||
@ -741,14 +741,14 @@ func TestImageNotFoundErrorIncludesBuildHint(t *testing.T) {
|
||||
|
||||
tag := runtimeTagFromImage("workspace-template:openclaw")
|
||||
wrapped := testErr(
|
||||
`docker image "workspace-template:openclaw" not found — run 'bash workspace-template/build-all.sh ` +
|
||||
`docker image "workspace-template:openclaw" not found — run 'bash workspace/build-all.sh ` +
|
||||
tag + `' to build it (underlying error: ` + underlying.Error() + `)`,
|
||||
)
|
||||
s := wrapped.Error()
|
||||
|
||||
for _, want := range []string{
|
||||
`"workspace-template:openclaw"`,
|
||||
`bash workspace-template/build-all.sh openclaw`,
|
||||
`bash workspace/build-all.sh openclaw`,
|
||||
`No such image: workspace-template:openclaw`,
|
||||
} {
|
||||
if !strings.Contains(s, want) {
|
||||
|
||||
Binary file not shown.
@ -39,7 +39,7 @@ class BaseAdapter(ABC):
|
||||
"""Interface every agent infrastructure adapter must implement.
|
||||
|
||||
To add a new agent infra:
|
||||
1. Create workspace-template/adapters/<your_infra>/
|
||||
1. Create workspace/adapters/<your_infra>/
|
||||
2. Implement adapter.py with a class extending BaseAdapter
|
||||
3. Add requirements.txt with your infra's dependencies
|
||||
4. Export as Adapter in __init__.py
|
||||
|
||||
@ -37,7 +37,7 @@ import pytest
|
||||
def _make_a2a_stubs() -> None:
|
||||
"""Register minimal a2a SDK stubs in sys.modules.
|
||||
|
||||
Mirrors what workspace-template/tests/conftest.py does; needed because
|
||||
Mirrors what workspace/tests/conftest.py does; needed because
|
||||
this test file lives outside the ``tests/`` directory and conftest.py
|
||||
is not automatically loaded for it.
|
||||
"""
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
Resolution order for ``(plugin_name, runtime)``:
|
||||
|
||||
1. Platform registry → ``workspace-template/plugins_registry/<plugin>/<runtime>.py``
|
||||
1. Platform registry → ``workspace/plugins_registry/<plugin>/<runtime>.py``
|
||||
2. Plugin-shipped → ``<plugin_root>/adapters/<runtime>.py``
|
||||
3. Raw filesystem → :class:`RawDropAdaptor` (warns, drops files only)
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
"""Tests for workspace-template/platform_auth.py (Phase 30.1)."""
|
||||
"""Tests for workspace/platform_auth.py (Phase 30.1)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
@ -16,7 +16,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
# Resolve workspace-template/ so `import plugins_registry` works in CI without
|
||||
# Resolve workspace/ so `import plugins_registry` works in CI without
|
||||
# requiring an installed package.
|
||||
_WS_TEMPLATE = Path(__file__).resolve().parents[1]
|
||||
if str(_WS_TEMPLATE) not in sys.path:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user