From 1b8190d2713eabba4146eaacf3f979e6765c5afe Mon Sep 17 00:00:00 2001 From: Molecule AI Infra Lead Date: Fri, 15 May 2026 10:58:30 +0000 Subject: [PATCH 1/2] fix(ci): remove stale PHASE3_MASKED from all-required sentinel (DISCOVERY #1167) mc#774 was closed 2026-05-14, re-enabling continue-on-error: false on platform-build. The PHASE3_MASKED workaround that suppressed platform-build failures in the sentinel is now stale and was creating a false-green signal on the all-required CI gate. Also removes Phase 3 references from comments and success message. --- .gitea/workflows/ci.yml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 8438221b3..bb0b87141 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -604,20 +604,17 @@ jobs: set -euo pipefail # `needs.*.result` is one of: success | failure | cancelled | skipped | null. # We assert success per dep (not != failure) — see RFC §2 reasoning above. - # Null results are skipped: they come from Phase 3 (continue-on-error: true - # suppresses status) or from jobs still in-flight. The sentinel succeeds - # rather than blocking PRs on Phase 3 noise. + # Null results are skipped: they come from jobs still in-flight. + # The sentinel succeeds rather than blocking PRs on transient in-flight noise. results='${{ toJSON(needs) }}' echo "$results" echo "$results" | python3 -c ' import json, sys ns = json.load(sys.stdin) - # Phase 3 masked: jobs with continue-on-error: true may report "failure" - # Remove when mc#774 handler test failures are resolved. - PHASE3_MASKED = {"platform-build"} - # Exclude null (Phase 3 suppressed / in-flight) from the bad list. + # Phase 3 is over (mc#774 closed 2026-05-14, continue-on-error: false re-enabled). + # All non-null/non-skipped/non-cancelled deps must succeed. bad = [(k, v.get("result")) for k, v in ns.items() - if v.get("result") not in ("success", None, "cancelled", "skipped") and k not in PHASE3_MASKED] + if v.get("result") not in ("success", None, "cancelled", "skipped")] if bad: print(f"FAIL: jobs not green:", file=sys.stderr) for k, r in bad: @@ -633,5 +630,5 @@ jobs: if cancelled: print(f"INFO: {len(cancelled)} job(s) masked by continue-on-error: " + ", ".join(k for k, _ in cancelled), file=sys.stderr) - print(f"OK: all {len(ns)} required jobs succeeded (or Phase-3 suppressed)") + print(f"OK: all {len(ns)} required jobs succeeded") ' -- 2.52.0 From e28e996f1c1231a9258633f8a25a9c521b5879b4 Mon Sep 17 00:00:00 2001 From: Molecule AI Infra Lead Date: Fri, 15 May 2026 16:34:31 +0000 Subject: [PATCH 2/2] fix(provisioner): remove 12-char UUID truncation from container/volume names (KI-010) ContainerName(), ConfigVolumeName(), and ClaudeSessionVolumeName() were truncating workspace UUIDs to 12 characters, causing Docker container-name collisions. This broke A2A peer routing because the A2A adapter embeds the container name (ws-) which is not globally unique. Fix: use the full workspaceID for all three functions. Docker limits container names to 128 chars; a 36-char UUID (ws-) is well within that limit. Volume names are unbounded. Updated tests to reflect no-truncation behavior. Refs: KI-010, internal#412 Co-Authored-By: Claude Opus 4.7 --- .../internal/provisioner/provisioner.go | 24 +++++++------------ .../internal/provisioner/provisioner_test.go | 15 ++++++------ 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/workspace-server/internal/provisioner/provisioner.go b/workspace-server/internal/provisioner/provisioner.go index 4c19c2046..87bf2a82d 100644 --- a/workspace-server/internal/provisioner/provisioner.go +++ b/workspace-server/internal/provisioner/provisioner.go @@ -129,24 +129,19 @@ const ( ) // ConfigVolumeName returns the Docker named volume for a workspace's configs. +// Full workspaceID is used to avoid container-name aliasing that breaks +// A2A routing (KI-010). Docker volume names support the full UUID length. func ConfigVolumeName(workspaceID string) string { - id := workspaceID - if len(id) > 12 { - id = id[:12] - } - return fmt.Sprintf("ws-%s-configs", id) + return fmt.Sprintf("ws-%s-configs", workspaceID) } // ClaudeSessionVolumeName returns the Docker named volume for a workspace's // Claude Code session directory (/root/.claude/sessions). Separate from the // config volume so it can be discarded independently (via WORKSPACE_RESET_SESSION // or ?reset=true) without wiping the user's config. Issue #12. +// Full workspaceID is used to avoid aliasing (KI-010). func ClaudeSessionVolumeName(workspaceID string) string { - id := workspaceID - if len(id) > 12 { - id = id[:12] - } - return fmt.Sprintf("ws-%s-claude-sessions", id) + return fmt.Sprintf("ws-%s-claude-sessions", workspaceID) } // Provisioner manages Docker containers for workspace agents. @@ -164,12 +159,11 @@ func New() (*Provisioner, error) { } // ContainerName returns the Docker container name for a workspace. +// Full workspaceID is used to avoid container-name collisions that break +// A2A peer routing (KI-010). Docker caps container names at 128 chars; +// a 36-char UUID is well within limits. func ContainerName(workspaceID string) string { - id := workspaceID - if len(id) > 12 { - id = id[:12] - } - return fmt.Sprintf("ws-%s", id) + return fmt.Sprintf("ws-%s", workspaceID) } // containerNamePrefix is the shared prefix every workspace container diff --git a/workspace-server/internal/provisioner/provisioner_test.go b/workspace-server/internal/provisioner/provisioner_test.go index 8d4a20f05..c02a9e080 100644 --- a/workspace-server/internal/provisioner/provisioner_test.go +++ b/workspace-server/internal/provisioner/provisioner_test.go @@ -350,15 +350,16 @@ func TestTierEscalation(t *testing.T) { } // TestContainerName verifies the naming convention. +// No truncation: full workspaceID is used to avoid container-name collisions +// that break A2A peer routing (KI-010). func TestContainerName(t *testing.T) { tests := []struct { id string want string }{ {"short", "ws-short"}, - {"exactly12ch", "ws-exactly12ch"}, - {"longer-than-twelve-characters", "ws-longer-than-"}, {"abc", "ws-abc"}, + {"ab078012-c305-42be-b93d-b6e8a78d5409", "ws-ab078012-c305-42be-b93d-b6e8a78d5409"}, } for _, tt := range tests { @@ -370,15 +371,15 @@ func TestContainerName(t *testing.T) { } // TestConfigVolumeName verifies config volume naming. +// No truncation — full workspaceID used (KI-010). func TestConfigVolumeName(t *testing.T) { tests := []struct { id string want string }{ {"short", "ws-short-configs"}, - {"exactly12ch", "ws-exactly12ch-configs"}, - {"longer-than-twelve-characters", "ws-longer-than--configs"}, {"abc", "ws-abc-configs"}, + {"ab078012-c305-42be-b93d-b6e8a78d5409", "ws-ab078012-c305-42be-b93d-b6e8a78d5409-configs"}, } for _, tt := range tests { @@ -392,17 +393,15 @@ func TestConfigVolumeName(t *testing.T) { // ---------- #12 — claude-sessions volume naming ---------- // TestClaudeSessionVolumeName_Deterministic: same ID → same volume name, and -// the name follows the ws--claude-sessions shape used everywhere -// else in the provisioner. +// the name follows the ws--claude-sessions shape (no truncation, KI-010). func TestClaudeSessionVolumeName_Deterministic(t *testing.T) { tests := []struct { id string want string }{ {"short", "ws-short-claude-sessions"}, - {"exactly12ch", "ws-exactly12ch-claude-sessions"}, - {"longer-than-twelve-characters", "ws-longer-than--claude-sessions"}, {"abc", "ws-abc-claude-sessions"}, + {"ab078012-c305-42be-b93d-b6e8a78d5409", "ws-ab078012-c305-42be-b93d-b6e8a78d5409-claude-sessions"}, } for _, tt := range tests { got := ClaudeSessionVolumeName(tt.id) -- 2.52.0