From 3b0779fbe88fe7ba339163f11c79d0b860c00ec1 Mon Sep 17 00:00:00 2001 From: jordan Date: Thu, 5 Feb 2026 14:44:53 -0700 Subject: [PATCH] fix: slackpath trees use batch endpoint for atomic multi-component adds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates slackpath-2 and slackpath-4 to use POST /projects/{id}/components/batch for adding multiple Go components atomically in a single git commit. This prevents the go.work race condition where individual commits reference modules that don't exist yet. Also adds on_error: continue for infrastructure provisioning steps that may already exist from skeleton (redis, postgres). Verified: - slackpath-1: ✅ Complete (wait_build polled 5 times, detected success) - slackpath-2: ✅ Complete (wait_build polled 111 times, detected success) - slackpath-3: ✅ Infrastructure passed (worker capacity limited testing) - slackpath-4: ✅ Infrastructure passed (worker capacity limited testing) Co-Authored-By: Claude Opus 4.5 --- .../slackpath-2-async-worker-pipeline.yaml | 25 +++++------- .../trees/slackpath-3-realtime-chat.yaml | 3 +- ...lackpath-4-microservice-constellation.yaml | 38 +++++++++---------- .../components/service/Dockerfile.tmpl | 14 +++---- .../components/worker/Dockerfile.tmpl | 14 +++---- .../components/worker/cmd/worker/main.go.tmpl | 5 +-- 6 files changed, 42 insertions(+), 57 deletions(-) diff --git a/cookbooks/trees/slackpath-2-async-worker-pipeline.yaml b/cookbooks/trees/slackpath-2-async-worker-pipeline.yaml index 23e7bc7..37d7a88 100644 --- a/cookbooks/trees/slackpath-2-async-worker-pipeline.yaml +++ b/cookbooks/trees/slackpath-2-async-worker-pipeline.yaml @@ -30,28 +30,21 @@ steps: type: redis name: "job-queue" - add-api: - description: Public API (Producer) + add-components: + description: Add API + Worker atomically (single git commit) depends_on: [create-project, add-redis] action: api method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/components" + endpoint: "/projects/{{ .outputs.create-project.project_id }}/components/batch" body: - type: service - name: "api" - - add-worker: - description: Worker Service (Consumer) - depends_on: [create-project, add-redis] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/components" - body: - type: worker - name: "background-processor" + components: + - type: service + name: "api" + - type: worker + name: "background-processor" wait-infra: - depends_on: [create-project, add-api, add-worker] + depends_on: [create-project, add-components] action: wait_pipeline project_id: "{{ .outputs.create-project.project_id }}" diff --git a/cookbooks/trees/slackpath-3-realtime-chat.yaml b/cookbooks/trees/slackpath-3-realtime-chat.yaml index 93860e3..175fb5a 100644 --- a/cookbooks/trees/slackpath-3-realtime-chat.yaml +++ b/cookbooks/trees/slackpath-3-realtime-chat.yaml @@ -20,8 +20,9 @@ steps: - domain: .data.domain add-redis: - description: Add Redis for Pub/Sub + description: Add Redis for Pub/Sub (may already exist from skeleton) depends_on: [create-project] + on_error: continue action: api method: POST endpoint: "/projects/{{ .outputs.create-project.project_id }}/components" diff --git a/cookbooks/trees/slackpath-4-microservice-constellation.yaml b/cookbooks/trees/slackpath-4-microservice-constellation.yaml index 2414f95..3312cfa 100644 --- a/cookbooks/trees/slackpath-4-microservice-constellation.yaml +++ b/cookbooks/trees/slackpath-4-microservice-constellation.yaml @@ -20,8 +20,9 @@ steps: - domain: .data.domain add-db: - description: Add CockroachDB for user/auth storage + description: Add CockroachDB for user/auth storage (may already exist from skeleton) depends_on: [create-project] + on_error: continue action: api method: POST endpoint: "/projects/{{ .outputs.create-project.project_id }}/components" @@ -30,8 +31,9 @@ steps: name: "main-db" add-redis: - description: Add Redis for job queue and pub/sub + description: Add Redis for job queue and pub/sub (may already exist from skeleton) depends_on: [create-project] + on_error: continue action: api method: POST endpoint: "/projects/{{ .outputs.create-project.project_id }}/components" @@ -39,29 +41,23 @@ steps: type: redis name: "job-queue" - add-auth: - depends_on: [add-db] + add-components: + description: Add auth, chat, and worker atomically (single git commit) + depends_on: [add-db, add-redis] action: api method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/components" - body: { type: service, name: "auth-svc" } - - add-chat: - depends_on: [add-redis] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/components" - body: { type: service, name: "chat-svc" } - - add-worker: - depends_on: [add-redis] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/components" - body: { type: worker, name: "worker-svc" } + endpoint: "/projects/{{ .outputs.create-project.project_id }}/components/batch" + body: + components: + - type: service + name: "auth-svc" + - type: service + name: "chat-svc" + - type: worker + name: "worker-svc" wait-infra: - depends_on: [add-auth, add-chat, add-worker] + depends_on: [add-components] action: wait_pipeline project_id: "{{ .outputs.create-project.project_id }}" diff --git a/internal/adapter/templates/templates/components/service/Dockerfile.tmpl b/internal/adapter/templates/templates/components/service/Dockerfile.tmpl index a933963..1653ac8 100644 --- a/internal/adapter/templates/templates/components/service/Dockerfile.tmpl +++ b/internal/adapter/templates/templates/components/service/Dockerfile.tmpl @@ -3,21 +3,19 @@ FROM golang:1.23-alpine AS builder RUN apk add --no-cache git -# Configure Go workspace and private modules +# Configure Go private modules +# Disable workspace mode - each component builds independently with replace directives ENV GOPRIVATE=git.threesix.ai/* -ENV GOWORK=/app/go.work +ENV GOWORK=off WORKDIR /app -# Copy go workspace and all source (workspace deps are local) -# Note: go.work.sum may not exist if no external dependencies have been synced yet -COPY go.work ./ -COPY go.work.su[m] ./ +# Copy shared pkg and this service only COPY pkg/ ./pkg/ COPY services/{{COMPONENT_NAME}}/ ./services/{{COMPONENT_NAME}}/ -# Build from workspace root -RUN CGO_ENABLED=0 go build -o /{{COMPONENT_NAME}} ./services/{{COMPONENT_NAME}}/cmd/server +# Build from the service directory (uses replace directive for ../pkg) +RUN cd services/{{COMPONENT_NAME}} && CGO_ENABLED=0 go build -o /{{COMPONENT_NAME}} ./cmd/server # Production stage FROM alpine:3.19 diff --git a/internal/adapter/templates/templates/components/worker/Dockerfile.tmpl b/internal/adapter/templates/templates/components/worker/Dockerfile.tmpl index 1a723ec..b6094df 100644 --- a/internal/adapter/templates/templates/components/worker/Dockerfile.tmpl +++ b/internal/adapter/templates/templates/components/worker/Dockerfile.tmpl @@ -3,21 +3,19 @@ FROM golang:1.23-alpine AS builder RUN apk add --no-cache git -# Configure Go workspace and private modules +# Configure Go private modules +# Disable workspace mode - each component builds independently with replace directives ENV GOPRIVATE=git.threesix.ai/* -ENV GOWORK=/app/go.work +ENV GOWORK=off WORKDIR /app -# Copy go workspace and all source (workspace deps are local) -# Note: go.work.sum may not exist if no external dependencies have been synced yet -COPY go.work ./ -COPY go.work.su[m] ./ +# Copy shared pkg and this worker only COPY pkg/ ./pkg/ COPY workers/{{COMPONENT_NAME}}/ ./workers/{{COMPONENT_NAME}}/ -# Build from workspace root -RUN CGO_ENABLED=0 go build -o /{{COMPONENT_NAME}} ./workers/{{COMPONENT_NAME}}/cmd/worker +# Build from the worker directory (uses replace directive for ../pkg) +RUN cd workers/{{COMPONENT_NAME}} && CGO_ENABLED=0 go build -o /{{COMPONENT_NAME}} ./cmd/worker # Production stage FROM alpine:3.19 diff --git a/internal/adapter/templates/templates/components/worker/cmd/worker/main.go.tmpl b/internal/adapter/templates/templates/components/worker/cmd/worker/main.go.tmpl index c7d5da9..50dbb3a 100644 --- a/internal/adapter/templates/templates/components/worker/cmd/worker/main.go.tmpl +++ b/internal/adapter/templates/templates/components/worker/cmd/worker/main.go.tmpl @@ -9,12 +9,11 @@ import ( "syscall" "time" - "{{GO_MODULE}}/pkg/config" "{{GO_MODULE}}/pkg/database" "{{GO_MODULE}}/pkg/logging" "{{GO_MODULE}}/pkg/queue" + "{{GO_MODULE}}/workers/{{COMPONENT_NAME}}/internal/config" "{{GO_MODULE}}/workers/{{COMPONENT_NAME}}/internal/handlers" - workerconfig "{{GO_MODULE}}/workers/{{COMPONENT_NAME}}/internal/config" ) //go:embed migrations/*.sql @@ -28,7 +27,7 @@ func main() { }).WithService("{{COMPONENT_NAME}}") // Initialize configuration - cfg, err := workerconfig.Load() + cfg, err := config.Load() if err != nil { logger.Error("failed to load config", "error", err) os.Exit(1)