diff --git a/CLAUDE.md b/CLAUDE.md index 2e156023..71f5f59e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -240,9 +240,11 @@ OPENAI_API_KEY=... bash scripts/test-team-e2e.sh # E2E: Multi-template cd platform && go test -race ./... # 818 Go tests (handlers, registry, provisioner, CLI, delegation, org, channels, wsauth, middleware, scheduler, crypto, db — sqlmock + miniredis; +2 on 2026-04-15 tick-32 for YAML-injection runtime/model allowlist + TestSanitizeRuntime_Allowlist; +70 on 2026-04-15 overnight sweep across the security fix cluster; +6 on 2026-04-14 tick-8 for TestTenantGuard_*) cd canvas && npm test # 482 Vitest tests (store, components, hydration, buildTree, secrets API, org template import, ConfirmDialog singleButton + 7 native-dialog replacements, WCAG critical batch, +12 on tick-32 for CookieConsent dialog + privacy-preserving default, +17 on tick-32 for PricingTable dispatch matrix + billing helper) cd workspace-template && python -m pytest -v # 1179 pytest tests (adds platform_auth token store for Phase 30.1, memory_write activity logging, Hermes multi-provider registry, +10 on tick-32 for test_hermes_phase2_dispatch covering native Anthropic + native Gemini paths via auth_scheme dispatch; fixed env-var leak in test_hermes_providers fixture via snapshot/restore) -# SDK + MCP server tests now in standalone repos: -# github.com/Molecule-AI/molecule-sdk-python (pip install molecule-ai-sdk) -# github.com/Molecule-AI/molecule-mcp-server (npx @molecule-ai/mcp-server) +# SDK, MCP, CLI, and workspace runtime now in standalone repos: +# https://github.com/Molecule-AI/molecule-sdk-python pip install molecule-ai-sdk (132 tests) +# https://github.com/Molecule-AI/molecule-mcp-server npx @molecule-ai/mcp-server (97 tests) +# https://github.com/Molecule-AI/molecule-cli go install (Go TUI dashboard) +# https://github.com/Molecule-AI/molecule-ai-workspace-runtime pip install molecule-ai-workspace-runtime (shared adapter base) ``` ### Integration Tests diff --git a/manifest.json b/manifest.json index 121d13bf..166dce91 100644 --- a/manifest.json +++ b/manifest.json @@ -1,4 +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.", "version": 1, "plugins": [ {"name": "browser-automation", "repo": "Molecule-AI/molecule-ai-plugin-browser-automation", "ref": "main"}, diff --git a/platform/Dockerfile b/platform/Dockerfile index 3aa49fa4..d5789b41 100644 --- a/platform/Dockerfile +++ b/platform/Dockerfile @@ -12,7 +12,7 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o /platform ./cmd/server # Clone templates + plugins at build time from manifest.json FROM alpine:3.20 AS templates -RUN apk add --no-cache git python3 +RUN apk add --no-cache git jq COPY manifest.json /manifest.json COPY scripts/clone-manifest.sh /scripts/clone-manifest.sh RUN chmod +x /scripts/clone-manifest.sh && /scripts/clone-manifest.sh /manifest.json /workspace-configs-templates /org-templates /plugins diff --git a/platform/Dockerfile.tenant b/platform/Dockerfile.tenant index 26f8a459..99bef4e0 100644 --- a/platform/Dockerfile.tenant +++ b/platform/Dockerfile.tenant @@ -35,7 +35,7 @@ RUN npm run build # ── Stage 3: Clone templates + plugins from manifest.json ───────────── FROM alpine:3.20 AS templates -RUN apk add --no-cache git python3 +RUN apk add --no-cache git jq COPY manifest.json /manifest.json COPY scripts/clone-manifest.sh /scripts/clone-manifest.sh RUN chmod +x /scripts/clone-manifest.sh && /scripts/clone-manifest.sh /manifest.json /workspace-configs-templates /org-templates /plugins diff --git a/platform/internal/middleware/securityheaders.go b/platform/internal/middleware/securityheaders.go index d16707ea..fad4ea92 100644 --- a/platform/internal/middleware/securityheaders.go +++ b/platform/internal/middleware/securityheaders.go @@ -37,13 +37,12 @@ func SecurityHeaders() gin.HandlerFunc { // Content-Type after Next() — but that's too late for headers. // // Simpler: apply a permissive CSP that allows Next.js to work. - // 'unsafe-inline' + 'unsafe-eval' are needed for Next.js dev - // hydration; in production Next.js uses nonces but we don't - // propagate them through the proxy. This is acceptable because - // the canvas is our own code, not user-generated content. + // 'unsafe-inline' is needed for Next.js standalone builds that + // inject inline scripts for hydration. 'unsafe-eval' was dropped + // after confirming production canvas renders without it. c.Header("Content-Security-Policy", "default-src 'self'; "+ - "script-src 'self' 'unsafe-inline' 'unsafe-eval'; "+ + "script-src 'self' 'unsafe-inline'; "+ "style-src 'self' 'unsafe-inline'; "+ "img-src 'self' data: blob:; "+ "connect-src 'self' ws: wss:; "+ diff --git a/platform/internal/middleware/securityheaders_test.go b/platform/internal/middleware/securityheaders_test.go index 43e0abd2..b0e8d8d1 100644 --- a/platform/internal/middleware/securityheaders_test.go +++ b/platform/internal/middleware/securityheaders_test.go @@ -56,7 +56,7 @@ func TestSecurityHeaders(t *testing.T) { csp := w.Header().Get("Content-Security-Policy") for _, fragment := range []string{ "default-src 'self'", - "script-src 'self' 'unsafe-inline' 'unsafe-eval'", + "script-src 'self' 'unsafe-inline'", "style-src 'self' 'unsafe-inline'", "img-src 'self' data: blob:", "connect-src 'self' ws: wss:", diff --git a/scripts/clone-manifest.sh b/scripts/clone-manifest.sh index 6d0615b8..82bf4803 100644 --- a/scripts/clone-manifest.sh +++ b/scripts/clone-manifest.sh @@ -1,12 +1,11 @@ #!/usr/bin/env bash # clone-manifest.sh — clone all repos listed in manifest.json into their -# target directories. Replaces 33 hardcoded git-clone lines in Dockerfiles. +# target directories. Replaces hardcoded git-clone lines in Dockerfiles. # # Usage: # ./scripts/clone-manifest.sh # -# Example (Docker build stage): -# /scripts/clone-manifest.sh /manifest.json /workspace-configs-templates /org-templates /plugins +# Requires: git, jq (lighter than python3 — ~2MB vs ~50MB in Alpine) set -euo pipefail @@ -15,26 +14,34 @@ WS_DIR="${2:?Missing workspace-templates dir}" ORG_DIR="${3:?Missing org-templates dir}" PLUGINS_DIR="${4:?Missing plugins dir}" +EXPECTED=0 +CLONED=0 + clone_category() { local category="$1" local target_dir="$2" mkdir -p "$target_dir" - # Use python3 to parse JSON (jq may not be available in Docker) - python3 -c " -import json, sys -with open('$MANIFEST') as f: - m = json.load(f) -for entry in m.get('$category', []): - print(entry['name'], entry['repo'], entry.get('ref', 'main')) -" | while read -r name repo ref; do + local count + count=$(jq -r ".${category} | length" "$MANIFEST") + EXPECTED=$((EXPECTED + count)) + + local i=0 + while [ "$i" -lt "$count" ]; do + local name repo ref + name=$(jq -r ".${category}[$i].name" "$MANIFEST") + repo=$(jq -r ".${category}[$i].repo" "$MANIFEST") + ref=$(jq -r ".${category}[$i].ref // \"main\"" "$MANIFEST") + echo " cloning $repo -> $target_dir/$name (ref=$ref)" if [ "$ref" = "main" ]; then git clone --depth=1 -q "https://github.com/${repo}.git" "$target_dir/$name" else git clone --depth=1 -q --branch "$ref" "https://github.com/${repo}.git" "$target_dir/$name" fi + CLONED=$((CLONED + 1)) + i=$((i + 1)) done # Strip .git dirs to save space @@ -50,4 +57,10 @@ clone_category "org_templates" "$ORG_DIR" echo "==> Cloning plugins..." clone_category "plugins" "$PLUGINS_DIR" -echo "==> Done. $(find "$WS_DIR" "$ORG_DIR" "$PLUGINS_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ') repos cloned." +# Verify all repos were cloned +if [ "$CLONED" -ne "$EXPECTED" ]; then + echo "::error::Expected $EXPECTED repos but only cloned $CLONED — some clones failed" + exit 1 +fi + +echo "==> Done. $CLONED/$EXPECTED repos cloned successfully." diff --git a/workspace-template/pyproject.toml b/workspace-template/pyproject.toml deleted file mode 100644 index ed64cbc3..00000000 --- a/workspace-template/pyproject.toml +++ /dev/null @@ -1,35 +0,0 @@ -[build-system] -requires = ["setuptools>=68.0", "wheel"] -build-backend = "setuptools.build_meta" - -[project] -name = "molecule-ai-workspace-runtime" -version = "0.1.0" -description = "Molecule AI workspace runtime — shared infrastructure for all agent adapters" -requires-python = ">=3.11" -license = {text = "BSL-1.1"} -readme = "README.md" -# Don't pin heavy deps — each adapter adds its own -dependencies = [ - "a2a-sdk[http-server]>=0.3.25", - "httpx>=0.27.0", - "uvicorn>=0.30.0", - "starlette>=0.38.0", - "websockets>=12.0", - "pyyaml>=6.0", - "langchain-core>=0.3.0", - "opentelemetry-api>=1.24.0", - "opentelemetry-sdk>=1.24.0", - "opentelemetry-exporter-otlp-proto-http>=1.24.0", - "temporalio>=1.7.0", -] - -[project.scripts] -molecule-runtime = "molecule_runtime.main:main_sync" - -[tool.setuptools.packages.find] -where = ["."] -include = ["molecule_runtime*"] - -[tool.setuptools.package-data] -"molecule_runtime" = ["py.typed"]