---
name: mbu-golang
description: Go backend development standards and scaffold tools for MBU ID services. Use this skill for any Go backend work including project initialization, entity generation, repository patterns, usecase implementation, REST/gRPC handlers, event publishers/subscribers, and dependency injection. Apply when scaffolding new services, adding features to existing Go services, or implementing clean architecture patterns. This skill covers the complete workflow from SQL DDL to production-ready handlers.
---

# MBU Go Backend Standards

Go backend coding standards, clean architecture patterns, and project structure for MBU microservices.

## Table of Contents

- [Core Principles](#core-principles)
- [Project Structure](#project-structure)
- [Naming Conventions](#naming-conventions)
- [Two Naming Domains](#two-naming-domains)
- [Clean Architecture Layers](#clean-architecture-layers)
- [Scaffold Tools](#scaffold-tools)
- [Workflows](#workflows)
- [Code Patterns](#code-patterns)
- [Validation](#validation)
- [Error Handling](#error-handling)
- [Event Publishing](#event-publishing)
- [Testing](#testing)
- [Best Practices](#best-practices)
- [gRPC & Proto](#grpc--proto)
- [Event-Driven Architecture](#event-driven-architecture)
- [Checklist for New Features](#checklist-for-new-features)

## Core Principles

1. **Clean Architecture** — Strict layer separation: Handler → Usecase → Repository → Database
2. **Scaffold First** — Generate skeleton code, then customize
3. **No Layer Skipping** — Every request flows through all layers
4. **Raw Errors** — Never wrap errors, return them as-is
5. **Type Safety** — Explicit types, no `interface{}` unless necessary

## Project Structure

```
service-warehouse/                # Service root
├── .air.toml                     # Air hot-reload config
├── .dockerignore                 # Docker build exclusions
├── .gitignore                    # Git ignore rules
├── go.mod                        # Main module
├── main.go                       # Entry point
├── entity/                       # Entity definitions
│   ├── warehouse.go
│   └── delivery_plan.go
├── src/
│   ├── handler.go                # Route registration
│   ├── handler/
│   │   ├── rest/                 # REST handlers
│   │   │   ├── warehouse/        # Module handlers
│   │   │   │   ├── handler.go    # Route group + RegisterHandler
│   │   │   │   ├── request_create.go
│   │   │   │   ├── request_update.go
│   │   │   │   ├── request_get.go
│   │   │   │   └── request_delete.go
│   │   │   └── meta/             # Meta endpoint (enum values)
│   │   │       └── handler.go
│   │   └── grpc/                 # gRPC handlers
│   ├── event/
│   │   ├── publisher/
│   │   │   └── warehouse.go      # RabbitMQ publishers
│   │   └── subscriber/
│   │       └── warehouse.go      # RabbitMQ subscribers
│   ├── repository/
│   │   ├── warehouse.go          # Data access layer
│   │   └── delivery_plan.go
│   ├── services/
│   │   └── service_ordering.go   # gRPC client to external service
│   └── usecase/
│       ├── factory.go            # Dependency injection (Factory struct)
│       ├── warehouse.go          # Business logic
│       └── delivery_plan.go
└── migrations/                   # Database migrations
    ├── 20260525_create_warehouses.up.sql
    └── 20260525_create_warehouses.down.sql
```

## Naming Conventions

### Files & Directories

- **snake_case** — All files and directories
- **Singular names** — `warehouse.go`, not `warehouses.go`
- **Module directories** — `handler/rest/warehouse/`, `handler/rest/delivery_plan/`
- **Request file naming** — `request_{action}.go` for root CRUD; `request_{subresource}_{action}.go` for child endpoints. Example: `request_create.go`, `request_update.go`, `request_document_create.go`, `request_document_delete.go`. Subresource before action.

### Go Code

- **PascalCase** — Exported types, functions, constants
- **camelCase** — Unexported variables, functions
- **ALL_CAPS** — Constants (rare, prefer PascalCase)

```go
// ✅ Correct
type Warehouse struct {}
func NewWarehouse() *Warehouse {}
const MaxRetries = 3

// ❌ Wrong
type warehouse struct {}  // Should be exported
func new_warehouse() {}   // Should be camelCase
const max_retries = 3     // Should be PascalCase
```

### Packages

- **Short, lowercase** — `handler`, `usecase`, `repository`
- **Single-word names preferred** — `warehouse`, `order`
- **Multi-word module dirs use underscore** — `business_partner/`, `item_category/` for handler module directories (exception: multi-word package names use underscore since Go requires single-word names)
- **Descriptive** — Package name should describe its purpose

## Two Naming Domains

All code generation is driven by two naming domains:

| Domain | Source | Example | Used By |
|---|---|---|---|
| **ENTITY_NAME** | SQL table name (PascalCase) | `Warehouse` | `entity/`, `repository/` |
| **MODULE_NAME** | Business feature (PascalCase) | `DeliveryPlan` | `usecase/`, `handler/`, `factory.go` |

**Key distinction:**
- **Entity** = database table (data layer)
- **Module** = business feature (business layer)

A module may use multiple entities. Example: `DeliveryPlan` module uses `DeliveryPlan`, `DeliveryPlanItem`, and `Item` entities.

## Clean Architecture Layers

### Layer Flow

```
HTTP Request
    ↓
Handler (transport layer)
    ↓
Request Struct (validation + transformation)
    ↓
Usecase (business logic)
    ↓
Repository (data access)
    ↓
Database
```

### Layer Rules

**Rule 1 — Never skip layers**
Every request must flow through all layers. No shortcuts.

```go
// ❌ Wrong — Handler bypassing usecase (repo exported, but shouldn't be)
func (h *Handler) Create(ctx *rest.Context) error {
    // Repo is unexported — you can't even access it. Always use usecase.
    // ...
}

// ✅ Correct — Handler calls usecase, usecase calls repository
func (h *Handler) Create(ctx *rest.Context) error {
    warehouse, err := h.uc.Warehouse.FindByID(id)
    // ...
}
```

**Rule 2 — Repository only accessible by usecase**
Handler and request structs MUST NOT access `.Repo` directly.

**Rule 3 — Return raw errors, never wrap**
The engine extracts error messages from raw errors. Wrapping loses the message.

```go
// ✅ Correct
return nil, err

// ❌ Wrong — wrapping loses the message
return nil, fmt.Errorf("failed to create: %w", err)
```

**Rule 4 — Publish events async with captured context**

```go
// ✅ Correct — capture context before goroutine
ctx := u.ctx
go publisher.WarehouseCreated(ctx, req)

// ❌ Wrong — loses context reference
go publisher.WarehouseCreated(u.ctx, req)
```

## Scaffold Tools

### Location

Scripts are in `skills/mbu-golang/scripts/`:
- `scaffold-init.sh` — Project skeleton
- `scaffold-entity.sh` — Entity + repository (chains to scaffold-repo.sh)
- `scaffold-repo.sh` — Repository (called by scaffold-entity.sh)
- `scaffold-usecase.sh` — Usecase
- `scaffold-factory.sh` — Factory wiring
- `scaffold-handler.sh` — Handler + request files
- `scaffold-grpc.sh` — gRPC handler + proto boilerplate
- `scaffold-publisher.sh` — Event publisher
- `scaffold-subscriber.sh` — Event subscriber
- `scaffold-service.sh` — gRPC client to external service (`src/services/service_{name}.go`)

See [Workflows](#workflows) section for automated task sequences using these tools.

### Usage

**Step 0 — Determine project type**

Ask the user:
- **Existing project?** — `go.mod` exists, proceed to Step 1
- **New project?** — Ask for Go module path (e.g., `github.com/mbu-id/service-myapi`)

For new projects:
```bash
mkdir -p ~/Works/Enigma/service-myapi
cd ~/Works/Enigma/service-myapi
go mod init github.com/mbu-id/service-myapi
```

**Step 1 — Initialize project**

```bash
SCRIPTS=~/Works/Enigma/claude-plugins/skills/mbu-golang/scripts
cd ~/Works/Enigma/service-myapi

# Generate project skeleton (creates .air.toml, .gitignore, .dockerignore,
# main.go, Makefile, Dockerfile, Helm chart, and directory structure)
$SCRIPTS/scaffold-init.sh

# Initialize entity module
cd entity && go mod init github.com/mbu-id/service-myapi/entity && cd ..

# Wire entity module (required for subdirectory modules)
go mod edit -require github.com/mbu-id/service-myapi/entity@v0.0.0
go mod edit -replace github.com/mbu-id/service-myapi/entity=./entity
```

The generated `Makefile` includes dev targets:
- `make dev-run` — Start Air hot-reload (requires `.air.toml`)
- `make run` — Direct `go run .`
- `make test` — Run all tests with `-v -count=1`
- `make tidy` — `go mod tidy && go build ./...`

**Step 2 — Generate entity + repository**

```bash
# From SQL CREATE TABLE statement
echo "CREATE TABLE warehouses (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(255) NOT NULL,
    address TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);" | $SCRIPTS/scaffold-entity.sh --from-stdin

# This generates:
# - entity/warehouse.go
# - src/repository/warehouse.go
```

**Notes:**
- **Enum types**: If the DDL has `CHECK IN` constraints, `scaffold-entity.sh` auto-detects them and generates typed Go enum constants + list functions. It also skips non-column tokens (`CONSTRAINT`, `INDEX`, `UNIQUE`, `PRIMARY`, `FOREIGN KEY`, etc.).
- **Soft-delete**: If the DDL has `is_deleted` or `deleted_at` columns, soft-delete is auto-configured in the repository.

**Step 3 — Register enums in meta handler**

Only needed if the entity has enum types (CHECK IN constraints):

```bash
$SCRIPTS/scaffold-meta.sh Branch

# Creates src/handler/rest/meta/handler.go if not exists
# Appends enum entries for BranchTypes(), BranchStatuses(), etc.
# GET /meta endpoint returns all enum values for frontend dropdowns
```

**Step 4 — Generate usecase**

```bash
$SCRIPTS/scaffold-usecase.sh Warehouse

# Generates: src/usecase/warehouse.go
```

**Step 5 — Wire into factory**

```bash
$SCRIPTS/scaffold-factory.sh Warehouse

# Updates: src/factory/factory.go
```

**Step 6 — Generate handler**

```bash
$SCRIPTS/scaffold-handler.sh Warehouse

# Generates:
# - src/handler/warehouse/create.go
# - src/handler/warehouse/update.go
# - src/handler/warehouse/show.go
# - src/handler/warehouse/list.go
# - src/handler/warehouse/delete.go
# Updates: src/handler/handler.go (route registration)
```

**Step 7 — Format + Resolve dependencies**

Always format before build — `gofmt` normalizes whitespace, alignment, and import grouping. Done automatically on `go build`, but running explicitly catches formatting issues early:

```bash
go fmt ./... && go mod tidy && go build ./...
```

### Force Overwrite

Use `-f` flag to overwrite existing files:

```bash
$SCRIPTS/scaffold-entity.sh --from-stdin -f
$SCRIPTS/scaffold-usecase.sh Warehouse -f
```

## Code Patterns

### Entity (Data Layer)

```go
package entity

import (
    "github.com/uptrace/bun"
    "github.com/google/uuid"
    "time"
)

type Warehouse struct {
    bun.BaseModel `bun:"table:warehouse,alias:w"`

    ID        uuid.UUID `bun:"id,pk,type:uuid,default:gen_random_uuid()"`
    Name      string    `bun:"name,notnull"`
    Address   string    `bun:"address,nullzero"`
    IsActive  bool      `bun:"is_active,notnull,default:true"`
    CreatedAt time.Time `bun:"created_at,notnull"`
    UpdatedAt time.Time `bun:"updated_at,notnull"`
}
```

**Bun tags:**
- `pk` — Primary key
- `notnull` — Required field
- `nullzero` — Nullable column; zero values (`""`, `0`, `false`) treated as NULL on write
- `type:uuid` — UUID type
- `type:jsonb` — JSONB type
- `default:gen_random_uuid()` — Auto-generate UUID
- `alias:a` — Table alias for queries

**Nullable fields:**
Plain types for most nullable columns — zero value in JSON (`""`, `0`, `false`). Only `*time.Time` and `*uuid.UUID` stay pointer for nullable FKs/timestamps.

### Enum Types (Auto-generated from DDL CHECK Constraints)

When a migration has CHECK IN constraints, `scaffold-entity.sh` auto-detects them and generates typed enum constants + list functions.

**DDL example:**
```sql
CREATE TABLE branch (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    type VARCHAR(50) NOT NULL,
    status VARCHAR(50) NOT NULL DEFAULT 'active',
    CONSTRAINT chk_type CHECK (type IN ('farm', 'flock', 'henhouse')),
    CONSTRAINT chk_status CHECK (status IN ('active', 'inactive'))
);
```

**Generated entity (auto-detected):**
```go
package entity

type BranchType string

const (
    BranchTypeFarm     BranchType = "farm"
    BranchTypeFlock    BranchType = "flock"
    BranchTypeHenhouse BranchType = "henhouse"
)

func BranchTypes() []BranchType {
    return []BranchType{BranchTypeFarm, BranchTypeFlock, BranchTypeHenhouse}
}

type BranchStatus string

const (
    BranchStatusActive   BranchStatus = "active"
    BranchStatusInactive BranchStatus = "inactive"
)

func BranchStatuses() []BranchStatus {
    return []BranchStatus{BranchStatusActive, BranchStatusInactive}
}

type Branch struct {
    bun.BaseModel `bun:"table:branch,alias:branch"`

    ID     uuid.UUID    `bun:"id,type:uuid,pk" json:"id"`
    Type   BranchType   `bun:"type" json:"type"`
    Status BranchStatus `bun:"status" json:"status"`
}
```

**Rules:**
- Enum type name = `{EntityName}{FieldPascalCase}` — e.g., `BranchType`, `BranchStatus`
- Constant suffix derived from value: ALL_CAPS for short values (`farm` → `Farm`), PascalCase for multi-word
- List function pluralizes correctly: `Status` → `Statuses`, `Type` → `Types`
- Entity field uses the typed enum instead of `string`
- Enum values are registered in the meta handler for frontend consumption

### Meta Handler (GET /meta)

Every service has a `GET /meta` endpoint that aggregates all enum values for frontend dropdowns.

**Implementation (`src/handler/rest/meta/handler.go`):**
```go
package meta

import (
    "github.com/mbu-id/engine/transport/rest"
    "{{SERVICE_MODULE}}/entity"
)

type handler struct{}

func (h *handler) show(ctx *rest.Context) (err error) {
    result := map[string]any{
        "branch": map[string]any{
            "type":   entity.BranchTypes(),
            "status": entity.BranchStatuses(),
        },
    }
    return ctx.Respond(result, nil)
}

func RegisterHandler(s *rest.RestServer) {
    h := &handler{}
    s.GET("/meta", h.show, nil)
}
```

**Registration in `src/handler.go`:**
```go
import hmeta "{{SERVICE_MODULE}}/src/handler/rest/meta"

func RegisterRestRoutes(s *rest.RestServer) {
    hmeta.RegisterHandler(s)
}
```

**Scaffold:**
```bash
SCRIPTS=~/Works/Enigma/claude-plugins/skills/mbu-golang/scripts
cd ~/Works/Enigma/service-myapi

# Register entity enums in meta handler
$SCRIPTS/scaffold-meta.sh Branch
# Creates src/handler/rest/meta/handler.go if not exists
# Appends enum entries for BranchTypes(), BranchStatuses(), etc.
```

**Key points:**
- The meta handler is auto-scaffolded — no manual creation needed
- `scaffold-entity.sh` already tells you to run `scaffold-meta.sh` next
- Frontend consumes `GET /meta` to populate dropdowns and validate form inputs client-side
- Separate meta handlers for non-entity data (e.g., currencies) go in `src/handler/rest/meta/currency/handler.go`

### Repository (Data Access Layer)

```go
package repository

import (
    "github.com/mbu-id/engine/ds/postgres"
    "github.com/mbu-id/service-myapi/entity"
)

type WarehouseRepository struct {
    *postgres.BaseRepository[entity.Warehouse]
}

func NewWarehouseRepository() *WarehouseRepository {
    return &WarehouseRepository{
        BaseRepository: postgres.NewBaseRepository[entity.Warehouse](
            postgres.GetDB(),                     // DB from engine's postgres package
            "warehouse",                          // table name
            []string{"w.name", "w.address"},      // searchable fields
            []string{},                           // default relations
            false,                                // soft delete disabled
        ),
    }
}

// Override WithContext for method chaining
func (r *WarehouseRepository) WithContext(ctx context.Context) common.BaseRepositoryInterface[entity.Warehouse] {
    return &WarehouseRepository{
        BaseRepository: r.BaseRepository.WithCtx(ctx).(*postgres.BaseRepository[entity.Warehouse]),
    }
}

// Custom query methods
func (r *WarehouseRepository) FindByName(ctx context.Context, name string) (*entity.Warehouse, error) {
    return r.WithContext(ctx).FindOne(func(q *bun.SelectQuery) *bun.SelectQuery {
        return q.Where("name = ?", name)
    })
}
```

### Usecase (Business Logic Layer)

```go
package usecase

import (
    "context"
    "github.com/mbu-id/mbu-service/src/repository"
    "github.com/mbu-id/mbu-service/entity"
)

type WarehouseUsecase struct {
    repo *repository.WarehouseRepository  // unexported — only usecase can access
    ctx  context.Context
}

func NewWarehouseUsecase() *WarehouseUsecase {
    return &WarehouseUsecase{
        repo: repository.NewWarehouseRepository(),
        ctx:  context.Background(),
    }
}

func (u *WarehouseUsecase) Create(req *entity.Warehouse) error {
    if err := u.repo.WithContext(u.ctx).Insert(req); err != nil {
        return err
    }
    ctx := u.ctx
    go publisher.WarehouseCreated(ctx, req)
    return nil
}

func (u *WarehouseUsecase) FindByID(id string) (*entity.Warehouse, error) {
    return u.repo.WithContext(u.ctx).FindByID(id)
}

func (u *WarehouseUsecase) Update(req *entity.Warehouse) error {
    if err := u.repo.WithContext(u.ctx).Update(req); err != nil {
        return err
    }
    ctx := u.ctx
    go publisher.WarehouseUpdated(ctx, req)
    return nil
}
```

### Handler (Transport Layer)

```go
package warehouse

import (
    "github.com/mbu-id/engine/transport/rest"
    "github.com/mbu-id/mbu-service/src/usecase"
)

type handler struct {
    uc *usecase.Factory
}

func CreateHandler(uc *usecase.Factory) *handler {
    return &handler{uc: uc}
}

func (h *handler) create(ctx *rest.Context) error {
    var req createRequest
    if err := ctx.Bind(req.with(ctx, h.uc)); err != nil {
        return ctx.Respond(nil, err)
    }
    result, err := req.execute()
    return ctx.Respond(result, err)
}
```

**Request Validation Flow:**

`ctx.Bind()` automatically handles validation via the `validate.Request` interface:

1. **Bind** — Decodes JSON body / query params / path params into the request struct
2. **Validate** — Calls `req.Validate()` if the struct implements `validate.Request`
3. **Messages** — Applies custom error messages from `req.Messages()`
4. **Return** — Returns validation errors or nil if valid

**Source:** `github.com/mbu-id/engine/transport/rest/context.go`

```go
// Inside ctx.Bind():
if err := c.Validate(v); !err.Valid {
    return err
}

// Inside ctx.Validate():
if vr, ok := obj.(validate.Request); ok {
    resp = c.validator.Request(vr)  // Calls vr.Validate() and vr.Messages()
}
```

**Key Point:** You never call `Validate()` explicitly in handler code. `ctx.Bind()` does it automatically.
```

### Create Request Pattern

```go
package warehouse

import (
	"context"
	"time"

	"github.com/mbu-id/engine/common"
	"github.com/mbu-id/engine/transport/rest"
	"github.com/mbu-id/engine/validate"
	"github.com/mbu-id/service-myapi/src/usecase"
	"github.com/mbu-id/service-myapi/entity"
)

type createRequest struct {
	CompanyID string  `json:"company_id" valid:"required|uuid"`
	Code      string  `json:"code" valid:"required"`
	Name      string  `json:"name" valid:"required"`
	Phone     string  `json:"phone"`
	Email     string  `json:"email"`

	company *entity.Company // cached during Validate
	ctx     context.Context
	uc      *usecase.Factory
}

// Validate — FK existence checks with field guard (bulk validation safety)
func (r *createRequest) Validate() *validate.Response {
	v := validate.NewResponse()
	var err error

	// Field guard: FindByID only fires when the FK field is non-empty.
	// Validation runs as bulk — required fields may fail first, but we
	// still guard every usecase call to prevent empty-string DB lookups.
	if r.CompanyID != "" {
		if r.company, err = r.uc.Company.FindByID(r.CompanyID); err != nil {
			v.SetError("company_id.invalid", "company not found")
		}
	}

	return v
}

// Messages — custom validation error messages
func (r *createRequest) Messages() map[string]string {
	return map[string]string{}
}

// toEntity — transform request to entity
func (r *createRequest) toEntity() *entity.Warehouse {
	return &entity.Warehouse{
		CompanyID: r.company.ID,  // from cached entity, no uuid.Parse
		Code:      r.Code,
		Name:      r.Name,
		Phone:     r.Phone,
		Email:     r.Email,
		Status:    "ACTIVE",
		CreatedAt: time.Now(),
	}
}

// execute — persist via usecase and publish event
func (r *createRequest) execute() (*rest.ResponseBody, error) {
	mx := r.toEntity()
	if err := r.uc.Warehouse.Create(mx); err != nil {
		return nil, err
	}
	return rest.NewResponseBody(mx), nil
}

// with — inject context and factory
func (r *createRequest) with(ctx context.Context, uc *usecase.Factory) *createRequest {
	r.uc = uc.WithContext(ctx)
	r.ctx = ctx
	return r
}
```

### Update Request Pattern — PUT with apply(existing)

Updates cache the existing entity in `Validate()`, then use `apply(existing)` to overlay fields. This lets partial-update semantics coexist with PUT-like simplicity — non-sent fields keep their DB values, FK lookups resolve to cached entities (no re-parse).

```go
package warehouse

import (
	"context"
	"time"

	"github.com/google/uuid"
	"github.com/mbu-id/engine/common"
	"github.com/mbu-id/engine/transport/rest"
	"github.com/mbu-id/engine/validate"
	"github.com/mbu-id/service-myapi/src/usecase"
	"github.com/mbu-id/service-myapi/entity"
)

type updateRequest struct {
	ID        string `json:"id" param:"id" valid:"required|uuid"`
	CompanyID string `json:"company_id" valid:"required|uuid"`
	Code      string `json:"code"`
	Name      string `json:"name"`
	Phone     string `json:"phone"`
	Email     string `json:"email"`
	Status    string `json:"status"`

	ctx      context.Context
	uc       *usecase.Factory
	session  *common.SessionClaims
	company  *entity.Company   // cached during Validate
	existing *entity.Warehouse // cached during Validate
}

// Validate — cache existing + resolve FK references with field guards
func (r *updateRequest) Validate() *validate.Response {
	v := validate.NewResponse()
	var err error

	// Guard every FindByID: validation is bulk, required fields may be empty
	if r.ID != "" {
		if r.existing, err = r.uc.Warehouse.FindByID(r.ID); err != nil {
			v.SetError("id.invalid", "data not found.")
		}
	}
	if r.CompanyID != "" {
		if r.company, err = r.uc.Company.FindByID(r.CompanyID); err != nil {
			v.SetError("company_id.invalid", "company not found")
		}
	}

	return v
}

func (r *updateRequest) Messages() map[string]string {
	return map[string]string{}
}

// apply overlays request fields onto the cached existing entity.
func (r *updateRequest) apply(e *entity.Warehouse) {
	e.CompanyID = r.company.ID
	e.Code = r.Code
	e.Name = r.Name
	e.Phone = r.Phone
	e.Email = r.Email
	e.Status = r.Status
	e.UpdatedAt = time.Now()
	if r.session != nil && r.session.UserID != "" {
		uid := uuid.MustParse(r.session.UserID)
		e.UpdatedBy = &uid
	}
}

// execute — apply overlays onto cached entity, persist via usecase
func (r *updateRequest) execute() (*rest.ResponseBody, error) {
	r.apply(r.existing)
	if err := r.uc.Warehouse.Update(r.existing); err != nil {
		return nil, err
	}
	return rest.NewResponseBody(r.existing), nil
}

func (r *updateRequest) with(ctx context.Context, uc *usecase.Factory) *updateRequest {
	r.uc = uc.WithContext(ctx)
	r.ctx = ctx
	r.session = common.GetContextSession(ctx)
	return r
}
```

### Delete Request Pattern

```go
package warehouse

import (
	"context"

	"github.com/mbu-id/service-myapi/entity"
	"github.com/mbu-id/service-myapi/src/usecase"

	"github.com/mbu-id/engine/transport/rest"
	"github.com/mbu-id/engine/validate"
)

type deleteRequest struct {
	ID string `json:"id" param:"id" valid:"required|uuid"`

	ctx      context.Context
	uc       *usecase.Factory
	existing *entity.Warehouse
}

func (r *deleteRequest) Validate() *validate.Response {
	v := validate.NewResponse()

	var err error
	if r.ID != "" {
		r.existing, err = r.uc.Warehouse.FindByID(r.ID)
		if err != nil {
			v.SetError("id.invalid", "data not found.")
		}
	}

	return v
}

func (r *deleteRequest) Messages() map[string]string {
	return map[string]string{}
}

func (r *deleteRequest) execute() (*rest.ResponseBody, error) {
	if err := r.uc.Warehouse.Delete(r.ID); err != nil {
		return nil, err
	}
	return &rest.ResponseBody{
		Message: "Warehouse deleted successfully",
	}, nil
}

func (r *deleteRequest) with(ctx context.Context, uc *usecase.Factory) *deleteRequest {
	r.uc = uc.WithContext(ctx)
	r.ctx = ctx
	return r
}
```

### Factory (Dependency Injection)

Factory lives in `src/usecase/factory.go` (package `usecase`). A single `Factory` struct holds all usecase instances.

```go
package usecase

import (
    "context"
)

// Factory holds all usecase instances for the service.
type Factory struct {
    Warehouse *WarehouseUsecase
}

// NewFactory creates a new Factory with all usecases.
// Takes NO parameters — repositories call postgres.GetDB() internally.
func NewFactory() *Factory {
    return &Factory{
        Warehouse: NewWarehouseUsecase(),
    }
}

// WithContext returns a new Factory with context propagated to all usecases.
func (f *Factory) WithContext(ctx context.Context) *Factory {
    return &Factory{
        Warehouse: f.Warehouse.WithContext(ctx),
    }
}
```

## Validation

### Validation Tags

| Tag | Description | Example |
|---|---|---|
| `required` | Field must not be empty | `valid:"required"` |
| `email` | Valid email format | `valid:"email"` |
| `uuid` | Valid UUID format | `valid:"uuid"` |
| `min:n` | Minimum value/length | `valid:"min:3"` |
| `max:n` | Maximum value/length | `valid:"max:100"` |
| `gte:n` | Greater than or equal | `valid:"gte:0"` |
| `lte:n` | Less than or equal | `valid:"lte:100"` |
| `in:A,B,C` | Value must be in list (comma-sep). `IsIn()` returns `true` for empty — pair with `required` when mandatory | `valid:"required|in:admin,user"` / `valid:"in:A,B"` (optional) |
| `password` | Password strength | `valid:"password"` |
| `phone` | Phone number format | `valid:"phone"` |
| `alphanum` | Alphanumeric only | `valid:"alphanum"` |
| `numeric` | Numeric only | `valid:"numeric"` |

### Field Guard Rule — Critical for Bulk Validation

**Every FindByID call in Validate() must be field-guarded.** Engine validation is bulk — all rules fire regardless of prior failures. If a `required` field is empty, the Validate method still runs, and `FindByID("")` will panic or DB-error.

```go
// ✅ CORRECT — guard every FK lookup
if r.CompanyID != "" {
    if r.company, err = r.uc.Company.FindByID(r.CompanyID); err != nil {
        v.SetError("company_id.invalid", "company not found")
    }
}

// ❌ WRONG — FindByID("") panics on empty string
if r.company, err = r.uc.Company.FindByID(r.CompanyID); err != nil {
    v.SetError("company_id.invalid", "company not found")
}
```

Rules:
- `string` FK (`valid:"required|uuid"`) → guard with `if r.XxxID != "" { ... }`
- `*string` FK (`valid:"omitempty|uuid"`) → guard with `if r.XxxID != nil { ... }`
- Applies to **create**, **update**, and **delete** request Validate() methods
- `request_get.go` is exempt — ID comes from `ctx.Param("id")`, not body binding

### Enum Validation Tags (`in:`) — Mirror DB CHECK Constraints

Entity enums auto-generated from DDL CHECK constraints (`scaffold-entity.sh`) must have matching `in:` tags in create/update request structs.

**Rules:**
1. Add `in:VAL1,VAL2,...` to every request field that maps to a DB CHECK IN column
2. **Required enums** → `valid:"required|in:A,B,C"` — field must be present AND valid value
3. **Nullable/optional enums** → `valid:"in:A,B,C"` alone — `IsIn()` returns `true` for empty/zero values, so `required` is unnecessary
4. Values must match the entity constants **case-sensitive** (e.g., `ORGANIZATION` not `Organization`)

```go
// Entity enum (auto-generated from DDL)
const (
    PartnerTypeOrganization PartnerType = "ORGANIZATION"
    PartnerTypePerson       PartnerType = "PERSON"
)

// ✅ Correct — create request: required enum
type createRequest struct {
    PartnerType string `json:"partner_type" valid:"required|in:ORGANIZATION,PERSON"`
}

// ✅ Correct — update request: same tag
type updateRequest struct {
    PartnerType string `json:"partner_type" valid:"in:ORGANIZATION,PERSON"`
}

// ✅ Correct — optional enum with bare in:
type updateRequest struct {
    Status string `json:"status" valid:"in:ACTIVE,INACTIVE"`
}

// ❌ Wrong — missing in:, allows invalid DB values
type createRequest struct {
    PartnerType string `json:"partner_type"`        // valid:"in:..." missing
}
```

**Why this matters:** Without `in:` tags, invalid enum values pass Go validation and fail at the DB constraint layer with a 500. The `in:` tag catches them early with a 422 validation response.

**Don't forget the meta handler** — registered enum types are served via `GET /meta` for frontend dropdowns. The `in:` tag in requests and the meta handler must stay in sync.

### Custom Validation

Use `validate.NewResponse()` + `v.SetError()`. Cache FK entities in struct fields
during validation — use `.ID` in `toEntity()`, never re-parse with `uuid.Parse()`.

```go
type createRequest struct {
	CompanyID string `json:"company_id" valid:"required|uuid"`
	VendorID  string `json:"vendor_id" valid:"required|uuid"`

	company *entity.Company // cached during Validate
	vendor  *entity.Vendor  // cached during Validate
	// ...
}

func (r *createRequest) Validate() *validate.Response {
	v := validate.NewResponse()
	var err error

	if r.CompanyID != "" {
		if r.company, err = r.uc.Company.FindByID(r.CompanyID); err != nil {
			v.SetError("company_id.invalid", "company not found")
		}
	}
	if r.VendorID != "" {
		if r.vendor, err = r.uc.Vendor.FindByID(r.VendorID); err != nil {
			v.SetError("vendor_id.invalid", "vendor not found")
		}
	}

	return v
}

func (r *createRequest) toEntity() *entity.Warehouse {
	return &entity.Warehouse{
		CompanyID: r.company.ID,
		VendorID:  r.vendor.ID,   // ✅ from cached entity, no uuid.Parse
		// ...
	}
}
```

## Error Handling

### Return Raw Errors

```go
// ✅ Correct
warehouse, err := u.repo.WithContext(u.ctx).FindByID(id)
if err != nil {
    return nil, err
}

// ❌ Wrong — wrapping loses error message
if err != nil {
    return nil, fmt.Errorf("failed to find warehouse: %w", err)
}
```

### Custom Errors

```go
import "errors"

var (
    ErrWarehouseNotFound = errors.New("warehouse not found")
    ErrInvalidCoordinates = errors.New("invalid coordinates")
)

func (u *WarehouseUsecase) Show(id string) (*entity.Warehouse, error) {
    warehouse, err := u.Repo.WithContext(u.ctx).FindByID(id)
    if err != nil {
        return nil, ErrWarehouseNotFound
    }
    return warehouse, nil
}
```

## OAS Response Schemas (API Contracts)

Engine standard response envelope. Every service API contract must use these schemas.

### Response Envelope

```yaml
ResponseBody:
  type: object
  properties:
    success:
      type: boolean
      description: Indicates if the request was successful
    message:
      type: string
      description: Human-readable status message
      example: success
    data:
      description: Response payload (null on errors)
      nullable: true
    errors:
      description: Error details (null on success)
      nullable: true
    meta:
      $ref: "#/components/schemas/Meta"
  required:
    - success
    - message
```

### Meta (Pagination)

```yaml
Meta:
  type: object
  properties:
    page:
      type: integer
    page_size:
      type: integer
    total:
      type: integer
    total_pages:
      type: integer
    has_next:
      type: boolean
    has_prev:
      type: boolean
```

### Typed Response Pattern

Use `allOf` to compose domain data with the standard envelope:

```yaml
WarehouseResponse:
  allOf:
    - $ref: "#/components/schemas/ResponseBody"
    - type: object
      properties:
        data:
          $ref: "#/components/schemas/Warehouse"

WarehouseListResponse:
  allOf:
    - $ref: "#/components/schemas/ResponseBody"
    - type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Warehouse"
        meta:
          $ref: "#/components/schemas/Meta"
```

### Standard Error Responses

All services share these reusable `responses` components:

```yaml
components:
  responses:
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            allOf:
              - $ref: "#/components/schemas/ResponseBody"
              - type: object
                properties:
                  success:
                    enum: [false]
                  message:
                    example: resource not found
                  data:
                    nullable: true
                    example: null
                  errors:
                    nullable: true
                    example: null
                  meta:
                    nullable: true

    BadRequest:
      description: Bad request
      content:
        application/json:
          schema:
            allOf:
              - $ref: "#/components/schemas/ResponseBody"
              - type: object
                properties:
                  success:
                    enum: [false]
                  message:
                    example: invalid request body. please check your input format
                  data:
                    nullable: true
                    example: null
                  errors:
                    nullable: true
                    example: null
                  meta:
                    nullable: true

    Conflict:
      description: Conflict
      content:
        application/json:
          schema:
            allOf:
              - $ref: "#/components/schemas/ResponseBody"
              - type: object
                properties:
                  success:
                    enum: [false]
                  message:
                    example: conflict
                  data:
                    nullable: true
                    example: null
                  errors:
                    nullable: true
                    example: null
                  meta:
                    nullable: true

    ValidationError:
      description: Validation failed
      content:
        application/json:
          schema:
            allOf:
              - $ref: "#/components/schemas/ResponseBody"
              - type: object
                properties:
                  success:
                    enum: [false]
                  message:
                    example: validation failed
                  data:
                    nullable: true
                    example: null
                  errors:
                    type: object
                    description: Field-level validation messages
                    example:
                      name: The name field is required
                      quantity: Must be a positive integer
                  meta:
                    nullable: true

    RateLimited:
      description: Rate limited
      content:
        application/json:
          schema:
            allOf:
              - $ref: "#/components/schemas/ResponseBody"
              - type: object
                properties:
                  success:
                    enum: [false]
                  message:
                    example: too many requests
                  data:
                    nullable: true
                    example: null
                  errors:
                    nullable: true
                    example: null
                  meta:
                    nullable: true

    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            allOf:
              - $ref: "#/components/schemas/ResponseBody"
              - type: object
                properties:
                  success:
                    enum: [false]
                  message:
                    example: internal server error
                  data:
                    nullable: true
                    example: null
                  errors:
                    type: string
                    description: Raw error message (not exposed in production)
                  meta:
                    nullable: true
```

### Response Envelope Rules

| Field | Success | Validation Error | HTTPError | Internal Error |
|---|---|---|---|---|
| `success` | `true` | `false` | `false` | `false` |
| `message` | `"success"` | `"validation failed"` | per error type | `"internal server error"` |
| `data` | payload | `null` | `null` | `null` |
| `errors` | `null` | field → message map | `null` | raw error string |
| `meta` | pagination | `null` | `null` | `null` |

### How to use in API contracts

```yaml
# 1. Reference ResponseBody + Meta as component schemas
# 2. Define domain schemas (Upload, Warehouse, etc.)
# 3. Define typed response wrappers with allOf
# 4. Reuse standard error responses via $ref

paths:
  /warehouses:
    get:
      responses:
        "200":
          description: Warehouse list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WarehouseListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"

components:
  schemas:
    ResponseBody:
      # ... copy from above
    Meta:
      # ... copy from above
    Warehouse:
      type: object
      properties:
        id: ...
    WarehouseListResponse:
      allOf:
        - $ref: "#/components/schemas/ResponseBody"
        - type: object
          properties:
            data:
              type: array
              items:
                $ref: "#/components/schemas/Warehouse"
            meta:
              $ref: "#/components/schemas/Meta"
  responses:
    BadRequest:
      # ... copy from above
```

### Go → OAS Mapping

| Engine Go Type | OAS Schema |
|---|---|
| `rest.ResponseBody` | `ResponseBody` |
| `rest.Meta` | `Meta` |
| `rest.HTTPError` | `NotFound`, `BadRequest`, `Conflict`, etc. |
| `validate.Response` | `ValidationError` |
| Internal/unknown error | `InternalError` |

Place the standard schemas in each service's `api-contract.yaml`. All service contracts share these identical schemas.

## Event Publishing

### Publisher Pattern

```go
package publisher

import (
    "context"
    "github.com/mbu-id/engine/broker/rabbitmq"
)

type WarehouseCreatedPayload struct {
    ID      string `json:"id"`
    Name    string `json:"name"`
    Address string `json:"address"`
}

func WarehouseCreated(ctx context.Context, payload WarehouseCreatedPayload) error {
    return rabbitmq.Publish(ctx, "warehouse.created", payload)
}
```

### Event Naming

Use **dot notation**: `noun.verb`

```
✅ Correct:
- warehouse.created
- warehouse.updated
- delivery.plan.published

❌ Wrong:
- warehouse:created  (colon separator)
- WarehouseCreated   (PascalCase)
- warehouse_created  (underscore)
```

### Async Publishing

Publishers are called from the usecase layer via `go` for fire-and-forget. The context must be captured before the goroutine:

```go
func (u *WarehouseUsecase) Create(req *entity.Warehouse) error {
    if err := u.repo.Insert(req); err != nil {
        return err
    }
    ctx := u.ctx  // capture before goroutine
    go publisher.WarehouseCreated(ctx, req)
    return nil
}
```

The usecase template auto-wires these calls. Only scaffold additional publishers for custom events beyond Created/Updated/Deleted.

## Testing

### Repository Tests

```go
package repository_test

import (
    "context"
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/mbu-id/service-myapi/src/repository"
    "github.com/mbu-id/service-myapi/entity"
)

func TestWarehouseRepository_Create(t *testing.T) {
    // Setup test database
    repo := repository.NewWarehouseRepository()
    ctx := context.Background()

    // Test data
    warehouse := &entity.Warehouse{
        Name:    "Test Warehouse",
        Address: "123 Test St",
    }

    // Execute
    err := repo.WithContext(ctx).Insert(warehouse)

    // Assert
    assert.NoError(t, err)
    assert.NotEmpty(t, warehouse.ID)
}
```

### Usecase Tests

Usecases depend on repositories internally injected via `NewXxxUsecase()`. Test with a real database connection:

```go
package usecase_test

import (
    "context"
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/mbu-id/mbu-service/entity"
    "github.com/mbu-id/mbu-service/src/usecase"
)

func TestWarehouseUsecase_Create(t *testing.T) {
    uc := usecase.NewWarehouseUsecase()
    ctx := context.Background()

    warehouse := &entity.Warehouse{
        Name:    "Test Warehouse",
        Address: "123 Test St",
    }

    err := uc.WithContext(ctx).Create(warehouse)

    assert.NoError(t, err)
    assert.NotEmpty(t, warehouse.ID)
}
```

## Best Practices

### 1. Scaffold First, Customize Later

Always generate skeleton code with scaffold scripts, then customize.

### 2. Keep Usecases Thin

Business logic should be simple. Complex logic goes in domain services.

### 3. Use Transactions for Multiple Operations

```go
err := repo.RunInTx(ctx, func(ctx context.Context, tx bun.Tx) error {
    // Multiple operations...
    return nil
})
```

### 4. Always Run `go mod tidy`

After scaffolding or adding dependencies:

```bash
go mod tidy && go build ./...
```

### 5. Use Context Everywhere

```go
// ✅ Correct
repo.WithContext(ctx).FindByID(id)

// ❌ Wrong
repo.FindByID(id)
```

### 6. Validate at the Boundary

Validate input in request structs, not in usecases or repositories.

### 7. Cache FK Entities in Validate() + Field Guard

For create/update requests, cache foreign key entities in `Validate()` via usecase
lookup. Use the cached entity's `.ID` field in `toEntity()` — never parse
UUIDs again with `uuid.Parse()` and discard the error.

**Always field-guard every FindByID.** Validation is bulk — `Validate()` fires even when
`required` fields fail. An empty FK string passed to `FindByID("")` will panic or DB-error.

```go
// ✅ Correct — field guard + cache in Validate, use .ID
func (r *createRequest) Validate() *validate.Response {
    v := validate.NewResponse()
    var err error
    if r.CompanyID != "" {
        if r.company, err = r.uc.Company.FindByID(r.CompanyID); err != nil {
            v.SetError("company_id.invalid", "company not found")
        }
    }
    return v
}
func (r *createRequest) toEntity() *entity.Warehouse {
    return &entity.Warehouse{CompanyID: r.company.ID} // .ID from cache
}

// ❌ Wrong — no field guard, FindByID("") panics
if r.company, err = r.uc.Company.FindByID(r.CompanyID); err != nil {
    v.SetError("company_id.invalid", "company not found")
}

// ❌ Wrong — uuid.Parse discards errors
companyID, _ := uuid.Parse(r.CompanyID)
```

### 8. Update Pattern: `apply(existing)` + Session + UpdatedBy

Update requests must:
1. Cache `existing *entity.Xxx` via FindByID in `Validate()` — this is the base entity
2. Define `apply(e *entity.Xxx)` to overlay request fields onto the cached entity
3. Use `common.GetContextSession(ctx)` in `with()` to capture session for `UpdatedBy`
4. Pass `r.session.UserID` as `uuid.MustParse(...)` for `UpdatedBy *uuid.UUID`
5. Call `execute()` → `r.apply(r.existing)` → `r.uc.Xxx.Update(r.existing)`

```go
// ✅ Correct — update pattern
type updateRequest struct {
    CompanyID string `json:"company_id" valid:"required|uuid"`
    // ...
    existing *entity.Warehouse  // cached in Validate
    session  *common.SessionClaims
}

func (r *updateRequest) Validate() *validate.Response {
    // field-guarded FindByID stores into r.existing
}

func (r *updateRequest) apply(e *entity.Warehouse) {
    // overlay fields
    e.UpdatedAt = time.Now()
    if r.session != nil && r.session.UserID != "" {
        uid := uuid.MustParse(r.session.UserID)
        e.UpdatedBy = &uid
    }
}

func (r *updateRequest) execute() (*rest.ResponseBody, error) {
    r.apply(r.existing)  // apply overlays onto cached existing
    if err := r.uc.Warehouse.Update(r.existing); err != nil {
        return nil, err
    }
    return rest.NewResponseBody(r.existing), nil
}

func (r *updateRequest) with(ctx context.Context, uc *usecase.Factory) *updateRequest {
    r.uc = uc.WithContext(ctx)
    r.ctx = ctx
    r.session = common.GetContextSession(ctx)
    return r
}

// ❌ Wrong — toEntity() rebuilds from scratch, loses DB defaults + misses UpdatedBy
func (r *updateRequest) execute() (*rest.ResponseBody, error) {
    mx := r.toEntity()  // loses fields not sent by client
    // ...
}
```

### 9. Session Propagation

Request structs don't carry `Session` as a JSON field. Capture in `with()`:
- updateRequest: `r.session = common.GetContextSession(ctx)`
- Only use for `UpdatedBy` in `apply()` — not for permission checks (those are middleware)

### 10. Multi-Word Handler Directories Use Underscore

```
handler/business_partner/   ✅
handler/item_category/      ✅
handler/businesspartner/    ❌
```

Single-word directories stay flat: `warehouse/`, `order/`.

### 11. Use Plain Types in Requests (No `*string`)

Entity and request structs use plain types for string/bool/int fields. Nullable columns show
zero values in JSON (`""`, `false`, `0`). Only `*time.Time` and `*uuid.UUID` stay pointer
for nullable timestamps and FKs.

```go
type updateRequest struct {
    Name   string `json:"name"`
    Status string `json:"status"`
}
func (r *updateRequest) toEntity() *entity.Warehouse {
    return &entity.Warehouse{
        Name:   r.Name,
        Status: r.Status,
        UpdatedAt: time.Now(),
    }
}
```

### 10. Nested Item Arrays in Requests

For requests with sub-item arrays (order lines, plan items, etc.), split the sub-item type + validator into a dedicated `request_{item}.go` file within the same package.

**Pattern — three layers of validation:**

```go
// --- request_item.go — sub-item type + per-item validation ---
type itemRequest struct {
    ID     string `json:"id"` // omit for new, include uuid for updates
    ItemID string `json:"item_id"`
    Qty    int    `json:"qty"`
}

func (r *itemRequest) Validate(v *validate.Response, key int) {
    if r.ItemID == "" {
        v.SetError(fmt.Sprintf("items.%d.item_id.required", key), "item is required")
    }
    if r.Qty <= 0 {
        v.SetError(fmt.Sprintf("items.%d.qty.invalid", key), "quantity must be > 0")
    }
}

// --- request_create.go — collection-level validation ---
type createRequest struct {
    Items []itemRequest `json:"items" valid:"required|min:1"`
    // ...
}

func (r *createRequest) Validate() *validate.Response {
    v := validate.NewResponse()

    // 1. Empty check
    if len(r.Items) == 0 {
        v.SetError("items.required", "items cannot be empty")
        return v
    }

    // 2. Per-item validation + dedup
    seen := make(map[string]bool)
    for k := range r.Items {
        r.Items[k].Validate(v, k)
        if r.Items[k].ItemID != "" {
            if seen[r.Items[k].ItemID] {
                v.SetError(fmt.Sprintf("items.%d.item_id.duplicate", k), "duplicate item")
            }
            seen[r.Items[k].ItemID] = true
        }
    }

    // 3. Cross-item checks (e.g., FK balance, totals) — only if items valid
    if v.Valid {
        for k, it := range r.Items {
            // DB check per item
        }
    }

    return v
}
```

**Execution — map slices in `execute()`:**
```go
func (r *createRequest) execute() (*rest.ResponseBody, error) {
    items := make([]entity.ModuleItem, len(r.Items))
    for i, it := range r.Items {
        items[i] = entity.ModuleItem{
            ItemID: uuid.MustParse(it.ItemID),
            Qty:    it.Qty,
        }
    }
    result, err := r.uc.Module.CreateWithItems(r.toEntity(), items)
    return rest.NewResponseBody(result), err
}
```

**Rules:**
1. Sub-item type + `Validate(v, key int)` lives in `request_{item}.go` — one file per sub-type.
2. The sub-validator takes `*validate.Response` and index key, generates scoped error keys (`items.3.qty.invalid`).
3. Parent `Validate()` owns collection-level concerns: empty, duplicates, cross-item FK checks.
4. Dedup and cross-item checks run inside `v.Valid` guard to avoid redundant errors.

### 11. Transactions for Multi-Query Usecases

If a usecase method executes more than one DB operation, wrap them in a transaction. Two patterns:

**Single repository `RunInTxWithRepo`:**

```go
func (u *OrderUsecase) CreateWithItems(order *entity.Order, items []entity.OrderItem) error {
    return u.repo.RunInTxWithRepo(u.ctx, func(txRepo *repository.OrderRepository) error {
        if err := txRepo.Insert(order); err != nil {
            return err
        }
        for i := range items {
            items[i].OrderID = order.ID
            if err := txRepo.Insert(&items[i]); err != nil {
                return err
            }
        }
        return nil
    })
}
```

**Multiple repositories `RunInTx`:**

```go
func (u *PaymentUsecase) CreatePayment(payment *entity.Payment, invoice *entity.Invoice) error {
    return u.repo.RunInTx(u.ctx, func(txCtx context.Context, tx bun.Tx) error {
        // Create payment repo bound to this transaction
        paymentRepoTx := u.repo.BaseRepository.WithTx(txCtx, tx)
        if err := paymentRepoTx.Insert(payment); err != nil {
            return err
        }

        // Same for invoice repo — different repository, same transaction
        invoiceRepoTx := u.repoInvoice.BaseRepository.WithTx(txCtx, tx)
        if err := invoiceRepoTx.Update(invoice); err != nil {
            return err
        }

        return nil
    })
}
```

**Rules:**
1. Use `RunInTxWithRepo` for single-repository multi-ops (insert order + items in same repo).
2. Use `RunInTx` for cross-repository operations (order repo + payment repo).
3. The `WithTx` creates a new repo instance bound to the transaction — always use it inside the callback, never the outer repo.
4. Publisher calls go AFTER `RunInTx` returns nil (not inside the callback), so events are only emitted on commit.

```go
func (u *OrderUsecase) CreateWithItems(order *entity.Order, items []entity.OrderItem) error {
    if err := u.repo.RunInTxWithRepo(u.ctx, func(txRepo *repository.OrderRepository) error {
        // ... all DB ops
        return nil
    }); err != nil {
        return err
    }
    ctx := u.ctx   // capture after tx success
    go publisher.OrderCreated(ctx, order)
    return nil
}
```

## gRPC & Proto

### Proto Module Structure

Proto definitions live in a separate Go module:

```
service-order/
├── go.mod                    # Main service module
├── proto/
│   ├── go.mod                # Separate proto module
│   ├── order.proto           # Proto definition (manual)
│   ├── order.pb.go           # Generated by protoc
│   ├── order_grpc.pb.go      # Generated by protoc
│   ├── constant.go           # ServiceName constant
│   └── converter.go          # Entity ↔ Proto converters
└── src/handler/grpc/
    └── order.go              # gRPC handler implementation
```

### Proto File Conventions

```protobuf
syntax = "proto3";

package order;
option go_package = "github.com/mbu-id/service-order/proto;proto";

message Order {
  string id = 1;
  string code = 2;
  string name = 3;
  double total_charges = 4;
}

message ShowRequest {
  string id = 1;
}

message OrderResponse {
  Order order = 1;
}

service OrderService {
  rpc Show(ShowRequest) returns (OrderResponse);
  rpc List(ListRequest) returns (ListResponse);
}
```

### Scaffold gRPC Handler

**Prerequisites:**
1. Write `.proto` file manually in `proto/` directory
2. Define service and RPC methods

**Generate boilerplate:**

```bash
SCRIPTS=~/Works/Enigma/claude-plugins/skills/mbu-golang/scripts
cd ~/Works/Enigma/service-myapi

# Generate gRPC handler boilerplate
$SCRIPTS/scaffold-grpc.sh Order

# This generates:
# - proto/constant.go (ServiceName)
# - proto/converter.go (entity ↔ proto stubs)
# - src/handler/grpc/order.go (handler skeleton)
# - Updates src/handler.go (RegisterGrpcRoutes)
```

**Generate proto code:**

```bash
cd proto
protoc --go_out=. --go-grpc_out=. order.proto
```

### Converter Pattern

```go
package proto

import (
    "github.com/mbu-id/service-order/entity"
    "github.com/google/uuid"
)

// ConvertOrder converts entity.Order to proto.Order
func ConvertOrder(m *entity.Order) *Order {
    if m == nil {
        return nil
    }

    return &Order{
        Id:           m.ID.String(),
        Code:         m.Code,
        Name:         m.Name,
        TotalCharges: m.TotalCharges,
    }
}

// ConvertOrderToEntity converts proto.Order to entity.Order
func ConvertOrderToEntity(m *Order) (*entity.Order, error) {
    if m == nil {
        return nil, nil
    }

    id, err := uuid.Parse(m.Id)
    if err != nil {
        return nil, err
    }

    return &entity.Order{
        ID:           id,
        Code:         m.Code,
        Name:         m.Name,
        TotalCharges: m.TotalCharges,
    }, nil
}
```

### gRPC Handler Implementation

```go
package grpc

import (
    "context"

    "github.com/mbu-id/service-order/proto"
    "github.com/mbu-id/service-order/src/usecase"
)

type orderHandler struct {
    proto.UnimplementedOrderServiceServer
    uc *usecase.Factory
}

func RegisterOrderHandler() proto.OrderServiceServer {
    return &orderHandler{
        uc: usecase.NewFactory(),
    }
}

// Show implements OrderService.Show
func (h *orderHandler) Show(ctx context.Context, req *proto.ShowRequest) (*proto.OrderResponse, error) {
    mx, err := h.uc.Order.WithContext(ctx).FindByID(req.Id)
    if err != nil {
        return nil, err
    }

    return &proto.OrderResponse{
        Order: proto.ConvertOrder(mx),
    }, nil
}
```

### Service Registration

In `src/handler.go`:

```go
package src

import (
    "github.com/mbu-id/engine/transport/grpc"
    "github.com/mbu-id/service-order/proto"
    grpcHandler "github.com/mbu-id/service-order/src/handler/grpc"
)

// RegisterGrpcRoutes registers all gRPC service handlers.
func RegisterGrpcRoutes(srv *grpc.GrpcServer) {
    proto.RegisterOrderServiceServer(srv, grpcHandler.RegisterOrderHandler())
}
```

In `main.go`, ensure gRPC server is active:

```go
// Start gRPC server
transportGRPC := grpc.NewService(&grpc.Config{
    ServiceName:       engine.Config.Name,
    Namespace:         os.Getenv("PLATFORM"),
    Address:           os.Getenv("GRPC_SERVER"),
    AdvertisedAddress: os.Getenv("GRPC_ADDRESS"),
}, engine.Logger, src.RegisterGrpcRoutes)

go transportGRPC.Start(ctx)
defer transportGRPC.Shutdown(ctx)
```

### Proto Code Generation

Install protoc compiler and Go plugins:

```bash
# Install protoc
brew install protobuf  # macOS
# or download from https://github.com/protocolbuffers/protobuf/releases

# Install Go plugins
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
```

Generate code:

```bash
cd proto
protoc --go_out=. --go-grpc_out=. *.proto
```

### gRPC Best Practices

1. **Separate Proto Module** — Keep proto definitions isolated with their own `go.mod`
2. **Manual Proto Design** — Write `.proto` files manually, don't auto-generate from entities
3. **Bidirectional Converters** — Always implement both `Convert*()` and `Convert*ToEntity()`
4. **Nil Checks** — Always check for nil in converters
5. **Error Propagation** — Return gRPC errors directly, don't wrap
6. **Context Propagation** — Always pass context to usecase methods

### gRPC Client to External Service

When your service needs to call another service via gRPC, create a client in `src/services/`.

**Structure:**
```
src/services/
└── service_ordering.go        # gRPC client for service-ordering
```

**Naming:**
- File: `service_{name}.go` — snake_case target service name
- Struct: `{ServiceName}Service` — PascalCase
- Package: `services`

**Pattern (Redis discovery via `grpc.GetClient`):**

```go
package services

import (
    "context"

    "github.com/mbu-id/service-ordering/proto"
    "github.com/mbu-id/engine/transport/grpc"
)

// OrderingService provides access to service-ordering gRPC endpoints.
type OrderingService struct{}

func NewOrderingService() *OrderingService {
    return &OrderingService{}
}

func (s *OrderingService) withClient(ctx context.Context, fn func(proto.OrderingServiceClient) error) error {
    client, closeFn, err := grpc.GetClient(
        ctx,
        proto.ServiceName,
        proto.NewOrderingServiceClient,
    )
    if err != nil {
        return err
    }
    defer closeFn()
    return fn(client)
}

// Ship calls the Ship RPC on service-ordering.
func (s *OrderingService) Ship(ctx context.Context, orderID string) (*proto.ShipResponse, error) {
    var resp *proto.ShipResponse
    err := s.withClient(ctx, func(client proto.OrderingServiceClient) error {
        var err error
        resp, err = client.Ship(ctx, &proto.ShipRequest{OrderId: orderID})
        return err
    })
    return resp, err
}
```

**Key differences from the old pattern:**
- **No `*grpc.ClientConn` struct field** — connection is ephemeral via `grpc.GetClient`
- **No `Close()` method** — `closeFn()` is called automatically by `defer` inside `withClient`
- **No `GRPC_ADDRESS` env var** — services discover each other via Redis (proto's `ServiceName`)
- **Stateless struct** — `OrderingService` has no fields, instantiated as `&OrderingService{}`
- **`withClient` helper** — wraps the boilerplate of getting/releasing a client connection

**Usage in usecase:**
```go
type OrderUsecase struct {
    repo         *repository.OrderRepository
    orderingSvc  *services.OrderingService  // gRPC client to service-ordering
    ctx          context.Context
}

func (u *OrderUsecase) Process(ctx context.Context, id string) error {
    order, err := u.repo.FindByID(id)
    if err != nil {
        return err
    }
    // Call external service via gRPC
    result, err := u.orderingSvc.Ship(ctx, order.ID.String())
    if err != nil {
        return err
    }
    // ...
}
```

**Scaffold:**
```bash
SCRIPTS=~/Works/Enigma/claude-plugins/skills/mbu-golang/scripts
cd ~/Works/Enigma/service-myapi

$SCRIPTS/scaffold-service.sh Ordering github.com/mbu-id/service-ordering/proto OrderingService
```

**Key rules:**
1. **File location** — Always `src/services/service_{name}.go`, never ad-hoc locations
2. **Package `services`** — All gRPC clients share the same `services` package
3. **Redis discovery** — Uses `grpc.GetClient(ctx, proto.ServiceName, proto.NewXxxClient)` — no env var for address
4. **Stateless struct** — No `*grpc.ClientConn` field, no `Close()` method. Connection is ephemeral via `withClient` helper
5. **`withClient` helper** — Always define it to reduce boilerplate; wraps `grpc.GetClient` + `closeFn()`
6. **Init in usecase struct** — Inject the service client into the usecase that needs it (not factory)

## Event-Driven Architecture

### Publisher Pattern

Publishers emit events when entity state changes. Follow the service-payment pattern:

**Structure:**
```
src/event/publisher/
└── order.go              # Event struct + publish functions
```

**Implementation:**

```go
package publisher

import (
    "context"
    "time"

    "github.com/mbu-id/engine/broker/rabbitmq"
    "github.com/mbu-id/service-order/entity"
)

// OrderEvent is the event payload for Order events.
type OrderEvent struct {
    Order       *entity.Order
    PublishedAt time.Time
}

// OrderCreated publishes an order.created event.
func OrderCreated(ctx context.Context, m *entity.Order) {
    rabbitmq.Publish(ctx, "order.created", &OrderEvent{
        Order:       m,
        PublishedAt: time.Now(),
    })
}

// OrderUpdated publishes an order.updated event.
func OrderUpdated(ctx context.Context, m *entity.Order) {
    rabbitmq.Publish(ctx, "order.updated", &OrderEvent{
        Order:       m,
        PublishedAt: time.Now(),
    })
}

// OrderDeleted publishes an order.deleted event.
func OrderDeleted(ctx context.Context, m *entity.Order) {
    rabbitmq.Publish(ctx, "order.deleted", &OrderEvent{
        Order:       m,
        PublishedAt: time.Now(),
    })
}
```

**Usage in Usecase (auto-wired in template):**

```go
package usecase

import (
    "github.com/mbu-id/service-order/src/event/publisher"
)

func (u *OrderUsecase) Create(req *entity.Order) error {
    if err := u.repo.Insert(req); err != nil {
        return err
    }
    go publisher.OrderCreated(u.ctx, req)
    return nil
}
```

Publisher is called via `go` for async fire-and-forget. The default usecase template (from `templates/usecase.tpl`) already includes these calls in `Create`, `Update`, and `Delete`.

**Scaffold Publisher:**

```bash
SCRIPTS=~/Works/Enigma/claude-plugins/skills/mbu-golang/scripts
cd ~/Works/Enigma/service-order

# Generate publisher boilerplate
$SCRIPTS/scaffold-publisher.sh Order

# This generates:
# - src/event/publisher/order.go (event struct + Created/Updated/Deleted functions)
```

### Subscriber Pattern

Subscribers listen to events from other services. Follow the service-tracking pattern:

**Structure:**
```
src/event/subscriber/
└── order.go              # Message struct + handler functions
```

**Implementation:**

```go
package subscriber

import (
    "context"

    "github.com/mbu-id/service-tracking/src/usecase"

    entityOrder "github.com/mbu-id/service-order/entity"
    amqp "github.com/rabbitmq/amqp091-go"
)

// OrderMessage wraps the Order entity from service-order.
type OrderMessage struct {
    Order *entityOrder.Order
}

// SubscribeOrderCreated handles order.created events.
func SubscribeOrderCreated(req *OrderMessage, msg amqp.Delivery) error {
    uc := usecase.NewFactory().WithContext(context.Background())

    err := uc.Tracking.OrderCreated(req.Order)

    return msg.Ack(err == nil)
}

// SubscribeOrderUpdated handles order.updated events.
func SubscribeOrderUpdated(req *OrderMessage, msg amqp.Delivery) error {
    uc := usecase.NewFactory().WithContext(context.Background())

    err := uc.Tracking.OrderUpdated(req.Order)

    return msg.Ack(err == nil)
}
```

**Error Handling with Requeue:**

```go
func SubscribeOrderProcess(req *OrderMessage, msg amqp.Delivery) error {
    uc := usecase.NewFactory().WithContext(context.Background())

    err := uc.Tracking.ProcessOrder(req.Order)
    if err != nil {
        // Requeue on failure (retry)
        msg.Nack(false, true)
        return err
    }

    return msg.Ack(false)
}
```

**Registration in src/subscriber.go:**

```go
package src

import (
    "github.com/mbu-id/service-tracking/src/event/subscriber"
    "github.com/mbu-id/engine/broker/rabbitmq"
)

func RegisterSubscriber() {
    // service-order events
    rabbitmq.Subscribe("order.created", subscriber.SubscribeOrderCreated)
    rabbitmq.Subscribe("order.updated", subscriber.SubscribeOrderUpdated)
    rabbitmq.Subscribe("order.deleted", subscriber.SubscribeOrderDeleted)

    // service-payment events
    rabbitmq.Subscribe("payment.paided", subscriber.SubscribePaymentPaided)
    rabbitmq.Subscribe("payment.expired", subscriber.SubscribePaymentExpired)
}
```

**Scaffold Subscriber:**

```bash
SCRIPTS=~/Works/Enigma/claude-plugins/skills/mbu-golang/scripts
cd ~/Works/Enigma/service-tracking

# Generate subscriber boilerplate
$SCRIPTS/scaffold-subscriber.sh order Order

# This generates:
# - src/event/subscriber/order.go (message struct + handler functions)
# - Updates src/subscriber.go (registers handlers)
```

### Event Naming Conventions

| Pattern | Example | Description |
|---|---|---|
| `{entity}.{action}` | `order.created` | Standard CRUD events |
| `{entity}.{status}` | `payment.paided` | Status change events |
| `{entity}.{entity}.{action}` | `order.route.departed` | Nested entity events |

**Rules:**
- Lowercase, dot-separated
- Past tense for actions: `created`, `updated`, `deleted`, `paided`
- Present tense for status: `active`, `expired`

### Event-Driven Best Practices

1. **Publish After Success** — Only publish events after database commit succeeds
2. **Delegate to Usecase** — Subscribers should delegate business logic to usecase methods, not implement inline
3. **Idempotency** — Design handlers to be idempotent (safe to retry)
4. **Ack on Success** — Use `msg.Ack(err == nil)` for simple cases
5. **Nack with Requeue** — Use `msg.Nack(false, true)` for retriable failures
6. **Context Propagation** — Always create fresh context in subscribers: `context.Background()`
7. **External Entities** — Import external entities with alias: `entityOrder "github.com/mbu-id/service-order/entity"`
8. **Event Versioning** — Include version in event name for breaking changes: `order.v2.created`

### When to Use Events

**Use Events For:**
- Cross-service notifications (order created → send invoice)
- Async workflows (payment received → fulfill order)
- Audit trails (track state changes)
- Fan-out patterns (one event, multiple subscribers)

**Don't Use Events For:**
- Synchronous validation (use gRPC)
- Request-response patterns (use REST/gRPC)
- Transactions across services (use saga pattern with compensation)

### API Contract Generation

Scaffold generates OAS 3.0 contracts from handler + request files:

```bash
# Single module
scaffold-contract.sh Branch

# All modules (per-module files)
scaffold-contract.sh --all

# Full service spec (api-contract.yaml at root)
scaffold-contract.sh --api-contract
```

**Generated output includes:**
- Standard `ResponseBody` envelope + `Meta` pagination
- `allOf`-composed `{Entity}Response` / `{Entity}ListResponse`
- `Create{Entity}Request` / `Update{Entity}Request` with `valid:"in:"` → OAS `enum`
- Standard error responses: `$ref` to `NotFound`, `BadRequest`, `Conflict`, `ValidationError`, `InternalError`
- Standard params: `Page`, `PageSize`, `SearchQuery`, `OrderBy`, `IdPath`, `x-company-id`
- Entity response schemas from `entity/*.go` JSON tags (relation pointers excluded)
- Query params from `getRequest` struct fields
- Path params from route patterns `{id}`

**Convention:** Generated contract must match the manually authored `api-contract.yaml` structure. If the hand-written file diverges from the generated output, update the hand-written file.

## Checklist for New Features

### REST API Feature
- [ ] Scaffold entity + repository
- [ ] Register enums in meta handler: `scaffold-meta.sh {Entity}`
- [ ] Scaffold usecase
- [ ] Wire into factory
- [ ] Scaffold handler + request files
- [ ] Add validation rules to request struct
- [ ] Implement business logic in usecase
- [ ] Add event publishing (if needed)
- [ ] Write tests
- [ ] Run `go mod tidy && go build ./...`

### gRPC Service Feature (Server)
- [ ] Write `.proto` file with service definition
- [ ] Scaffold gRPC handler with `scaffold-grpc.sh`
- [ ] Generate proto code: `cd proto && protoc --go_out=. --go-grpc_out=. *.proto`
- [ ] Implement converter functions in `proto/converter.go`
- [ ] Implement RPC methods in `src/handler/grpc/{module}.go`
- [ ] Ensure gRPC server is active in `main.go`
- [ ] Wire proto module in main `go.mod` if needed
- [ ] Write tests
- [ ] Run `go mod tidy && go build ./...`

### gRPC Client Feature (Call External Service)
- [ ] Scaffold gRPC client with `scaffold-service.sh` → `src/services/service_{name}.go`
- [ ] Add `{SERVICE}_GRPC_ADDRESS` to `.env`
- [ ] Add proto dependency: `go get {proto_module}`
- [ ] Implement RPC method wrappers in generated client
- [ ] Inject service client into usecase that needs it
- [ ] Call `service.Close()` in `OnStop`
- [ ] Run `go mod tidy && go build ./...`

### Event Publisher Feature
- [ ] Scaffold publisher with `scaffold-publisher.sh`
- [ ] Publisher calls are auto-wired in usecase template (Create/Update/Delete)
- [ ] Add custom event types if needed (beyond Created/Updated/Deleted)
- [ ] Test event publishing with RabbitMQ
- [ ] Document event schema for consumers

### Event Subscriber Feature
- [ ] Scaffold subscriber with `scaffold-subscriber.sh`
- [ ] Implement usecase methods to handle events
- [ ] Update external entity import paths if needed
- [ ] Add custom event handlers if needed
- [ ] Test event consumption with RabbitMQ
- [ ] Handle errors with proper Ack/Nack strategy
- [ ] Test endpoints manually

## Workflows

Automated task sequences for common development patterns. These workflows execute intelligently based on context and handle errors gracefully.

### Initialize New Service

**Trigger:** User asks to "initialize new service" or "create new Go service"

**Steps:**
1. Confirm service module path (e.g., `github.com/mbu-id/service-myapi`)
2. Run `scaffold-init.sh <module_path>`
   - Generates project skeleton with engine lifecycle
   - Creates `main.go` with OnStart/OnStop/Run
   - Creates `src/handler.go` for route registration
   - Creates `src/subscriber.go` for event registration
   - Creates `src/permission.go` for permission sync
   - Creates `entity/` module with separate `go.mod`
   - Initializes main `go.mod`
3. Run `go mod tidy` in root and entity directories
4. Verify: `go build ./...`
5. Report: Project initialized, next steps (add entities, configure database)

**Error Recovery:**
- If directory not empty → ask to overwrite or choose different path
- If module path invalid → suggest correct format
- If build fails → diagnose missing dependencies, retry

**Example:**
```
User: "Initialize new service for warehouse management"

→ Asks for module path: github.com/mbu-id/service-warehouse
→ Runs scaffold-init.sh
→ Reports: "Service initialized. Next: add database config in main.go, create first entity"
```

### Full Entity Scaffold (from SQL DDL)

**Trigger:** User provides SQL DDL or asks to "create entity from table"

**Steps:**
1. Detect table name from SQL DDL
2. **Generate migration files** — Create `migrations/<timestamp>_create_<table>.up.sql` / `.down.sql` from the DDL. Skip if using auto-migrate or if migration already exists.
3. Run `scaffold-entity.sh` with table DDL
   - Auto-chains to `scaffold-repo.sh`
   - Auto-wires entity module in main `go.mod`
4. Run `scaffold-usecase.sh <EntityName>`
5. Run `scaffold-factory.sh <EntityName>` (updates `src/usecase/factory.go`)
6. **Check API contract** — If `api-contract.yaml` exists and has no paths for this entity:
   - Ask: "No API contract defined for this entity. Create spec stubs or skip handler?"
   - User chooses: create spec stubs → proceed, or skip handler → stop
7. Run `scaffold-handler.sh <EntityName>` (generates REST handlers + requests)
8. **Customize request struct fields** — Edit `src/handler/<module>/request_create.go` and `request_update.go`:
   - Replace scaffold fields with actual API contract fields
   - Add FK caching fields (e.g., `company *entity.Company`) in `Validate()`
   - All fields are plain types (no `*string`) — maps directly to entity in `toEntity()`
9. **Verify handler wiring** — Check `src/handler/<module>/handler.go` uses `*usecase.Factory`.
10. **Sync API contract** — If `api-contract.yaml` exists, add request/response schemas that match customized fields. Dead OAS = tribal knowledge.
11. Verify: `go mod tidy && go build ./...`
12. **Publisher is auto-wired** — Create/Update/Delete in usecase template already publish events via `go publisher.Xxx{Created,Updated,Deleted}()`. Scaffold publisher first if missing.
13. Report: Files created, next steps (implement business logic, test endpoints)

**Error Recovery:**
- If table name detection fails → ask user for entity name
- If build fails → diagnose error, fix imports, retry
- If entity module already exists → skip auto-wiring step

**Example:**
```
User: "Create entity from this table:
CREATE TABLE warehouses (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name VARCHAR(255) NOT NULL,
  address TEXT
);"

→ Executes full scaffold workflow
→ Reports: "Created Warehouse entity with REST handlers. Next: implement business logic in src/usecase/warehouse.go"
```

### Add gRPC Service (Server)

**Trigger:** User asks to "add gRPC for <Entity>" or "create gRPC service"

**Steps:**
1. Check if proto file exists at `proto/<entity>.proto`
   - If missing → ask user to create proto file first
2. Run `scaffold-grpc.sh <EntityName>`
   - Generates `proto/constant.go`
   - Generates `proto/converter.go` stubs
   - Generates `src/handler/grpc/<entity>.go` handler skeleton
   - Updates `src/handler.go` with `RegisterGrpcRoutes`
3. Run `cd proto && protoc --go_out=. --go-grpc_out=. <entity>.proto`
4. Verify: proto compiles, handler registered in `main.go`
5. Report: Files created, next steps (implement converters, add RPC logic)

**Error Recovery:**
- If proto file missing → guide user to create it first
- If protoc fails → check proto syntax, suggest fixes
- If handler already exists → ask to overwrite or skip

**Example:**
```
User: "Add gRPC service for Order"

→ Checks proto/order.proto exists
→ Runs scaffold-grpc.sh Order
→ Compiles proto
→ Reports: "gRPC handler created. Implement converter.go methods and RPC logic in src/handler/grpc/order.go"
```

### Add gRPC Client (Call External Service)

**Trigger:** User asks to "call service-X from gRPC" or "add gRPC client for <Service>"

**Steps:**
1. Confirm the target service proto module path and proto service name
2. Run `scaffold-service.sh <ServiceName> <ProtoModule> <ProtoService>`
   - Generates `src/services/service_{name}.go`
   - Creates `New{Name}Service()` with env-based address, timeout interceptor, round-robin
3. Add proto dependency: `go get <ProtoModule>`
4. Add `{UPPER}_GRPC_ADDRESS` to `.env`
5. Add `service.Close()` call in `OnStop`
6. Inject into usecase that needs the external call
7. Verify: `go build ./...`

**Example:**
```
User: "Add gRPC client for ordering service"

→ Runs scaffold-service.sh Ordering github.com/mbu-id/service-ordering/proto OrderingService
→ Adds ORDERING_GRPC_ADDRESS to .env
→ Reports: "gRPC client created at src/services/service_ordering.go. Next: add RPC wrappers, inject in usecase"
```

### Add Event Publisher

**Trigger:** User asks to "publish events for <Entity>" or "add event publisher"

**Steps:**
1. Run `scaffold-publisher.sh <EntityName>`
   - Generates `src/event/publisher/<entity>.go`
   - Creates event struct with entity + PublishedAt
   - Generates Created/Updated/Deleted functions
2. No manual wiring needed — usecase template already includes `go publisher.Xxx{Created,Updated,Deleted}()` after repo calls.
3. Verify: imports correct, events compile
4. Report: Publisher created, events auto-wired in usecase

**Error Recovery:**
- If publisher already exists → ask to overwrite or add custom events
- If usecase not found → guide user to create usecase first

**Example:**
```
User: "Add event publishing for Order"

→ Runs scaffold-publisher.sh Order
→ Usecase already has publisher.OrderCreated() in Create, OrderUpdated in Update, OrderDeleted in Delete
→ Reports: "Event publisher created. Events: order.created, order.updated, order.deleted"
```

### Add Event Subscriber

**Trigger:** User asks to "subscribe to <service> events" or "consume events from <service>"

**Steps:**
1. Confirm event source service and entity name
2. Run `scaffold-subscriber.sh <event_source> <EntityName>`
   - Generates `src/event/subscriber/<event_source>.go`
   - Creates message struct wrapping external entity
   - Generates handler functions (Created/Updated/Deleted)
   - Auto-registers in `src/subscriber.go`
3. Verify: external entity import path is correct
4. Guide user to implement usecase methods for event handling
5. Report: Subscriber created, next steps (implement business logic)

**Error Recovery:**
- If external entity import fails → ask user for correct module path
- If subscriber already exists → ask to add custom handlers or skip

**Example:**
```
User: "Subscribe to order events from service-order"

→ Runs scaffold-subscriber.sh order Order
→ Generates subscriber/order.go with SubscribeOrderCreated/Updated/Deleted
→ Reports: "Subscriber created. Implement usecase methods to handle order events"
```

### Quick REST Endpoint

**Trigger:** User asks to "add endpoint for <action>" without full entity scaffold

**Steps:**
1. Detect module name from context or ask user
2. Check if usecase exists
   - If missing → run usecase scaffold first
3. Generate single handler file in `src/handler/<module>/<action>.go`
4. Add route registration in `src/handler.go`
5. Verify: handler compiles, route registered
6. Report: Endpoint created, implement logic in handler

**Error Recovery:**
- If module unclear → ask user for module name
- If usecase missing → offer to scaffold full module or just handler

**Example:**
```
User: "Add endpoint to publish delivery plan"

→ Detects DeliveryPlan module
→ Generates src/handler/delivery_plan/publish.go
→ Adds route in handler.go
→ Reports: "POST /delivery-plan/publish endpoint created"
```

### Workflow Execution Principles

1. **Context-Aware** — Read codebase state before executing (check if files exist, detect patterns)
2. **Error Recovery** — If a step fails, diagnose and retry with fixes (don't blindly re-run)
3. **User Confirmation** — For destructive operations (overwrite existing files), ask first
4. **Incremental Progress** — Report after each major step, don't wait until end
5. **Adaptive** — Skip unnecessary steps (e.g., if entity already exists, skip scaffold-entity)
6. **Validation** — Always verify with `go build` before reporting success
7. **Next Steps** — Always tell user what to do next (implement logic, test, etc.)
