diff --git a/.ai/AI_WORKFLOW.md b/.ai/AI_WORKFLOW.md deleted file mode 100644 index ee9de24..0000000 --- a/.ai/AI_WORKFLOW.md +++ /dev/null @@ -1,46 +0,0 @@ -# go-core — AI Development Principles - -`go-core` is an infrastructure foundation library for Go microservices. -Not a business service. Not a generic utils repo. Domain-agnostic. Go 1.24. - -## Ownership: What go-core Owns - -- Startup composition and runtime wiring -- Config loading and validation (env-driven, 12-factor) -- Lifecycle and graceful shutdown -- Transport wrappers: gRPC + HTTP gateway -- Logging, metrics, and tracing baseline -- Infrastructure connectors: DB, cache, messaging, migration -- Technical error contract and transport mapping -- Selected platform-standard technical contracts intentionally shared across services - -## Ownership: What go-core Does NOT Own - -- Business entities or domain rules -- Service-specific schema or workflow semantics -- Product-specific naming, aliases, or event payloads -- Hidden automation not controlled by the consuming service -- Generic utilities → those belong in `utils-shared` - -## Prompt Roles - -| Prompt | Purpose | -|---|---| -| `prompts/breakdown.md` | Plan a task — assess risk and define scope before writing code | -| `prompts/execute.md` | Implement a planned change | -| `prompts/fix.md` | Debug and fix a specific error | -| `prompts/test.md` | Write unit and integration tests | -| `prompts/review.md` | Review code before merge | -| `prompts/new-feature.md` | Add a new module or feature | -| `prompts/security-review.md` | Audit a change touching auth, data, or secrets | -| `prompts/architecture-consult.md` | Get structured analysis on a design decision | - -## Execution Principles - -- Prefer safe evolution — additive changes first -- Allow bounded refactors that improve the framework shape -- Avoid hidden side effects and undocumented runtime behavior -- Preserve documented exported behavior as the semver contract -- Keep defaults generic — no service-specific names anywhere -- Keep service-specific semantics out of framework code and docs -- Target Go `1.24` diff --git a/.ai/README.md b/.ai/README.md deleted file mode 100644 index 7e68745..0000000 --- a/.ai/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# .ai/ — AI Context for go-core - -This folder contains the structured AI context for all work in the `go-core` repository. - -## Reading Order - -Always start here, in order: - -1. **`.ai/context.md`** — project overview, key modules, critical rules -2. **`.ai/architecture.md`** — system design, layer model, scaling considerations -3. Continue to the specific file for the area being worked on - -## Navigation by Task Area - -| Working on... | Read | -|---|---| -| Auth / JWT / security | `.ai/security.md` | -| DB transactions / idempotency / outbox | `.ai/transactions.md` | -| Module APIs / symbols / constructors | `.ai/modules.md` | -| Request flow / observability / logging | `.ai/data-flow.md` | -| External integrations / env config | `.ai/integrations.md` | -| Code style / naming / patterns | `.ai/conventions.md` | -| Why design decisions were made | `.ai/decisions.md` | -| Dev workflow / acceptance checklist | `.ai/workflow.md` | - -## Folder Structure - -``` -.ai/ -├── context.md ← READ THIS FIRST -├── architecture.md -├── security.md -├── transactions.md -├── modules.md -├── data-flow.md -├── integrations.md -├── conventions.md -├── decisions.md -├── workflow.md -│ -├── AI_RULES.md ← Compact rules & review checklist -├── AI_WORKFLOW.md ← Ownership principles & prompt roles -├── STATUS.md ← Task progress tracking -├── config.yaml ← AI context file index -│ -├── prompts/ ← Ready-to-use prompt templates (8 files) -└── tasks/ ← Task definitions for bounded changes -``` - -## Maintenance Rule - -Update the relevant `.ai/` file whenever public behavior changes. -`.ai/` is the single source of truth for AI context in this repo. diff --git a/.ai/STATUS.md b/.ai/STATUS.md deleted file mode 100644 index 529b780..0000000 --- a/.ai/STATUS.md +++ /dev/null @@ -1,40 +0,0 @@ -# Progress - -## Done - -- `clarify_boundary_and_docs` -- `formalize_transaction_observability` -- `document_service_bootstrap_golden_path` -- `align_ci_and_release_hygiene` -- `logging_flavor_expansion` -- `formalize_messaging_outbox_pattern` -- `improve_config_dx` -- `raise_observability_contracts` -- `harden_release_adoption_workflow` -- `strengthen_bootstrap_templates` -- `improve_runtime_orchestration` -- `align_transport_contracts` -- `improve_messaging_outbox_runtime` -- `refresh_examples_suite` -- `improve_cache_layer` -- `harden_error_contracts` -- `improve_security_observability` -- `improve_migration_adoption` -- `improve_resilience_contracts` -- `align_scripts_and_gates` -- `define_versioning_upgrade_discipline` -- `cleanup_ai_source_of_truth` - -## Pending - -- none - -## Superseded - -- `refactor_config_database_defaults` - -## Notes - -- `.ai/tasks/*.md` is the detailed source of truth per task. -- `done` means materially implemented in repo state, not merely planned. -- `superseded` means the task should not be used as the primary path anymore because newer tasks or repo direction replaced it. diff --git a/.ai/architecture.md b/.ai/architecture.md deleted file mode 100644 index d405f8e..0000000 --- a/.ai/architecture.md +++ /dev/null @@ -1,153 +0,0 @@ -# go-core · Architecture - -## System Design - -`go-core` is a **framework/library repository** — not a runnable service. -It provides the runtime foundation that consuming microservices compose and own. - -### Architectural Style - -- **Modular infrastructure library** — each package is independently adoptable -- **12-Factor App compliant** — all config from environment variables -- **Explicit lifecycle** — no hidden background goroutines; consuming service registers all shutdown hooks -- **Opt-in infra** — Redis, Kafka, Memcached, tracing are never started unless configured - ---- - -## Layer Model - -``` -┌─────────────────────────────────────────────────────┐ -│ Consuming Service │ -│ (business logic, domain, handlers, proto) │ -└───────────────────────┬─────────────────────────────┘ - │ uses -┌───────────────────────▼─────────────────────────────┐ -│ go-core │ -│ │ -│ ┌─────────────────────────────────────────────┐ │ -│ │ Transport Layer │ │ -│ │ server/grpc · server/gateway │ │ -│ │ (auth interceptors, metrics, panic recovery)│ │ -│ └──────────────────┬──────────────────────────┘ │ -│ │ │ -│ ┌──────────────────▼──────────────────────────┐ │ -│ │ Application Container (app/) │ │ -│ │ lifecycle · dependency wiring · shutdown │ │ -│ └──────────────────┬──────────────────────────┘ │ -│ │ │ -│ ┌──────────────────▼──────────────────────────┐ │ -│ │ Infrastructure Layer │ │ -│ │ database · dbtx · cache · messaging │ │ -│ │ migration · resilience │ │ -│ └──────────────────┬──────────────────────────┘ │ -│ │ │ -│ ┌──────────────────▼──────────────────────────┐ │ -│ │ Cross-Cutting Concerns │ │ -│ │ logger · observability · security · errors │ │ -│ └─────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────┘ -``` - ---- - -## Stable Bootstrap Path - -``` -main() - │ - ├─ signal.NotifyContext(SIGINT, SIGTERM) - │ - ├─ config.Load(ctx, cfg) ← env parsing + normalization - ├─ cfg.Validate() ← fail-fast on missing required fields - │ - ├─ migration.AutoRunUp(...) ← optional, with distributed lock - │ - ├─ app.New(ctx, cfg) ← wire: logger, metrics, tracing, - │ DB pools, Redis, Memcached, lifecycle - │ - ├─ build gRPC server + register handlers - ├─ build HTTP gateway + register handlers - │ - └─ server.Run(...) ← multiplex gRPC+HTTP, block on signal, - graceful shutdown via lifecycle -``` - ---- - -## Service Interactions - -### Transport Boundary - -``` -HTTP Client - │ REST/JSON - ▼ -server/gateway (HTTP) - │ HMAC signature check (optional) - │ request-id injection - │ OTEL metrics + tracing - │ panic recovery - │ translates → gRPC-Gateway → protobuf - ▼ -server/grpc (gRPC) - │ JWT extraction + verification - │ Claims injected into context - │ grpc_request ServiceLog - │ request metrics - ▼ -Service Handler (consuming service) - │ - ├─ dbtx.WithTx → repository → dbtx.FromContext - ├─ outbox.PublishTx (same transaction) - └─ logger.LogTransaction / LogService -``` - -### Readiness Path - -``` -GET /ready - │ - ├─ DB ping (required databases) - ├─ Redis ping (if enabled) - ├─ Memcached ping (if enabled) - └─ 200 OK or 503 Service Unavailable -``` - ---- - -## High-Risk Change Areas - -Changes in these packages must be treated as **public contract risk** first: - -| Package | Risk | -|---|---| -| `app/` | Breaks every service bootstrap path | -| `config/` | Breaks every env-based configuration | -| `server/` | Breaks transport contract, readiness, health | -| `migration/` | Breaks migration autorun and lock semantics | -| `errors/` | Breaks transport-facing error contract | -| `security/` | Breaks cross-service auth behavior | -| `logger/` | Breaks operational observability baselines | -| `observability/` | Breaks Prometheus metric naming/labels | - ---- - -## Scaling Considerations - -- **Stateless by design** — `go-core` holds no mutable runtime state beyond initialized connections -- **Horizontal scaling**: All instances share the same connection pool config; connection counts multiply with replicas -- **Migration locking**: `MIGRATION_LOCK_ENABLED` prevents concurrent migration corruption in multi-pod K8s deployments -- **Outbox workers**: Worker pods vs. handler pods can be separated — consuming service controls topology -- **Metrics cardinality**: Avoid high-cardinality label values (e.g., user IDs) — label sets are defined at framework level and are intentionally bounded -- **Circuit breaker**: Per-instance state — no distributed state sharing; each pod has its own breaker counts - ---- - -## Key Design Decisions - -- **gRPC-gateway over Gin/Fiber/Echo** — enforces single protobuf contract, HTTP is a projection -- **Viper/env config over YAML** — enforces 12-factor compliance, simplifies container deployments -- **Context-based transaction propagation** — `dbtx` avoids passing `*sql.Tx` through function signatures -- **Explicit lifecycle ownership** — no hidden goroutines; consuming service decides what starts and when -- **Outbox pattern for event durability** — direct publish is lossy on crash; outbox ensures transactional delivery diff --git a/.ai/config.yaml b/.ai/config.yaml deleted file mode 100644 index afd41ac..0000000 --- a/.ai/config.yaml +++ /dev/null @@ -1,19 +0,0 @@ -version: v1 - -# AI context file index for go-core. -# Lists the canonical context files in recommended reading order. - -context: - # Always read first - - .ai/context.md - - .ai/architecture.md - - # Read based on task area - - .ai/security.md - - .ai/transactions.md - - .ai/modules.md - - .ai/data-flow.md - - .ai/integrations.md - - .ai/conventions.md - - .ai/decisions.md - - .ai/workflow.md diff --git a/.ai/context.md b/.ai/context.md deleted file mode 100644 index 0eb6b28..0000000 --- a/.ai/context.md +++ /dev/null @@ -1,105 +0,0 @@ -# go-core · AI Context - -> **Primary lens for all AI-assisted work. Read this first. Always.** - -## Project Overview - -`go-core` is the **production-grade infrastructure foundation** for all Go microservices in this ecosystem. -It is a framework/library repo — not a business service, not a generic utils dump. - ---- - -## Core Architecture - -``` -config → app.New() → server.Run() - | - ┌──────┼──────────────────────┐ - logger metrics lifecycle databases - | - ┌──────┼───────────────────┐ - gRPC gateway security outbox -``` - -**Bootstrap order (golden path):** -1. `config.Load(...)` — parse env → validate -2. `app.New(ctx, cfg)` — wire logger, metrics, DB, cache, lifecycle -3. Register gRPC/HTTP handlers -4. `server.Run(...)` — start transports, block, graceful shutdown - ---- - -## Key Modules - -| Module | Responsibility | -|---|---| -| `app/` | Container, lifecycle, dependency wiring | -| `config/` | Env loading, typed struct, strict validation | -| `server/grpc/` | gRPC transport, auth interceptors, request metrics | -| `server/gateway/` | HTTP gateway, signature validation, panic recovery | -| `security/` | JWT verification (RS256/384/512 + JWKS), claims extraction | -| `errors/` | `AppError` canonical error contract + gRPC mapper | -| `logger/` | Zap structured logging: `ServiceLog`, `DBLog`, `TransactionLog` | -| `observability/` | Prometheus metrics, OTEL tracing | -| `dbtx/` | SQL transaction manager + context propagation | -| `messaging/` | Kafka publisher, consumer, outbox worker | -| `resilience/` | Retry (exp backoff + jitter), circuit breaker, timeout | -| `httpclient/` | Resilient outbound HTTP client (resty + CB + retry + OTEL) | -| `cache/` | Redis, Memcached adapters | -| `migration/` | Goose migration autorun with distributed lock | - ---- - -## Entry Points - -- `app.New(ctx, cfg)` — application bootstrap -- `server.Run(...)` — transport orchestration -- `config.Load(...)` — configuration initialization -- `errors.AppError` — canonical error type - ---- - -## Critical Rules - -### Security -- **Never** expose internal error details in API responses — sanitize at transport boundary -- JWT verification requires RS algorithm; symmetric (HS*) is blocked by `ValidMethods` constraint -- Sensitive fields are auto-redacted in logs (`password`, `token`, `card`, `otp`, `cvv`, `pin`, `secret`) -- Metadata-only mode (`INTERNAL_JWT_ENABLED=false`) is **non-enforcing** — intended for trusted internal calls only - -### Transactions -- Boundary: `dbtx.WithTx(ctx, db, fn)` — wraps commit/rollback/panic recovery -- Repository must use `dbtx.FromContext(ctx)` to reuse the active transaction -- Outbox record **must** be written in the **same transaction** as domain data -- Keep transactions short; no network calls inside a DB transaction - -### Stability -- This repo is at stable **v1.0.0** — all public API changes require semver intent -- Prefer additive changes; breaking changes require major-version bump + `MIGRATION.md` entry -- Keep `go-core` domain-agnostic — no business entities, no service-specific defaults - ---- - -## What Does NOT Belong Here - -- Business entities, product domain rules -- Service-specific workflow logic or event semantics -- Generic utility helpers → use `utils-shared` -- Hidden background automation not controlled by the consuming service - ---- - -## Primary References - -| File | Purpose | -|---|---| -| `.ai/architecture.md` | System design, layers, scaling | -| `.ai/security.md` | Auth flow, JWT, secrets handling | -| `.ai/transactions.md` | Transaction flow, idempotency, failure handling | -| `.ai/modules.md` | Module details, APIs, conventions | -| `.ai/data-flow.md` | Request lifecycle, observability pipeline | -| `.ai/integrations.md` | External services, dependencies | -| `.ai/conventions.md` | Code style, patterns, engineering rules | -| `.ai/decisions.md` | Architecture decision records | -| `docs/` | Framework guidance documents | -| `MIGRATION.md` | Upgrade notes | diff --git a/.ai/conventions.md b/.ai/conventions.md deleted file mode 100644 index 533c9cc..0000000 --- a/.ai/conventions.md +++ /dev/null @@ -1,206 +0,0 @@ -# go-core · Conventions - -## Code Style - -- **Target Go version:** `1.24` -- **Linter:** `golangci-lint` (see `.golangci.yml`) -- **Format:** `gofmt` / `goimports` — all files must be formatted before commit -- **Module path:** `github.com/yogayulanda/go-core` - ---- - -## API Design Rules - -### Function Signatures - -```go -// ✅ Correct — ctx first, named return only for defer-pattern -func (r *repo) Save(ctx context.Context, data Domain) error - -// ✅ Correct — options pattern for extensibility -func NewKafkaPublisher(cfg KafkaConfig, opts ...PublisherOption) (Publisher, error) - -// ❌ Wrong — no ctx, business arg before infra -func Save(data Domain, ctx context.Context) error -``` - -- `ctx context.Context` is always the first parameter in runtime functions -- Options pattern (`...Option`) for extensible constructors -- Interfaces kept small — prefer 1–3 methods -- Avoid naked `bool` returns — use `(result, error)` or named types - -### Error Handling - -```go -// ✅ Canonical error for application errors -return errors.New(errors.CodeNotFound, "user not found") -return errors.Wrap(errors.CodeInternal, "payment failed", internalErr) -return errors.Validation("invalid input", errors.Detail{Field: "amount", Reason: "must be positive"}) - -// ✅ Internal errors wrapped with context -return fmt.Errorf("repo.Save: %w", err) - -// ❌ Never expose internal error message to API clients -return status.Error(codes.Internal, err.Error()) // raw internals leak! -``` - -- Always use `errors.AppError` built with `errors.Build(domain, category, number)` for application-facing errors -- Downstream services must inject their bounded context domain prefix (e.g., TRF, PPOB) without modifying go-core -- Internal errors wrapped with `fmt.Errorf("...: %w", err)` for traceability -- The HTTP Gateway automatically pulls trace_id and transaction_id for edge responses -- Internal error detail stays in logs — **never** in API responses -- `unknown errors` (non-AppError) must be mapped to `INTERNAL_ERROR` - -### Logging - -```go -// ✅ Use structured log flavors — not raw log.Info -logger.LogService(ctx, logger.ServiceLog{ - Operation: "order_create", - Status: "success", - DurationMs: time.Since(start).Milliseconds(), -}) - -// ❌ Raw string logging loses structure -log.Info(ctx, "order created successfully") -``` - -- Use `LogService` for standard service operations -- Use `LogDB` for database operation diagnostics -- Use `LogTransaction` only for business-level transaction monitoring -- Use `logger.Logger.Info/Warn/Error` for informational/diagnostic messages -- Never log raw JWT tokens, passwords, or card data - -### Testing - -```go -// ✅ Prefer focused unit tests with mocks -func TestSave(t *testing.T) { - db, mock, _ := sqlmock.New() - mock.ExpectExec("INSERT INTO ...").WillReturnResult(...) - // test isolated behavior -} - -// ✅ Table-driven tests for multiple cases -func TestValidate(t *testing.T) { - cases := []struct{ ... }{ ... } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { ... }) - } -} -``` - -- Use `sqlmock` for DB isolation -- Use function overrides (not interface mocking) for simple cases -- Cover success path + all documented error paths for exported functions -- Tests must pass with `go test ./...` before work is considered done - ---- - -## Configuration Conventions - -- All config via **environment variables** — no YAML/JSON config files for runtime config -- Config struct fields use Go types (`time.Duration`, `int`, `bool`) — not raw strings -- `UPPERCASE_SNAKE_CASE` for all env var names -- DB alias in env: `UPPERCASE` → normalized to `lowercase` in code -- Validation in `config.Validate()` — fail fast at startup, never at request time -- Additive DX improvements go through `ValidateIssues()` — compact path unchanged - ---- - -## Naming Conventions - -| Element | Convention | Example | -|---|---|---| -| Package | lowercase single word | `security`, `resilience` | -| Exported type | PascalCase | `AppError`, `CircuitBreaker` | -| Interface | Noun or Noun+er | `Publisher`, `Consumer`, `Beginner` | -| Constructor | `New(...)` | `NewInternalJWTVerifier(...)` | -| Error variables | `err` | `errInvalidToken` | -| Metric names | `app___` | `app_request_duration_seconds` | -| Log operation names | `snake_case` verb+noun | `"payment_process"`, `"app_init"` | -| Log status values | stable lowercase | `"success"`, `"failed"`, `"pending"` | - ---- - -## Lifecycle Ownership Rules - -```go -// ✅ All shutdown hooks must be explicit -lifecycle.Register(func(ctx context.Context) error { - return db.Close() -}) - -// ❌ Hidden background goroutines not controlled by consuming service -go func() { worker.Start(ctx) }() // inside go-core — forbidden -``` - -- `go-core` never starts background goroutines automatically -- All cleanup must be registered in `lifecycle` -- Consuming service decides: which workers run, in which pods, at what interval - ---- - -## Boundary Enforcement - -### Allowed in `go-core` - -- Generic infrastructure helpers (transport, logging, config, DB, cache, messaging) -- Technical contracts intentionally standardized across services (e.g., `TransactionLog`) -- Observability baselines (Prometheus metrics, OTEL tracing) - -### Not Allowed in `go-core` - -- Business entities, domain models, product schemas -- Service-specific DB alias defaults (e.g., never hardcode `"transaction"` as a DB name) -- Product-specific event payloads or topic names -- Hidden background behavior not controllable by consuming service -- Generic utilities better suited for `utils-shared` - ---- - -## Change Checklist - -Before any change to a high-risk area (`app/`, `config/`, `server/`, `errors/`, `security/`): - -1. **Compatibility** — Does this break existing consuming services? -2. **Coupling** — Does this introduce product-specific knowledge? -3. **Concurrency** — Are there new goroutines or shared state? -4. **Scale risk** — Does this cause metric cardinality explosion or connection growth? -5. **Overengineering** — Is this simpler than the problem requires? - -For any public-contract change: -- Update `README.md` -- Update relevant `docs/` file -- Update `MIGRATION.md` -- Update `.ai/` context if behavior changes -- Run `go test ./...` and `make quality-gate` - ---- - -## Semver and Release Rules - -- `v1.x.y` is the stable series — published public API is a compatibility contract -- **Patch** (`y`): bug fixes, internal refactors, no API change -- **Minor** (`x`): additive new exported API, no breaking changes -- **Major** (new `v2`): breaking change to any public API, config, or runtime behavior - -Breaking changes require: -- Explicit major-version decision -- Entry in `MIGRATION.md` -- Upstream team communication - ---- - -## Task Definition Standard - -All `.ai/tasks/*.md` files must define: -```yaml -goal: [one-line intent] -scope: [layer(s) affected] -allowed_paths: [list of permitted file paths] -constraints: [what must not change] -``` - -AI must not implement behavior outside `allowed_paths`. -Work is complete only when: implementation done + tests pass + docs aligned + `.ai` context updated. diff --git a/.ai/data-flow.md b/.ai/data-flow.md deleted file mode 100644 index ffa921c..0000000 --- a/.ai/data-flow.md +++ /dev/null @@ -1,231 +0,0 @@ -# go-core · Data Flow - -## Request Lifecycle (Full Path) - -``` -Client (REST) Client (gRPC) - │ │ - ▼ │ -┌────────────────────┐ │ -│ HTTP Gateway │ │ -│ server/gateway/ │ │ -│ │ │ -│ 1. Request ID │ │ -│ inject │ │ -│ 2. OTEL timer │ │ -│ start │ │ -│ 3. Signature │ │ -│ validate │ │ -│ (if enabled) │ │ -│ 4. Serialize → │ │ -│ protobuf │ │ -└────────┬───────────┘ │ - │ │ - ▼ gRPC (internal) ▼ gRPC (direct) -┌────────────────────────────────────────────────┐ -│ gRPC Server │ -│ server/grpc/ │ -│ │ -│ Interceptors (in order): │ -│ 1. OTEL tracing │ -│ 2. Request ID propagation │ -│ 3. Auth: JWT verify or metadata extract │ -│ Claims → context │ -│ 4. Request metrics (counter + histogram) │ -│ 5. grpc_request ServiceLog │ -│ 6. Panic recovery │ -└────────────────────┬───────────────────────────┘ - │ - ▼ -┌────────────────────────────────────────────────┐ -│ Service Handler (consuming service) │ -│ │ -│ Claims ← security.FromContext(ctx) │ -│ RequestID ← observability.RequestIDFromCtx │ -│ │ -│ dbtx.WithTx(ctx, db, func(txCtx) error { │ -│ repo.Save(txCtx, data) │ -│ outbox.PublishTx(txCtx, topic, payload) │ -│ }) │ -│ │ -│ logger.LogTransaction(ctx, tx) │ -└────────────────────────────────────────────────┘ -``` - ---- - -## Observability Pipeline - -``` -Request arrives - │ - ├─ OTEL span created (server interceptor) - │ └─ propagated through context - │ - ├─ Prometheus counter incremented (per request) - │ - ├─ Handler executes - │ ├─ DBLog emitted on each DB operation - │ ├─ ServiceLog emitted on key operation events - │ └─ TransactionLog emitted on business transaction completion - │ - └─ Response sent - ├─ OTEL span ended (with status) - ├─ Prometheus histogram observed (latency) - └─ grpc_request ServiceLog emitted (final status) - -Prometheus scrape: GET /metrics -OTEL export: OTLP push to configured endpoint -``` - ---- - -## Startup Data Flow - -``` -main() - │ - ├─ ENV variables read - │ config.Load() → Config struct - │ config.Validate() → fail fast on missing required - │ - ├─ migration.AutoRunUpWithLogger() (if MIGRATION_AUTO_RUN=true) - │ ├─ acquire distributed lock - │ ├─ run pending Goose migrations - │ └─ release lock - │ - ├─ app.New() - │ ├─ logger.New() → emit nothing yet - │ ├─ observability.NewMetrics() → register Prometheus metrics - │ ├─ observability.InitTracing() → connect OTEL exporter - │ ├─ database.New() × N → open and health-check DB pools - │ ├─ cache.NewRedis() → open and ping Redis (if enabled) - │ ├─ cache.NewMemcached() → open and test Memcached (if enabled) - │ └─ emit "app_init" ServiceLog - │ - ├─ Service wires handlers - │ ├─ build gRPC server with interceptors - │ └─ build HTTP gateway with middleware - │ - ├─ server.Run() - │ ├─ bind ports - │ ├─ server.LogStartupReadiness() → emit readiness ServiceLog - │ └─ multiplex gRPC + HTTP - │ - └─ Blocks on SIGINT/SIGTERM - └─ lifecycle.Shutdown() - ├─ DB pool close - ├─ Redis close - ├─ Kafka publisher/consumer close - ├─ OTEL exporter flush - └─ emit "app_runtime" shutdown ServiceLog -``` - ---- - -## Context Propagation Map - -| Context Key | Set By | Read By | Content | -|---|---|---|---| -| Request ID | gRPC interceptor / gateway middleware | All layers | UUID string | -| OTEL Span | OTEL interceptor | `observability.FromContext` | Active trace span | -| Auth Claims | gRPC auth interceptor | Service handlers | `*security.Claims` | -| DB Transaction | `dbtx.Inject` | `dbtx.FromContext` | `*sql.Tx` | - -All propagation is via standard `context.Context` — no global state. - ---- - -## Log Flow - -``` -logger.Logger - │ - ├─ LogService(ctx, ServiceLog) - │ └─ zap.Info("service_log", fields...) - │ ├─ category: "service" - │ ├─ operation, status, duration_ms - │ ├─ error_code (if present) - │ └─ metadata (sanitized) - │ - ├─ LogDB(ctx, DBLog) - │ └─ zap.Info("db_log", fields...) - │ ├─ category: "db" - │ ├─ db_name, operation, table, status, rows_affected, duration_ms - │ └─ error_code (if present) - │ - └─ LogTransaction(ctx, TransactionLog) - └─ zap.Info("transaction_log", fields...) - ├─ category: "transaction" - ├─ operation, transaction_id, user_id, status, duration_ms - ├─ error_code (if present) - └─ metadata (sanitized) - -All fields pass through sanitizeFieldValue(): - sensitive keys → masked (last 2 chars shown) -``` - ---- - -## Outbox Data Flow - -``` -Service writes: - dbtx.WithTx → { - INSERT INTO domain_table ... ← domain row - INSERT INTO outbox_table ... ← outbox row (topic, payload, status=pending) - } COMMIT - -OutboxWorker (explicit, service-controlled goroutine): - loop every interval: - SELECT * FROM outbox_table WHERE status='pending' LIMIT batch_size - for each row: - Kafka.Publish(row.topic, row.payload) - if success: UPDATE status='published' - if fail: UPDATE status='failed', retry_count++ - emit outbox_batch ServiceLog - emit app_outbox_batch_total counter -``` - ---- - -## Readiness Check Flow - -``` -GET /ready - │ - ├─ For each required database: - │ db.PingContext(ctx) → fail → 503 - │ - ├─ Redis (if enabled): - │ client.Ping() → fail → 503 - │ - ├─ Memcached (if enabled): - │ client.Get("__healthcheck__") - │ → miss is OK (healthy) - │ → network error → 503 - │ - └─ All pass → 200 {"status": "ok"} -``` - ---- - -## Error Response Data Flow - -``` -Handler returns AppError - │ - ├─ [gRPC path]: - │ errors.ToGRPC(err) → grpc.Status{Code, Message} - │ Internal Err field → logged in ServiceLog, NOT in response - │ - └─ [HTTP gateway path]: - grpc-gateway translates gRPC status → HTTP JSON - { - "code": "UNAUTHORIZED", - "message": "unauthorized request", - "request_id": "uuid", - "details": [...] ← only for validation errors - } - Deep technical errors → sanitized to "internal server error" -``` diff --git a/.ai/decisions.md b/.ai/decisions.md deleted file mode 100644 index 36adac1..0000000 --- a/.ai/decisions.md +++ /dev/null @@ -1,224 +0,0 @@ -# go-core · Architecture Decisions - -Architecture Decision Records (ADRs) for `go-core`. -Each decision documents the context, choice, and rationale. - ---- - -## ADR-001: gRPC-Gateway over Pure HTTP Framework - -**Status:** Accepted (v1.0.0) - -**Context:** -Services need both gRPC (internal) and REST/JSON (external) transport. Options considered: Gin, Fiber, Echo + manual gRPC, or grpc-gateway. - -**Decision:** -Use `grpc-ecosystem/grpc-gateway/v2`. HTTP is a projection of the gRPC protobuf contract. - -**Rationale:** -- Single protobuf schema serves as source of truth for both transports -- Auth, metrics, and tracing interceptors apply once at gRPC level -- Eliminates duplicate validation and serialization logic -- Strong typing enforcement across transport boundary - -**Trade-off:** -- Imposes protobuf requirement on all services -- Less flexible than Gin/Fiber for purely HTTP-native services -- Not suitable for services that genuinely don't need gRPC - ---- - -## ADR-002: Environment Variables Only (12-Factor Config) - -**Status:** Accepted (v1.0.0) - -**Context:** -Config could be loaded from files (YAML/JSON), env vars, or a config server (Vault, AWS SSM). - -**Decision:** -All runtime configuration via environment variables only. Viper used for env parsing. No structured file configs for runtime. - -**Rationale:** -- 12-factor compliance — identical binary works across dev/staging/prod -- Simpler K8s ConfigMap/Secret injection -- No config drift between environments -- No file access requirements at runtime - -**Trade-off:** -- Large configs result in many env vars -- Nested config (DB per alias) requires naming convention discipline - ---- - -## ADR-003: Context-Based Transaction Propagation (`dbtx`) - -**Status:** Accepted (v1.0.0) - -**Context:** -SQL transactions need to flow from use-case boundary down through repository layers. Options: explicit `*sql.Tx` parameter, or context injection. - -**Decision:** -`dbtx.Inject(ctx, tx)` + `dbtx.FromContext(ctx)` pattern. Repositories receive context, extract `*sql.Tx` if present. - -**Rationale:** -- Eliminates `*sql.Tx` pollution through function signatures -- Repositories don't change signature when called inside vs. outside a transaction -- Fits idiomatic Go context propagation patterns -- `dbtx.WithTx` provides automatic commit/rollback/panic recovery - -**Trade-off:** -- Implicit dependency — developers must know to use `dbtx.FromContext` -- Requires discipline: repositories must always use context-extracted connection - ---- - -## ADR-004: Explicit Lifecycle Ownership (No Hidden Goroutines) - -**Status:** Accepted (v1.0.0) - -**Context:** -Infra packages could auto-start background workers (e.g., outbox worker, Kafka consumer) during initialization. - -**Decision:** -`go-core` never starts background goroutines automatically. All workers and lifecycle hooks must be explicitly registered by the consuming service. - -**Rationale:** -- Services must control which processes run in which pods (workers vs. handlers) -- Hidden automation creates non-obvious resource consumption -- Explicit ownership makes shutdown behavior predictable -- Outbox worker is a prime example: not all service replicas should poll - -**Trade-off:** -- More boilerplate in service `main.go` -- Requires documentation and templates to guide correct usage - ---- - -## ADR-005: Centralized Error Contract (`errors.AppError`) - -**Status:** Accepted (v1.0.0) - -**Context:** -Multiple services need consistent error responses. Without a canonical contract, each handler invents its own error format. - -**Decision:** -`errors.AppError{Code, Message, Category, Details, Err}` is the single error type for all application errors. `Err` (internal) is never exposed to clients. - -**Rationale:** -- Protects API consumers from leaking SQL internals, stack traces, or sensitive paths -- Consistent error codes enable frontend/mobile error handling -- `ToGRPC()` mapping ensures transport-level correctness -- Category enables structured monitoring without exposing implementation details - -**Trade-off:** -- Forces all services to learn and use `AppError` -- New error codes require repo-level changes - ---- - -## ADR-006: `TransactionLog` as Platform-Standard Contract - -**Status:** Accepted (v1.0.0) - -**Context:** -Transaction-oriented services (payments, transfers) need consistent observability for business-level flows — separate from technical service logs. - -**Decision:** -`logger.TransactionLog` and `logger.Logger.LogTransaction(...)` are approved platform-standard contracts in `go-core`, not generic utilities. - -**Rationale:** -- Enables unified Grafana dashboards across all transaction services -- Stable `app_transaction_total{service,operation,status}` metric for cross-service alerting -- `UserID`, `TransactionID`, `ErrorCode` are cross-service correlation fields -- Clearly scoped to transaction-oriented services — not imposed on all services - -**Trade-off:** -- Breaks the strict "no product-specific code in go-core" rule (intentional exception) -- New services must understand this is optional, not mandatory - ---- - -## ADR-007: Outbox Pattern for Durable Event Delivery - -**Status:** Accepted (v1.0.0) - -**Context:** -Services that write to a DB and publish a Kafka event risk losing the event if the process crashes between commit and publish. - -**Decision:** -`messaging/outbox` provides transactional outbox. Domain data + outbox record written in one DB transaction. Worker publishes separately. - -**Rationale:** -- Eliminates the dual-write problem (DB commit + Kafka publish in sequence) -- At-least-once delivery with explicit retry control -- Worker ownership stays with the service — `go-core` only provides the mechanism - -**Trade-off:** -- Adds operational complexity (outbox table, worker process) -- At-least-once requires consumer-side idempotency -- Worker must run somewhere — services must plan pod topology - ---- - -## ADR-008: Asymmetric JWT Only (RS256/384/512) - -**Status:** Accepted (v1.0.0) - -**Context:** -JWT can use symmetric (HMAC: HS256) or asymmetric (RSA: RS256) signing. Symmetric is simpler but requires shared secrets. - -**Decision:** -Only RS256, RS384, RS512 are in `ValidMethods`. Symmetric algorithms are blocked. - -**Rationale:** -- Asymmetric keys: private key signs (auth service only), public key verifies (any service) -- No shared secret risk — compromise of a verifying service doesn't compromise signing capability -- JWKS endpoint enables key rotation without redeploying all services -- Aligns with industry standard for inter-service JWT verification - -**Trade-off:** -- RSA keys are larger and slower than HMAC -- Requires a JWKS-serving auth service (or static public key management) - ---- - -## ADR-009: DB Alias Normalization Strategy - -**Status:** Accepted (v1.0.0) - -**Context:** -DB alias in env vars must be uppercase (e.g., `DB_TRANSACTION_HISTORY_DRIVER`), but Go maps are case-sensitive. - -**Decision:** -`config.NormalizeDBAlias(name)` lowercases all DB aliases. All internal keying uses normalized form. - -**Rationale:** -- Env var conventions use UPPERCASE -- Go idiomatic map keys use lowercase -- Normalization at a single point prevents scattered case handling -- `app.SQLByName("transaction_history")` always works regardless of env casing - -**Trade-off:** -- Non-obvious behavior — must be documented (see `docs/CONFIGURATION_PROFILES.md`) -- Accidental alias collision if services use aliases that normalize to the same string - ---- - -## ADR-010: Memcached Miss = Healthy - -**Status:** Accepted (v1.0.0) - -**Context:** -Readiness probes for cache could use GET (check miss vs. error) or SET/GET round-trips. - -**Decision:** -Memcached `/ready` check uses GET on a sentinel key. Cache miss = healthy. Only network/timeout errors = unhealthy. - -**Rationale:** -- A working Memcached that doesn't have a key is operating correctly -- Eliminates need for side-effect writes (SET) in readiness probes -- Network failures are the only meaningful signal for readiness -- Consistent with how caches actually behave in production - -**Trade-off:** -- Misleading to operators unfamiliar with the convention — must be documented diff --git a/.ai/integrations.md b/.ai/integrations.md deleted file mode 100644 index 0735314..0000000 --- a/.ai/integrations.md +++ /dev/null @@ -1,195 +0,0 @@ -# go-core · Integrations - -## External Service Dependencies - -All integrations are **opt-in** — only initialized when explicitly configured. -`go-core` handles connection lifecycle (open, health check, graceful close). -Business logic for each integration lives in the consuming service. - ---- - -## Databases (SQL) - -**Supported drivers:** `sqlserver` (MSSQL), `mysql`, `postgres` - -**Configuration:** -```env -DB_LIST=PRIMARY,READONLY # comma-separated alias list (UPPERCASE) -DB_PRIMARY_DRIVER=postgres -DB_PRIMARY_HOST=db.host -DB_PRIMARY_PORT=5432 -DB_PRIMARY_NAME=mydb -DB_PRIMARY_USER=user -DB_PRIMARY_PASSWORD=pass -DB_PRIMARY_REQUIRED=true # fail-fast if unavailable -DB_PRIMARY_MAX_OPEN_CONNS=25 -DB_PRIMARY_MAX_IDLE_CONNS=5 -DB_PRIMARY_CONN_MAX_LIFETIME=5m -DB_PRIMARY_CONN_MAX_IDLE_TIME=1m -``` - -**Access pattern:** -```go -db := app.SQLByName("primary") // normalized lowercase alias -``` - -**Lifecycle:** `app.New()` opens and health-checks pools; `lifecycle.Shutdown()` closes all pools. - -**Migration:** Uses [Goose](https://github.com/pressly/goose) with distributed lock. -```env -MIGRATION_AUTO_RUN=true -MIGRATION_DB_NAME=primary -MIGRATION_DIR=./migrations -MIGRATION_LOCK_ENABLED=true -MIGRATION_LOCK_TIMEOUT=60s -``` - ---- - -## Redis - -**Client library:** `go-redis/redis/v9` - -**Configuration:** -```env -REDIS_ENABLED=true -REDIS_ADDRESS=redis:6379 -REDIS_PASSWORD=secret -REDIS_DB=0 -``` - -**Access pattern:** -```go -redis := app.RedisCache() // nil if not enabled -``` - -**Readiness:** Ping check on startup + in `/ready`. Redis enabled = treated as required dependency. - ---- - -## Memcached - -**Client library:** `bradfitz/gomemcache` - -**Configuration:** -```env -MEMCACHED_ENABLED=true -MEMCACHED_SERVERS=mc1:11211,mc2:11211 -MEMCACHED_TIMEOUT=500ms -``` - -**Access pattern:** -```go -mc := app.MemcachedCache() // nil if not enabled -``` - -**Readiness note:** Cache miss is **intentionally healthy** — only network/timeout failures trigger 503. - ---- - -## Kafka - -**Client library:** `IBM/sarama` (wrapped) - -**Configuration:** -```env -KAFKA_ENABLED=true -KAFKA_BROKERS=kafka:9092,kafka2:9092 -KAFKA_CLIENT_ID=my-service -KAFKA_USERNAME=user # SASL plain -KAFKA_PASSWORD=pass -KAFKA_JKS_FILE=/path/cert.jks # TLS via JKS -KAFKA_JKS_PASSWORD=keystorepass -``` - -**Publisher:** -```go -pub, err := app.NewKafkaPublisher( - messaging.WithRetry(...), - messaging.WithDLQ("my-service.dlq"), - messaging.WithSuccessLog(true), -) -``` - -**Consumer:** -```go -consumer, err := app.NewKafkaConsumer( - "my.topic", "my-consumer-group", handler, - messaging.WithConcurrency(4), - messaging.WithRetry(...), -) -``` - -**Lifecycle:** Publisher and consumer lifecycle automatically registered via `app.NewKafka*`. -**Outbox:** Requires explicit `outbox.NewWorker(...)` and `worker.StartChecked(ctx)` in service. - ---- - -## OpenTelemetry (OTLP) - -**SDK:** `go.opentelemetry.io/otel` - -**Configuration:** -```env -OTLP_ENDPOINT=otel-collector:4317 -OTLP_INSECURE=true # disable TLS for internal collectors -OTLP_CA_CERT_FILE=/path/ca.crt # TLS CA cert (if not insecure) -TRACE_SAMPLING_RATIO=0.1 # 10% sampling in production -``` - -**Automatic:** Traces are injected at gRPC interceptor level — services don't need manual span creation for transport. -**Manual spans:** Use `go.opentelemetry.io/otel/trace` directly in service code. -**Exporter:** OTLP gRPC push to configured collector endpoint. -**Shutdown:** Registered in lifecycle — flushes pending spans on graceful shutdown. - ---- - -## JWKS Server (JWT Key Provider) - -**Client library:** `MicahParks/keyfunc/v3` - -**Configuration:** -```env -INTERNAL_JWT_ENABLED=true -INTERNAL_JWT_JWKS_ENDPOINT=https://auth.internal/jwks -INTERNAL_JWT_JWKS_REFRESH_INTERVAL=5m -INTERNAL_JWT_ISSUER=https://auth.internal -INTERNAL_JWT_AUDIENCE=my-service -``` - -**Behavior:** Keys are fetched and cached at startup. Background refresh at configured interval. -**Fallback:** If JWKS endpoint is empty, static `INTERNAL_JWT_PUBLIC_KEY` PEM is used. - ---- - -## Go Module Dependencies - -Key external dependencies (from `go.mod`): - -| Dependency | Purpose | -|---|---| -| `github.com/golang-jwt/jwt/v5` | JWT parsing and validation | -| `github.com/MicahParks/keyfunc/v3` | JWKS key fetching and caching | -| `github.com/IBM/sarama` | Kafka producer/consumer | -| `github.com/redis/go-redis/v9` | Redis client | -| `github.com/bradfitz/gomemcache` | Memcached client | -| `github.com/pressly/goose/v3` | Schema migration | -| `go.opentelemetry.io/otel` | Distributed tracing | -| `github.com/prometheus/client_golang` | Prometheus metrics | -| `go.uber.org/zap` | Structured logging | -| `github.com/sony/gobreaker/v2` | Circuit breaker | -| `github.com/grpc-ecosystem/grpc-gateway/v2` | HTTP-to-gRPC gateway | -| `google.golang.org/grpc` | gRPC transport | - -**Go version target:** `1.24` - ---- - -## No-Dependency Rule - -`go-core` must NOT depend on: -- Any consuming service's domain package -- Any product-specific library or SDK -- Any business-logic framework (no ORMs, no DI containers, no event buses) - -New external dependencies require explicit justification — keep the dependency surface minimal. diff --git a/.ai/modules.md b/.ai/modules.md deleted file mode 100644 index 84aed40..0000000 --- a/.ai/modules.md +++ /dev/null @@ -1,305 +0,0 @@ -# go-core · Modules - -## Module Reference - -### `app/` - -**Purpose:** Bootstrap and runtime container. Wires all infrastructure dependencies together. - -| Symbol | Description | -|---|---| -| `app.New(ctx, cfg)` | Initialize all infra (logger, metrics, DB, cache, tracing, lifecycle) | -| `app.App.Start(ctx)` | Block until context cancels, then trigger graceful shutdown | -| `app.App.SQLByName(name)` | Get DB pool by normalized alias | -| `app.App.SQLAll()` | Get all initialized DB pools | -| `app.App.RedisCache()` | Get Redis cache client (nil if disabled) | -| `app.App.MemcachedCache()` | Get Memcached client (nil if disabled) | -| `app.App.NewKafkaPublisher(opts...)` | Create publisher + register lifecycle close | -| `app.App.NewKafkaConsumer(...)` | Create consumer + register lifecycle close | -| `app.App.Lifecycle()` | Access lifecycle for custom shutdown hooks | -| `app.App.Logger()` | Access initialized logger | -| `app.App.Metrics()` | Access initialized Prometheus metrics | - -**Important:** `app.New()` does NOT start Kafka consumers or outbox workers. Service decides. - ---- - -### `config/` - -**Purpose:** Environment-driven typed configuration with strict validation. - -| Symbol | Description | -|---|---| -| `config.Load(ctx, cfg)` | Parse env vars into `*config.Config` struct | -| `cfg.Validate()` | Fail-fast validation — returns first error | -| `cfg.ValidateIssues()` | Structured validation — returns all issues | -| `config.NormalizeDBAlias(name)` | Lowercase normalization of DB alias keys | - -**Key config structs:** -``` -Config -├── AppConfig SERVICE_NAME, ENVIRONMENT, LOG_LEVEL, SHUTDOWN_TIMEOUT -├── Databases map[alias] → DBConfig (driver, DSN, pool settings, required) -├── GRPCConfig GRPC_PORT, TLS settings -├── HTTPConfig HTTP_PORT, TLS settings, pprof -├── ObservabilityConfig OTLP endpoint, sampling ratio -├── AuthConfig -│ ├── InternalJWTConfig JWT enable/keys/issuer/audience/methods/leeway -│ └── SignatureConfig HMAC signature key/headers/drift -├── RedisConfig enable, address, password, DB -├── MemcachedConfig enable, servers, timeout -└── KafkaConfig enable, brokers, SASL, JKS -``` - -**DB alias normalization:** `DB_LIST=TRANSACTION_HISTORY` → env prefix `DB_TRANSACTION_HISTORY_*` -→ key normalized to `"transaction_history"` in `app.SQLByName("transaction_history")` - ---- - -### `server/` - -**Purpose:** gRPC and HTTP gateway orchestration, readiness, and health. - -| Symbol | Description | -|---|---| -| `server.Run(...)` | Start gRPC + HTTP gateway, block, graceful shutdown | -| `server.LogStartupReadiness(...)` | Emit readiness log after bind | -| Standard endpoints | `GET /ready`, `GET /health`, `GET /metrics` | - -**gRPC interceptors (always active):** -- OTEL tracing -- Request ID injection -- Auth extraction/verification (JWT or metadata) -- Request metrics (`app_request_total`, `app_request_duration_seconds`) -- Service metrics via `ServiceLog` -- Panic recovery + sanitized error response - -**Gateway middleware (always active):** -- Request ID injection -- HTTP panic recovery -- HMAC signature validation (if enabled) -- HTTP metrics (`app_http_request_total`, `app_http_request_duration_seconds`) - ---- - -### `security/` - -**Purpose:** JWT verification and claims extraction. - -| Symbol | Description | -|---|---| -| `security.NewInternalJWTVerifier(cfg)` | Build verifier from config (JWKS or static key) | -| `verifier.Verify(token)` | Validate token, return `*Claims` or error | -| `verifier.ShouldAuthenticate(method)` | Check include/exclude method policy | -| `verifier.AuthMode()` | Returns `"jwt"` or `"metadata"` | -| `verifier.ConfigMetadata()` | Returns startup diagnostics map | -| `security.ExtractFromMetadata(ctx)` | Extract claims from gRPC metadata headers | -| `security.AuthErrorCode(err)` | Stable error code string for logging | - -See `.ai/security.md` for full auth flow. - ---- - -### `errors/` - -**Purpose:** Canonical error contract and transport mapping. - -| Symbol | Description | -|---|---| -| `errors.AppError` | Canonical error type: `Code`, `Message`, `Category`, `Details`, `Err` | -| `errors.New(code, msg)` | Create AppError without wrapping | -| `errors.Wrap(code, msg, err)` | Create AppError wrapping internal error | -| `errors.Validation(msg, details...)` | Create validation error with field details | -| `errors.ToGRPC(err)` | Map AppError → gRPC status code | - -**Error code → gRPC mapping:** -``` -INVALID_REQUEST → InvalidArgument -UNAUTHORIZED → Unauthenticated -FORBIDDEN → PermissionDenied -NOT_FOUND → NotFound -SESSION_EXPIRED → Unauthenticated -SERVICE_UNAVAILABLE → Unavailable -INTERNAL_ERROR → Internal -``` - -**Critical rule:** `Err` field (internal error) is NEVER exposed to clients — stays in logs only. - ---- - -### `logger/` - -**Purpose:** Structured Zap-based logging with redaction and distinct log flavors. - -| Symbol | Description | -|---|---| -| `logger.New(service, level)` | Create logger instance | -| `logger.Logger.LogService(ctx, log)` | Service-flow log → `service_log` | -| `logger.Logger.LogDB(ctx, log)` | Database operational log → `db_log` | -| `logger.Logger.LogTransaction(ctx, log)` | Business transaction log → `transaction_log` | -| `logger.Logger.Info/Warn/Error(ctx, msg, fields...)` | Generic structured log | -| `logger.Logger.WithComponent(name)` | Create child logger with component tag | - -**Log flavors:** -- `ServiceLog` — standard service operation flow (use by default) -- `DBLog` — DB query/operation diagnostics -- `TransactionLog` — platform-standard for transaction-oriented services only - -**Redaction:** Automatic on any sensitive key in any log field map. See `.ai/security.md`. - ---- - -### `observability/` - -**Purpose:** Prometheus metrics registry and OTEL tracing bootstrap. - -**Key metrics (stable — do not change names):** - -| Metric | Labels | Purpose | -|---|---|---| -| `app_request_total` | service, method, status | gRPC request count | -| `app_request_duration_seconds` | service, method | gRPC latency | -| `app_http_request_total` | service, method, route, status | HTTP request count | -| `app_http_request_duration_seconds` | service, method, route | HTTP latency | -| `app_service_operation_total` | service, operation, status | Service-level ops | -| `app_service_operation_duration_seconds` | service, operation | Service-level latency | -| `app_db_operation_total` | service, db_name, operation, status | DB ops | -| `app_db_operation_duration_seconds` | service, db_name, operation | DB latency | -| `app_message_publish_total` | service, topic, status | Kafka publishes | -| `app_message_consume_total` | service, topic, group, status | Kafka consumes | -| `app_message_process_duration_seconds` | service, topic, group | Consumer latency | -| `app_outbox_batch_total` | service, status | Outbox batch runs | -| `app_outbox_batch_duration_seconds` | service | Outbox batch latency | -| `app_outbox_batch_size` | service | Outbox batch distribution | -| `app_transaction_total` | service, operation, status | Business transactions | - -**Important:** Metrics are registered as global singletons via `sync.Once`. Do not call `NewMetrics()` more than once per process. - ---- - -### `dbtx/` - -**Purpose:** SQL transaction orchestration and context propagation. - -| Symbol | Description | -|---|---| -| `dbtx.WithTx(ctx, db, fn)` | Execute fn in a transaction (default options) | -| `dbtx.WithTxOptions(ctx, db, opts, fn)` | Execute fn with custom `*sql.TxOptions` | -| `dbtx.Inject(ctx, tx)` | Store `*sql.Tx` in context | -| `dbtx.FromContext(ctx)` | Retrieve `*sql.Tx` from context (or fallback to `*sql.DB`) | - -See `.ai/transactions.md` for full transaction flow. - ---- - -### `messaging/` - -**Purpose:** Kafka producer, consumer, and transactional outbox abstractions. - -| Symbol | Description | -|---|---| -| `messaging.NewKafkaPublisher(cfg, opts...)` | Create Kafka producer | -| `messaging.NewKafkaConsumer(cfg, topic, group, handler, opts...)` | Create consumer | -| `messaging.Publisher.Publish(ctx, msg)` | Publish message directly | -| `messaging.Consumer.Start(ctx)` | Start consuming | -| `outbox.NewWorker(repo, publisher, opts...)` | Create outbox worker | -| `outbox.Worker.StartChecked(ctx)` | Start worker loop (service-owned goroutine) | -| `outbox.Worker.RunOnce(ctx)` | Process one batch (for tests/admin jobs) | -| `outbox.NewPublisher(repo)` | Write outbox records in transaction | -| `outbox.Publisher.PublishTx(ctx, topic, payload)` | Write outbox row (must be inside dbtx) | - -**Outbox delivery guarantee:** At-least-once. Consumer must handle duplicate detection. -**Worker is never auto-started by `go-core`.** Service decides process topology. - ---- - -### `resilience/` - -**Purpose:** Retry with exponential backoff, circuit breaker, and timeout. - -| Symbol | Description | -|---|---| -| `resilience.Do(ctx, opts, fn)` | Execute fn with retry and backoff | -| `resilience.DefaultRetryOptions()` | Baseline options (MaxAttempts=1, no retry) | -| `resilience.NewCircuitBreaker(opts)` | Sony Gobreaker wrapper | -| `resilience.DefaultCircuitBreakerOptions(name)` | Baseline CB (trips at 5 consecutive failures) | -| `resilience.WithTimeout(ctx, d, fn)` | Execute fn with deadline | -| `resilience.IsTransientError(err)` | Default retryable predicate | - -**Default retry = 1 attempt = no retry.** Set `MaxAttempts > 1` explicitly. - ---- - -### `cache/` - -**Purpose:** Redis and Memcached adapters with unified `Cache` interface. - -| Symbol | Description | -|---|---| -| `cache.NewRedisFromConfig(cfg, logger)` | Create Redis client | -| `cache.NewMemcachedFromConfig(cfg, logger)` | Create Memcached client | -| `cache.Cache` | Interface: `Get`, `Set`, `Delete`, `Close` | - -**Readiness behavior:** -- Redis: ping check at startup and in `/ready` -- Memcached: cache-miss is **healthy**; network error is unhealthy - ---- - -### `migration/` - -**Purpose:** Goose-based schema migration with distributed locking. - -| Symbol | Description | -|---|---| -| `migration.AutoRunUp(ctx, db, cfg)` | Run pending migrations at startup | -| `migration.AutoRunUpWithLogger(ctx, db, cfg, log)` | Same with structured startup logging | - -**Distributed lock:** Prevents concurrent migration in multi-pod K8s deployments. -Lock is implemented per DB driver: `sp_getapplock` (MSSQL), `GET_LOCK` (MySQL), advisory lock (Postgres). - ---- - -### `httpclient/` - -**Purpose:** Resilient outbound HTTP client — wraps Resty with circuit breaker, retry, OTEL tracing, and structured logging. Available from **v1.1.0**. - -| Symbol | Description | -|---|---| -| `httpclient.NewClient(log, opts...)` | Create HTTP client with configured resilience | -| `client.Do(ctx, req, method, url)` | Execute request inside circuit breaker | -| `client.Get(ctx, url)` | Convenience GET | -| `client.Post(ctx, url, body)` | Convenience POST | -| `client.Request()` | Returns a raw `*resty.Request` for full control | - -**Options:** -```go -httpclient.WithTimeout(5 * time.Second) -httpclient.WithRetry(&resilience.RetryOptions{MaxAttempts: 3, ...}) -httpclient.WithCircuitBreaker(&resilience.CircuitBreakerOptions{...}) -httpclient.WithTracing(true) -httpclient.WithUserAgent("my-service/1.0") -``` - -**Behavior:** -- Emits `httpclient_request` ServiceLog on every outbound call (before) -- Emits `httpclient_response` ServiceLog on every response (after), with `status_code` and `duration_ms` -- HTTP 5xx responses **trigger the circuit breaker** counter -- HTTP 429 and 5xx trigger retry (when retry is configured) -- OTEL trace context is propagated via `otelhttp` transport - -**Rule:** Use `httpclient` for all outbound service-to-service HTTP calls — not `http.DefaultClient`. - ---- - -### `version/` - -**Purpose:** Build metadata injection. - -| Symbol | Description | -|---|---| -| `version.Version` | Semver string | -| `version.Commit` | Git commit SHA | -| `version.BuildDate` | Build timestamp | - -Must be set via `ldflags` at build time. Emitted at startup in `app_init` log. diff --git a/.ai/prompts/architecture-consult.md b/.ai/prompts/architecture-consult.md deleted file mode 100644 index 18b8593..0000000 --- a/.ai/prompts/architecture-consult.md +++ /dev/null @@ -1,44 +0,0 @@ -# Prompt: Architecture Consultation - -> **When to use:** Unsure about a design decision. Get structured analysis before implementing. - ---- - -``` -You are a principal Go engineer advising on an architecture decision for go-core. - -go-core is an infrastructure foundation library for Go microservices. -Decisions here become precedents for all downstream services. - -Read: .ai/context.md, .ai/architecture.md, .ai/decisions.md - -== QUESTION == -{{ Describe the design decision, trade-off, or architectural question }} - -== DELIVER == - -1. BOUNDARY VERDICT - - Domain-agnostic: YES / NO - - Reusable across services: YES / NO - - Infrastructure, not business logic: YES / NO - → BELONGS IN: go-core | consuming service | utils-shared - -2. PRECEDENT - Existing pattern, module, or ADR that is directly relevant. - Reference specific files and function names. - -3. OPTIONS - | Option | Pros | Cons | Risk | - |--------|------|------|------| - -4. RISK (for recommended option) - - Compatibility risk - - Scaling risk - - Maintenance risk - -5. RECOMMENDATION — which option and why - -6. NEXT STEP — first concrete action to take - -Base analysis on this repo's context, not generic best practices alone. -``` diff --git a/.ai/prompts/breakdown.md b/.ai/prompts/breakdown.md deleted file mode 100644 index b700e75..0000000 --- a/.ai/prompts/breakdown.md +++ /dev/null @@ -1,56 +0,0 @@ -# Prompt: Task Breakdown - -> **When to use:** Before writing any code. Plan the change, assess risk, define scope. - ---- - -``` -You are a senior Go framework engineer planning a change to go-core. - -go-core is an infrastructure foundation library for Go microservices. -NOT a business service. NOT a generic utils repo. Domain-agnostic. -Every change here affects all downstream consuming services. - -Read: .ai/context.md - -== TASK == -{{ Describe the requested change }} - -== DELIVER == - -1. BOUNDARY CHECK - Does this belong in go-core or in a consuming service? - Is it domain-agnostic? Can multiple services use it without modification? - → If NOT in go-core, explain where it belongs instead. Stop here. - -2. AFFECTED LAYERS - [ ] config / env contract - [ ] app bootstrap / lifecycle - [ ] transport (grpc / gateway) - [ ] security / auth - [ ] errors contract - [ ] logger / observability - [ ] database / dbtx / migration - [ ] cache / messaging / resilience - -3. CONTRACT RISK - [ ] Public API change → semver impact (patch / minor / major) - [ ] Config/env change → MIGRATION.md required - [ ] Runtime behavior change → test coverage required - [ ] Metric name/label change → dashboard coordination required - -4. IMPLEMENTATION STEPS - Ordered from lowest to highest risk. - -5. FILES TO CHANGE - List specific files and why. - -6. TESTS REQUIRED - -7. DOCS TO UPDATE - [ ] README.md [ ] MIGRATION.md [ ] CHANGELOG.md [ ] docs/ [ ] .ai/ - -8. ACCEPTANCE CRITERIA - -Do not implement yet. -``` diff --git a/.ai/prompts/execute.md b/.ai/prompts/execute.md deleted file mode 100644 index a4c5e8b..0000000 --- a/.ai/prompts/execute.md +++ /dev/null @@ -1,35 +0,0 @@ -# Prompt: Execute / Implement - -> **When to use:** You have a clear plan and are ready to implement. - ---- - -``` -You are a senior Go framework engineer implementing a change in go-core. - -go-core is an infrastructure foundation library for Go microservices. -NOT a business service. NOT a generic utils repo. Domain-agnostic. Target Go 1.24. - -Read: .ai/context.md, .ai/conventions.md - -== TASK == -{{ Describe exactly what must be implemented }} - -== HARD CONSTRAINTS == -- ctx context.Context is always the first parameter in runtime functions -- Use errors.AppError — never invent parallel error types -- Sanitize errors before returning to clients — internals stay in logs only -- Use LogService / LogDB / LogTransaction — not raw string logs -- dbtx.WithTx owns commit/rollback — repositories use dbtx.FromContext -- No hardcoded service names, DB aliases, topic names, or product-specific defaults -- No background goroutines started automatically — lifecycle must be explicit -- No new public API unless the task requires it -- No new external dependencies without justification -- Update tests if any public behavior changes - -== OUTPUT == -1. Full implementation — paste-ready files, not snippets -2. Tests — full test file, paste-ready -3. Docs — which .ai/ or docs/ files to update and what to change -4. MIGRATION.md entry — if any public contract changed -``` diff --git a/.ai/prompts/fix.md b/.ai/prompts/fix.md deleted file mode 100644 index 7364896..0000000 --- a/.ai/prompts/fix.md +++ /dev/null @@ -1,29 +0,0 @@ -# Prompt: Fix / Debug - -> **When to use:** There is a bug, error, or unexpected behavior to diagnose and fix. - ---- - -``` -You are a senior Go engineer debugging an issue in go-core. - -go-core is an infrastructure foundation library for Go microservices. -Fix only what is broken — a change here affects all consuming services. - -Read: .ai/context.md - -== PROBLEM == -{{ Paste the error message, stack trace, or describe the unexpected behavior }} - -== WHAT WAS TRIED == -{{ Describe previous attempts, or "none" }} - -== DELIVER == -1. ROOT CAUSE — actual cause with file/line reference, not the symptom -2. FIX — minimal corrected file(s), paste-ready -3. WHY — brief explanation of why this fix is correct -4. SIDE EFFECTS — what else in go-core or consuming services could be affected -5. REGRESSION TEST — full test that would have caught this bug, paste-ready - -Fix only the reported problem. No unrelated refactoring. -``` diff --git a/.ai/prompts/new-feature.md b/.ai/prompts/new-feature.md deleted file mode 100644 index f5ad0fd..0000000 --- a/.ai/prompts/new-feature.md +++ /dev/null @@ -1,52 +0,0 @@ -# Prompt: Add New Feature / Module - -> **When to use:** Adding a new module or feature to go-core. - ---- - -``` -You are a senior Go framework engineer adding a new feature to go-core. - -go-core is an infrastructure foundation library for Go microservices. -NOT a business service. NOT a generic utils repo. Target Go 1.24. - -Read: .ai/context.md, .ai/modules.md, .ai/conventions.md - -== FEATURE == -{{ Describe the new feature or module }} - -== BOUNDARY CHECK — answer before implementing == -1. Domain-agnostic? (usable by multiple services without modification) -2. Infrastructure/framework concern, not business logic? -3. NOT a generic utility that belongs in utils-shared? - -All YES → implement. Any NO → explain where it belongs instead. Stop here. - -== IMPLEMENTATION == - -1. PACKAGE & FILE STRUCTURE - -2. PUBLIC API - Exported types, functions, interfaces. Minimal surface. Options pattern for extensibility. - -3. CODE — full file(s), paste-ready - -4. TESTS — full test file, paste-ready - -5. APP INTEGRATION (if needed) - Injection into App struct or lifecycle? Show app.go change. - -6. DOCS TO UPDATE - - .ai/modules.md — add module entry - - .ai/context.md — add to module table if significant - - .ai/integrations.md — add env config if new vars needed - - CHANGELOG.md - -== CONSTRAINTS == -- ctx context.Context always first parameter -- Options pattern for constructors with more than 2 config values -- Register cleanup in lifecycle if resource needs closing -- No goroutines started automatically -- No hardcoded names (service, DB alias, topic) -- No new external dependencies without justification -``` diff --git a/.ai/prompts/review.md b/.ai/prompts/review.md deleted file mode 100644 index a3248d4..0000000 --- a/.ai/prompts/review.md +++ /dev/null @@ -1,53 +0,0 @@ -# Prompt: Code Review - -> **When to use:** Before merging any change. - ---- - -``` -You are a senior Go framework engineer reviewing a change in go-core. - -go-core is an infrastructure foundation library for Go microservices. -Every change here is a public contract — treat it accordingly. - -Read: .ai/context.md - -== CHANGE == -{{ Describe what was changed, or reference the diff / PR }} - -== REVIEW == - -Rate each: ✅ OK | ⚠️ Needs attention | ❌ Must fix - -COMPATIBILITY -[ ] Breaking change for consuming services? -[ ] Semver impact correct? (patch / minor / major) -[ ] MIGRATION.md entry needed? - -BOUNDARY -[ ] Code is domain-agnostic? No business logic? -[ ] Belongs in go-core, not a consuming service? -[ ] No product-specific naming or assumptions? - -SECURITY -[ ] Sensitive data cannot leak into logs or API responses? -[ ] No hardcoded credentials? -[ ] Errors sanitized — no internal detail exposed to clients? -[ ] Auth changes: JWT restricted to RS256/RS384/RS512? - -CONCURRENCY -[ ] No race conditions or shared mutable state? -[ ] No hidden background goroutines? - -OBSERVABILITY -[ ] Metric names/labels consistent with existing contracts? -[ ] Log fields follow ServiceLog / DBLog / TransactionLog? -[ ] Sensitive log fields auto-redacted? - -TESTS & DOCS -[ ] Tests cover success + all documented error paths? -[ ] README / docs / .ai/ aligned with implementation? - -== VERDICT == -PASS | NEEDS REVISION — list specific items to address. -``` diff --git a/.ai/prompts/security-review.md b/.ai/prompts/security-review.md deleted file mode 100644 index 7c7ccdf..0000000 --- a/.ai/prompts/security-review.md +++ /dev/null @@ -1,51 +0,0 @@ -# Prompt: Security Review - -> **When to use:** Any change touching auth, tokens, data handling, secrets, or HTTP transport. - ---- - -``` -You are a security engineer reviewing a change in go-core. - -go-core is used by ALL Go microservices — a gap here is a gap everywhere. - -Read: .ai/security.md - -== CHANGE == -{{ Describe what was changed, or reference the diff / PR }} - -== AUDIT == - -Rate each: ✅ SAFE | ⚠️ NEEDS ATTENTION | ❌ VULNERABILITY - -AUTH & TOKEN -[ ] JWT accepts only RS256 / RS384 / RS512 — no HS* algorithms -[ ] exp, nbf, iat validated correctly -[ ] Issuer and audience validated when configured -[ ] Raw JWT tokens never logged -[ ] Auth errors sanitized before returning to clients -[ ] Method include/exclude policy applied correctly - -DATA PROTECTION -[ ] Sensitive fields (password, token, card, pin, otp, cvv, secret, private_key) auto-redacted in logs -[ ] Internal error detail does not leak into API responses -[ ] No SQL injection risk in constructed queries - -SECRETS -[ ] All secrets from environment variables — nothing hardcoded -[ ] Secrets never appear in error messages or response bodies -[ ] DB DSN masked in logs - -HTTP (if gateway is touched) -[ ] HMAC signature uses timing-safe comparison -[ ] Timestamp drift check prevents replay attacks - -CONCURRENCY -[ ] No race conditions on shared state -[ ] No goroutine leaks or unclosed resources on error paths - -== VERDICT == -SEVERITY: Critical | High | Medium | Low | None -APPROVED TO MERGE: YES / NO -Fixes required: (list ❌ and ⚠️ items with specific remediation) -``` diff --git a/.ai/prompts/test.md b/.ai/prompts/test.md deleted file mode 100644 index f00f41d..0000000 --- a/.ai/prompts/test.md +++ /dev/null @@ -1,34 +0,0 @@ -# Prompt: Write Tests - -> **When to use:** Adding or updating tests for a module or exported behavior. - ---- - -``` -You are a senior Go engineer writing tests for go-core. - -go-core is an infrastructure foundation library for Go microservices. -Tests here guard public contracts that all downstream services depend on. - -Read: .ai/context.md - -== SCOPE == -{{ Name the module/function to test, or "full coverage" for the whole file }} - -== RULES == -- Table-driven tests for multiple cases -- Isolate external dependencies: DB → sqlmock, HTTP → httptest, functions → interface mocks -- Cover for every exported symbol: - ✅ Happy path - ✅ All documented error paths - ✅ Edge cases (nil, empty, zero, boundary values) -- Test names: Test_ -- Test public behavior, not internal implementation -- Must pass: go test ./... -- No real external services required (DB, Redis, Kafka, etc.) - -== OUTPUT == -1. Complete test file — paste-ready, all imports included -2. Coverage summary — what is covered, what edge cases included -3. Gaps — any exported behavior that cannot be tested without refactoring -``` diff --git a/.ai/security.md b/.ai/security.md deleted file mode 100644 index fbc1700..0000000 --- a/.ai/security.md +++ /dev/null @@ -1,170 +0,0 @@ -# go-core · Security - -## Auth Flow Overview - -``` -Incoming Request - │ - ├─ [HTTP] → gateway middleware - │ └─ Signature validation (if AUTH_SIGNATURE_ENABLED=true) - │ ├─ HMAC-SHA256 of body using MASTER_KEY - │ ├─ Timestamp drift check (MAX_TIME_DRIFT) - │ └─ Reject → 401 if invalid - │ - └─ [gRPC] → auth interceptor - └─ JWT enabled? (INTERNAL_JWT_ENABLED) - ├─ YES → JWT verification path - │ ├─ Extract Bearer token from metadata - │ ├─ Validate RS256/RS384/RS512 signature - │ ├─ Validate exp, nbf, iat - │ ├─ Validate issuer (if configured) - │ ├─ Validate audience (if configured) - │ ├─ Check include/exclude method policy - │ └─ Inject Claims into context - │ - └─ NO → Metadata extraction (non-enforcing) - ├─ x-subject → Claims.Subject - ├─ x-session-id → Claims.SessionID - ├─ x-role → Claims.Role - └─ x-claim- → Claims.Attributes[""] -``` - ---- - -## JWT Verification Details - -**Supported algorithms:** `RS256`, `RS384`, `RS512` (asymmetric only — symmetric HS* is blocked) - -**Key sources (mutually exclusive, JWKS takes priority):** -1. `INTERNAL_JWT_JWKS_ENDPOINT` — dynamic key refresh via MicahParks/keyfunc v3 -2. `INTERNAL_JWT_PUBLIC_KEY` — static RSA public key (PEM string or file path) - -**Claims extracted into `security.Claims`:** -```go -type Claims struct { - Subject string // "sub" - SessionID string // "session_id" or "sid" - Role string // "role" - Attributes map[string]string // "attributes" map -} -``` - -**Method policy (evaluated at request time):** -- `INTERNAL_JWT_INCLUDE_METHODS` — only these gRPC methods require JWT -- `INTERNAL_JWT_EXCLUDE_METHODS` — all methods except these require JWT -- If neither is set → all methods require JWT when enabled -- Include list takes precedence over exclude list - -**Leeway:** Default 30s clock skew tolerance (configurable via `INTERNAL_JWT_LEEWAY`) - ---- - -## HTTP Payload Signature (HMAC) - -Enabled via `AUTH_SIGNATURE_ENABLED=true`. - -- Signature computed as HMAC-SHA256 of request body using `AUTH_MASTER_KEY` -- Signature sent in header key `AUTH_HEADER_KEY` (configurable) -- Timestamp header `AUTH_TIMESTAMP_KEY` validated against `AUTH_MAX_TIME_DRIFT` -- Protects against replay attacks and payload tampering on HTTP gateway - ---- - -## Data Protection & Redaction - -**Auto-redacted log keys** (matched by substring, case-insensitive): - -``` -password, passwd, secret, token, authorization, -apikey, api_key, pin, otp, cvv, card, private_key -``` - -**Redaction strategy:** Last 2 characters shown, remainder masked with `*` -- Example: `"mysecrettoken"` → `"***********en"` -- Empty values → `"**"` - -**Applies to:** -- `map[string]interface{}` fields in `ServiceLog`, `DBLog`, `TransactionLog` -- `map[string]string` in `Claims.Attributes` -- Recursive traversal of nested maps - -**What is NEVER logged:** -- Raw JWT tokens -- Database passwords (DSN is masked in logs) -- Card numbers, CVV, full payment credentials -- Full request bodies - ---- - -## Secrets Handling - -| Secret | Storage | Access Pattern | -|---|---|---| -| JWT public key | Env var or file path | Loaded once at startup via `NewInternalJWTVerifier` | -| HMAC master key | Env var | Read from `config.SignatureConfig.MasterKey` | -| DB password | Env var | Injected into DSN at config load, masked in logs | -| Redis password | Env var | Passed to Redis client, never logged | -| Kafka SASL password | Env var | JKS or SASL plain, configurable via `KafkaConfig` | - -**Rules:** -- All secrets arrive via environment variables — no hardcoded credentials -- Secrets are never returned in API responses or error messages -- `config.Validate()` checks for required secret fields and rejects missing values at startup -- JWKS endpoint preferred over static key for key rotation support - ---- - -## Auth Error Handling - -Auth failures are sanitized before reaching the client: - -| Error Type | Client Response | Internal Log | -|---|---|---| -| Empty token | `UNAUTHORIZED` | `authorization_token_empty` | -| Invalid signature/expired | `UNAUTHORIZED` | `invalid_token` | -| Wrong issuer | `UNAUTHORIZED` | `invalid_token_issuer` | -| Wrong audience | `UNAUTHORIZED` | `invalid_token_audience` | - -Auth error codes are exposed via `security.AuthErrorCode(err)` for structured logging. -The gRPC interceptor always logs a `grpc_request` `ServiceLog` with the `auth_error_code` field. - ---- - -## RBAC - -`go-core` provides the **infrastructure** for RBAC but does **not enforce** role-based policies. - -- `Claims.Role` carries the role string extracted from the JWT `role` claim -- `Claims.Attributes` carries arbitrary key-value claims (e.g., `tenant_id`, `scope`) -- Consuming services implement their own authorization logic using the injected claims -- `go-core` does not implement permission checks or role hierarchies — those are service-owned concerns - ---- - -## Security Startup Observability - -On startup, the gRPC server emits an `auth_config` `ServiceLog` with: -```json -{ - "auth_mode": "jwt" | "metadata", - "policy_mode": "all" | "include" | "exclude" | "metadata", - "jwt_enabled": true | false, - "issuer_set": true | false, - "audience_set": true | false, - "include_method_count": 0, - "exclude_method_count": 0, - "leeway_ms": 30000 -} -``` - -This ensures operators can verify auth configuration at startup without inspecting environment variables. - ---- - -## Security Anti-Patterns to Avoid - -- ❌ Using `INTERNAL_JWT_ENABLED=false` (metadata mode) for public-facing services -- ❌ Logging `Claims` or raw token values directly -- ❌ Adding HS256/HS512 to `ValidMethods` — this breaks RSA key requirements -- ❌ Storing secrets in config files committed to source control -- ❌ Growing `Claims` top-level fields with product-specific data — use `Attributes` map diff --git a/.ai/tasks/align_ci_and_release_hygiene.md b/.ai/tasks/align_ci_and_release_hygiene.md deleted file mode 100644 index 9f1e9f0..0000000 --- a/.ai/tasks/align_ci_and_release_hygiene.md +++ /dev/null @@ -1,30 +0,0 @@ -Status: done - -Task: align ci and release hygiene - -Goal: -make repository quality gates visible both in CI and in release docs - -Scope Layers: - -ci -docs - -Allowed Paths: - -.github/ -docs/ -README.md -Makefile -scripts/ - -Constraints: - -CI should run `go test ./...`, `go vet ./...`, and `golangci-lint run` -keep stronger local quality gate with race testing and gosec -avoid changing runtime behavior - -Expected Output: - -- baseline CI workflow -- release docs aligned with local and CI gate expectations diff --git a/.ai/tasks/align_scripts_and_gates.md b/.ai/tasks/align_scripts_and_gates.md deleted file mode 100644 index 9f42160..0000000 --- a/.ai/tasks/align_scripts_and_gates.md +++ /dev/null @@ -1,36 +0,0 @@ -Status: done - -Task: align scripts and quality gates - -Goal: -make local scripts and repo gates reflect the current maturity of `go-core` as a foundation repo - -Scope Layers: - -ci -docs -scripts -ai - -Allowed Paths: - -scripts/ -Makefile -.golangci.yml -.github/ -docs/ -.ai/ -README.md - -Constraints: - -avoid runtime behavior changes -keep gates actionable and fast enough for regular use -align local gates, CI, and release docs - -Expected Output: - -- more coherent local and CI quality workflow -- docs and scripts that describe the same expectations -- CI baseline explicitly mapped to Makefile targets -- release evidence and sign-off docs aligned with the same gate model diff --git a/.ai/tasks/align_transport_contracts.md b/.ai/tasks/align_transport_contracts.md deleted file mode 100644 index 7c417e7..0000000 --- a/.ai/tasks/align_transport_contracts.md +++ /dev/null @@ -1,33 +0,0 @@ -Status: done - -Task: align transport contracts - -Goal: -bring gRPC and gateway behavior in line with the newer foundation contracts for logging, metrics, request ID, and errors - -Scope Layers: - -runtime -tests -docs -ai - -Allowed Paths: - -server/grpc/ -server/gateway/ -docs/ -.ai/ -README.md - -Constraints: - -keep transport behavior explicit -preserve compact external error responses -align request ID, metrics, and service logging across transports - -Expected Output: - -- better transport consistency -- tests covering aligned behavior -- docs that explain the transport contract clearly diff --git a/.ai/tasks/clarify_boundary_and_docs.md b/.ai/tasks/clarify_boundary_and_docs.md deleted file mode 100644 index b3f3de8..0000000 --- a/.ai/tasks/clarify_boundary_and_docs.md +++ /dev/null @@ -1,32 +0,0 @@ -Status: done - -Task: clarify repository boundary and align docs - -Goal: -make boundary guidance consistent across README, docs, legacy AI shims, and `.ai` - -Scope Layers: - -docs -ai - -Allowed Paths: - -.ai/ -docs/ -README.md -AI_RULES.md -CONTEXT.md - -Constraints: - -keep `go-core` foundation-oriented -allow selected platform-standard technical contracts only when intentionally standardized -do not move business rules into `go-core` -keep `utils-shared` as the home for generic utilities - -Expected Output: - -- one consistent repository boundary narrative -- legacy AI files reduced to compatibility shims or concise summaries -- docs that distinguish `dbtx` from transaction observability diff --git a/.ai/tasks/define_versioning_upgrade_discipline.md b/.ai/tasks/define_versioning_upgrade_discipline.md deleted file mode 100644 index 7f9740b..0000000 --- a/.ai/tasks/define_versioning_upgrade_discipline.md +++ /dev/null @@ -1,33 +0,0 @@ -Status: done - -Task: define versioning and upgrade discipline - -Goal: -prepare `go-core` for broader multi-service adoption with clearer versioning and upgrade expectations - -Scope Layers: - -docs -ai -versioning - -Allowed Paths: - -docs/ -MIGRATION.md -README.md -version/ -.ai/ - -Constraints: - -avoid inventing heavy release process machinery -focus on simple, explicit upgrade discipline -align migration notes with public contract changes - -Expected Output: - -- clearer versioning and upgrade guidance -- migration note discipline tied to public contract changes -- `.ai` context updated to enforce the same expectations -- release metadata expectations documented through `version.Version`, `version.Commit`, and `version.BuildDate` diff --git a/.ai/tasks/document_service_bootstrap_golden_path.md b/.ai/tasks/document_service_bootstrap_golden_path.md deleted file mode 100644 index 917f9ab..0000000 --- a/.ai/tasks/document_service_bootstrap_golden_path.md +++ /dev/null @@ -1,30 +0,0 @@ -Status: done - -Task: document canonical service bootstrap path - -Goal: -provide one clear onboarding path for service teams consuming `go-core` - -Scope Layers: - -docs -examples -templates - -Allowed Paths: - -docs/ -examples/ -templates/ -README.md - -Constraints: - -show `config.Load -> Validate -> app.New -> transport wiring -> server.Run` -keep transaction observability opt-in for transaction-oriented services only -do not imply every service must use every optional dependency - -Expected Output: - -- golden-path service bootstrap guidance -- examples and templates aligned with the intended onboarding flow diff --git a/.ai/tasks/formalize_messaging_outbox_pattern.md b/.ai/tasks/formalize_messaging_outbox_pattern.md deleted file mode 100644 index 54bc5d3..0000000 --- a/.ai/tasks/formalize_messaging_outbox_pattern.md +++ /dev/null @@ -1,32 +0,0 @@ -Status: done - -Task: formalize messaging and outbox service pattern - -Goal: -make direct publish vs outbox usage explicit for consuming services - -Scope Layers: - -docs -examples -ai - -Allowed Paths: - -docs/ -examples/ -.ai/ -README.md -messaging/ - -Constraints: - -keep worker ownership explicit in the consuming service -do not introduce hidden startup behavior -prefer additive helpers or docs over broad API churn - -Expected Output: - -- a blessed messaging pattern document -- example of write + outbox in one SQL transaction -- `.ai` context updated to point to the pattern diff --git a/.ai/tasks/formalize_transaction_observability.md b/.ai/tasks/formalize_transaction_observability.md deleted file mode 100644 index 1b5a60b..0000000 --- a/.ai/tasks/formalize_transaction_observability.md +++ /dev/null @@ -1,33 +0,0 @@ -Status: done - -Task: formalize transaction observability contract - -Goal: -lock the transaction monitoring contract so downstream transaction-oriented services implement it consistently - -Scope Layers: - -runtime -docs -tests - -Allowed Paths: - -logger/ -observability/ -docs/ -README.md - -Constraints: - -keep `TransactionLog`, `LogTransaction(...)`, and `app_transaction_total` -keep top-level fields small and stable -keep `UserID` top-level -use `Metadata` as the extension area -do not conflate transaction observability with `dbtx` - -Expected Output: - -- explicit contract guidance for `TransactionLog` -- docs that define field meaning and usage boundaries -- tests updated if contract behavior changes diff --git a/.ai/tasks/harden_error_contracts.md b/.ai/tasks/harden_error_contracts.md deleted file mode 100644 index 17ac12d..0000000 --- a/.ai/tasks/harden_error_contracts.md +++ /dev/null @@ -1,41 +0,0 @@ -Status: done - -Task: harden error contracts - -Goal: -improve consistency between app errors, gRPC mapping, REST responses, and service-facing guidance - -Scope Layers: - -runtime -tests -docs -ai - -Allowed Paths: - -errors/ -server/gateway/ -server/grpc/ -docs/ -.ai/ -README.md - -Constraints: - -keep external error payloads compact and sanitized -keep internal diagnostic detail in logs -prefer additive improvements over broad error-contract churn - -Expected Output: - -- tighter REST/gRPC error consistency -- clearer tests for mapping behavior -- docs that reflect the actual service-facing contract - -Implemented Notes: - -- `errors/` now owns the canonical public error response mapping for direct app errors and gRPC transport errors -- gRPC mapping preserves stable contract codes, including `SESSION_EXPIRED`, through `ErrorInfo.reason` -- gateway error responses now reuse canonical error mapping and sanitize unknown transport errors -- tests cover stable code round-trip, validation detail exposure, and gateway compact response behavior diff --git a/.ai/tasks/harden_release_adoption_workflow.md b/.ai/tasks/harden_release_adoption_workflow.md deleted file mode 100644 index 1fa0837..0000000 --- a/.ai/tasks/harden_release_adoption_workflow.md +++ /dev/null @@ -1,32 +0,0 @@ -Status: done - -Task: harden release and adoption workflow - -Goal: -make foundation-repo change discipline visible in docs, evidence templates, and contributor expectations - -Scope Layers: - -docs -ai -ci - -Allowed Paths: - -docs/ -.ai/ -.github/ -README.md -MIGRATION.md - -Constraints: - -avoid changing runtime behavior -keep release guidance concise and actionable -ensure `.ai` remains the primary source of truth for `forge` - -Expected Output: - -- change checklist for foundation changes -- release evidence aligned with that checklist -- `.ai` roadmap and tasks reflecting the current priority order diff --git a/.ai/tasks/improve_cache_layer.md b/.ai/tasks/improve_cache_layer.md deleted file mode 100644 index 8d4c63a..0000000 --- a/.ai/tasks/improve_cache_layer.md +++ /dev/null @@ -1,39 +0,0 @@ -Status: done - -Task: improve cache layer ergonomics and observability - -Goal: -make Redis and Memcached feel like first-class foundation dependencies, not just adapters - -Scope Layers: - -runtime -tests -docs -ai - -Allowed Paths: - -cache/ -docs/ -.ai/ -README.md - -Constraints: - -keep optional dependency semantics explicit -improve operational visibility without adding hidden behavior -align cache health and observability with the rest of the foundation - -Expected Output: - -- clearer cache operational behavior -- stronger tests for health/failure expectations -- docs aligned with cache usage in service foundations - -Implemented Notes: - -- Redis and Memcached now emit aligned `cache_connect` `ServiceLog` on startup success and failure -- cache health semantics are documented explicitly, including Memcached cache-miss-as-healthy behavior -- readiness tests cover enabled, disabled, healthy, and failed cache dependency states -- docs and README now describe enabled caches as explicit required runtime dependencies diff --git a/.ai/tasks/improve_config_dx.md b/.ai/tasks/improve_config_dx.md deleted file mode 100644 index 27d0793..0000000 --- a/.ai/tasks/improve_config_dx.md +++ /dev/null @@ -1,32 +0,0 @@ -Status: done - -Task: improve config ergonomics and validation DX - -Goal: -add structured config validation guidance without breaking the compact public validation path - -Scope Layers: - -config -docs -tests -ai - -Allowed Paths: - -config/ -docs/ -.ai/ -README.md - -Constraints: - -keep `Validate()` available as the compact public entry point -make structured validation additive -update docs and tests with any validation behavior change - -Expected Output: - -- structured validation issues -- grouped configuration onboarding guidance -- tests proving both compact and structured validation paths diff --git a/.ai/tasks/improve_messaging_outbox_runtime.md b/.ai/tasks/improve_messaging_outbox_runtime.md deleted file mode 100644 index 71639d7..0000000 --- a/.ai/tasks/improve_messaging_outbox_runtime.md +++ /dev/null @@ -1,35 +0,0 @@ -Status: done - -Task: improve messaging and outbox runtime behavior - -Goal: -bring publisher, consumer, and outbox worker runtime behavior up to the same observability and ownership standard as the rest of the foundation - -Scope Layers: - -runtime -tests -docs -ai - -Allowed Paths: - -messaging/ -app/ -observability/ -examples/ -docs/ -.ai/ -README.md - -Constraints: - -keep service-owned startup explicit -avoid hidden worker startup behavior -prefer additive metrics and helpers over broad API churn - -Expected Output: - -- messaging and outbox runtime emit aligned `ServiceLog` -- additive messaging and outbox metrics are available -- docs/examples reflect explicit ownership and current runtime behavior diff --git a/.ai/tasks/improve_migration_adoption.md b/.ai/tasks/improve_migration_adoption.md deleted file mode 100644 index 913ca8b..0000000 --- a/.ai/tasks/improve_migration_adoption.md +++ /dev/null @@ -1,40 +0,0 @@ -Status: done - -Task: improve migration adoption workflow - -Goal: -make migration behavior easier to adopt and safer to operate as more services consume `go-core` - -Scope Layers: - -runtime -tests -docs -ai - -Allowed Paths: - -migration/ -docs/ -MIGRATION.md -.ai/ -README.md - -Constraints: - -keep migration execution explicit -preserve lock safety semantics -improve runtime signals and upgrade discipline without hidden automation - -Expected Output: - -- clearer migration adoption guidance -- stronger tests/docs around lock and autorun behavior -- upgrade notes that match real public behavior - -Implemented Notes: - -- migration autorun now has additive logger-aware variants without changing explicit ownership -- logger-aware autorun emits `migration_autorun` and `migration_lock` service logs -- tests cover skipped and successful logger-backed autorun behavior -- migration guidance now documents the logger-aware adoption path and unchanged lock semantics diff --git a/.ai/tasks/improve_resilience_contracts.md b/.ai/tasks/improve_resilience_contracts.md deleted file mode 100644 index 7c7c78e..0000000 --- a/.ai/tasks/improve_resilience_contracts.md +++ /dev/null @@ -1,39 +0,0 @@ -Status: done - -Task: improve resilience contracts - -Goal: -connect retry and timeout helpers more clearly to the foundation’s logging and operational guidance - -Scope Layers: - -runtime -tests -docs -ai - -Allowed Paths: - -resilience/ -docs/ -.ai/ -README.md - -Constraints: - -keep APIs small -prefer additive guidance and light hooks over large redesign -align resilience usage with service and DB flow observability - -Expected Output: - -- clearer resilience usage story -- better tests and docs for retry/timeout behavior -- `.ai` context aligned with the intended usage - -Implemented Notes: - -- retry now supports additive retry hooks and timeout now has an observed variant -- resilience package provides logger-backed service-log hooks for retry and timeout events -- tests cover retry hook scheduling, timeout hook behavior, and service-log helper output -- reliability guidance now points services to the logger-aware resilience path when diagnostics are needed diff --git a/.ai/tasks/improve_runtime_orchestration.md b/.ai/tasks/improve_runtime_orchestration.md deleted file mode 100644 index 72e5e02..0000000 --- a/.ai/tasks/improve_runtime_orchestration.md +++ /dev/null @@ -1,33 +0,0 @@ -Status: done - -Task: improve runtime orchestration - -Goal: -raise `app/` and `server/` to the same foundation quality as the newer logging and bootstrap guidance - -Scope Layers: - -runtime -tests -docs -ai - -Allowed Paths: - -app/ -server/ -docs/ -.ai/ -README.md - -Constraints: - -preserve explicit lifecycle ownership -avoid hidden background behavior -align startup, shutdown, readiness, and logging contracts - -Expected Output: - -- clearer orchestration ownership -- better lifecycle and readiness signals -- docs/tests aligned with the runtime behavior diff --git a/.ai/tasks/improve_security_observability.md b/.ai/tasks/improve_security_observability.md deleted file mode 100644 index 70563fe..0000000 --- a/.ai/tasks/improve_security_observability.md +++ /dev/null @@ -1,40 +0,0 @@ -Status: done - -Task: improve security observability - -Goal: -make auth extraction and JWT verification easier to operate, diagnose, and document - -Scope Layers: - -runtime -tests -docs -ai - -Allowed Paths: - -security/ -server/grpc/ -docs/ -.ai/ -README.md - -Constraints: - -do not weaken auth behavior -keep external auth failures sanitized -improve internal diagnosability with explicit logging and guidance - -Expected Output: - -- stronger auth observability and docs -- tests covering operationally relevant auth behavior -- clearer separation between generic extractor mode and JWT verifier mode - -Implemented Notes: - -- gRPC startup now emits `auth_config` with mode and policy metadata -- auth interceptor logs stable `auth_request` failure reasons internally while keeping client-facing auth failures sanitized -- metadata extraction mode and JWT verification mode are documented more explicitly -- tests cover sanitized auth failures, metadata-mode injection, and verifier config metadata diff --git a/.ai/tasks/logging_flavor_expansion.md b/.ai/tasks/logging_flavor_expansion.md deleted file mode 100644 index 008c058..0000000 --- a/.ai/tasks/logging_flavor_expansion.md +++ /dev/null @@ -1,37 +0,0 @@ -Status: done - -Task: expand logging flavors - -Goal: -introduce `ServiceLog` and `DBLog` as foundation logging contracts while keeping `TransactionLog` as the platform-standard transaction contract - -Scope Layers: - -runtime -docs -tests -ai - -Allowed Paths: - -logger/ -database/ -docs/ -examples/ -.ai/ -README.md - -Constraints: - -keep `Info/Error/Debug/Warn` -keep `EventLog` -keep `TransactionLog` scoped to transaction-oriented services -keep top-level fields small and stable -use `Metadata` as the extension area -do not create a competing source of truth outside `.ai/` - -Expected Output: - -- logger API with `LogService(...)` and `LogDB(...)` -- initial framework adoption in DB initialization -- docs and `.ai` aligned on the 3 logging flavors diff --git a/.ai/tasks/raise_observability_contracts.md b/.ai/tasks/raise_observability_contracts.md deleted file mode 100644 index 18f7fc3..0000000 --- a/.ai/tasks/raise_observability_contracts.md +++ /dev/null @@ -1,32 +0,0 @@ -Status: done - -Task: raise observability contracts - -Goal: -balance service and DB observability with additive metric contracts that match the new logging flavors - -Scope Layers: - -observability -docs -tests -ai - -Allowed Paths: - -observability/ -docs/ -.ai/ -README.md - -Constraints: - -metric names must be additive and treated as stable once introduced -keep transaction observability intact -align docs with metric names and intended usage - -Expected Output: - -- additive service and DB metrics -- docs explaining when logs, metrics, and tracing work together -- `.ai` context updated for future `forge` usage diff --git a/.ai/tasks/refactor_config_database_defaults.md b/.ai/tasks/refactor_config_database_defaults.md deleted file mode 100644 index 22b8c14..0000000 --- a/.ai/tasks/refactor_config_database_defaults.md +++ /dev/null @@ -1,37 +0,0 @@ -Status: superseded - -Task: improve config and database defaults - -Goal: - -Remove service-biased defaults and make config/database behavior more generic for all consuming services. - -Scope Layers: - -config -runtime -docs -tests - -Allowed Paths: - -config/ -app/ -database/ -migration/ -README.md -docs/ - -Constraints: - -prefer safe evolution over accidental churn -do not hardcode service-specific DB aliases -do not introduce product-specific logic -do not move generic utility helpers into `go-core` -add tests for any config behavior changes - -Expected Output: - -- generic migration defaults or explicit migration requirements -- database config behavior aligned with service-supplied aliases -- updated README and tests diff --git a/.ai/tasks/refresh_examples_suite.md b/.ai/tasks/refresh_examples_suite.md deleted file mode 100644 index a0c3d7b..0000000 --- a/.ai/tasks/refresh_examples_suite.md +++ /dev/null @@ -1,30 +0,0 @@ -Status: done - -Task: refresh examples suite - -Goal: -bring older examples up to the current foundation golden path so they stop teaching stale patterns - -Scope Layers: - -examples -docs -ai - -Allowed Paths: - -examples/ -docs/ -.ai/ -README.md - -Constraints: - -keep examples generic and compilable or syntactically valid -align examples with current bootstrap, logging, config, and messaging patterns -do not turn examples into business-specific samples - -Expected Output: - -- examples that reflect the latest foundation direction -- docs pointing to the right example for each concern diff --git a/.ai/tasks/strengthen_bootstrap_templates.md b/.ai/tasks/strengthen_bootstrap_templates.md deleted file mode 100644 index 25b0937..0000000 --- a/.ai/tasks/strengthen_bootstrap_templates.md +++ /dev/null @@ -1,33 +0,0 @@ -Status: done - -Task: strengthen bootstrap examples and templates - -Goal: -make the starter path for a new service feel complete, explicit, and aligned with the golden path - -Scope Layers: - -docs -examples -templates -ai - -Allowed Paths: - -docs/ -examples/ -templates/ -.ai/ -README.md - -Constraints: - -show the canonical bootstrap path clearly -keep examples compilable or syntactically valid -do not encode business rules into templates - -Expected Output: - -- one canonical starter example -- templates that explain handler/service/repository responsibilities -- `.ai` context updated to treat bootstrap guidance as first-class foundation material diff --git a/.ai/tasks/template.md b/.ai/tasks/template.md deleted file mode 100644 index 3f967ca..0000000 --- a/.ai/tasks/template.md +++ /dev/null @@ -1,50 +0,0 @@ -Status: pending - -Task Context - -Task: -describe the task - -Goal: -what should be achieved - -Scope Layers: - -config -runtime -docs -tests - -Allowed Paths: - -config/ -app/ -database/ -migration/ -server/ -dbtx/ -errors/ -logger/ -observability/ -security/ -resilience/ -docs/ -README.md - -Do NOT modify: - -downstream services -product-specific schemas -generic utility code better suited for `utils-shared` - -Constraints: - -keep go-core domain-agnostic -keep it foundation-oriented, not utility-oriented -allow approved platform-standard technical contracts when clearly scoped -prefer safe evolution; bounded refactor is allowed when it improves the framework -update tests and docs with code changes - -Expected Output: - -describe expected framework result diff --git a/.ai/transactions.md b/.ai/transactions.md deleted file mode 100644 index d3b830b..0000000 --- a/.ai/transactions.md +++ /dev/null @@ -1,225 +0,0 @@ -# go-core · Transactions - -## Transaction Flow - -`go-core` distinguishes two transaction concepts: - -| Concept | Package | Purpose | -|---|---|---| -| **SQL Transaction** | `dbtx` | Database commit/rollback orchestration | -| **Business Transaction** | `logger.TransactionLog` | Observability for business-level operations | - -These are **separate concerns** and must not be conflated. - ---- - -## SQL Transaction Flow (`dbtx`) - -### Pattern - -```go -err := dbtx.WithTx(ctx, db, func(txCtx context.Context) error { - // 1. Repository uses txCtx to get the active transaction - if err := repo.SaveDomainData(txCtx, data); err != nil { - return err // triggers automatic rollback - } - // 2. Write outbox record IN THE SAME TRANSACTION - if err := outboxPublisher.PublishTx(txCtx, event); err != nil { - return err // triggers automatic rollback - } - return nil // triggers commit -}) -``` - -### Transaction Lifecycle - -``` -dbtx.WithTx(ctx, db, fn) - │ - ├─ db.BeginTx(ctx, opts) → START TRANSACTION - ├─ Inject tx into context → dbtx.Inject(ctx, tx) - ├─ fn(txCtx) → execute business logic - │ ├─ dbtx.FromContext(txCtx) → repository retrieves tx - │ └─ returns error? - │ ├─ YES → tx.Rollback() → ROLLBACK + return error - │ └─ NO → tx.Commit() → COMMIT - │ - └─ defer: panic recovery → Rollback + re-panic -``` - -### Repository Contract - -```go -// Repository correctly uses context to get the active transaction -func (r *repo) Save(ctx context.Context, data Domain) error { - db := dbtx.FromContext(ctx) // returns *sql.Tx if in transaction, else *sql.DB - _, err := db.ExecContext(ctx, "INSERT INTO ...", data.Field) - return err -} -``` - -### Rules - -- **Start tx at the use-case boundary** — not inside repositories -- **Keep transactions short** — no outbound HTTP/gRPC calls inside a DB transaction -- **Repositories must not begin their own transactions** — use `dbtx.FromContext` -- **Outbox record must be in the same transaction** as domain data write -- **Rollback is automatic** on any error returned from `fn` -- **Panic recovery** is built-in — rollback happens on panic then re-panic - ---- - -## Idempotency Strategy - -`go-core` provides the **infrastructure but not the enforcement** of idempotency. - -### Framework-Provided - -- `dbtx.WithTx` enables atomic writes that underpin idempotency tables -- `cache` package (Redis/Memcached) can be used for idempotency key storage -- Outbox pattern ensures at-least-once delivery semantics with deduplication responsibility in the consumer - -### Service-Owned Responsibility - -Each consuming service must implement: - -``` -Idempotency Record Schema (recommended): -┌─────────────────────────────────────────────┐ -│ idempotency_key VARCHAR PK │ -│ request_hash VARCHAR (payload hash) │ -│ status VARCHAR (pending/done/fail)│ -│ result_snapshot JSON (serialized response) │ -│ expires_at TIMESTAMP │ -└─────────────────────────────────────────────┘ -``` - -**Rule:** Same key + different payload → reject with `INVALID_REQUEST`. -**Rule:** Same key + same payload + status=done → return cached result. - -### Missing: No Framework-Level Idempotency Key Middleware - -`go-core` does NOT currently provide: -- gRPC/HTTP middleware to extract and validate idempotency keys -- Idempotency key storage helpers -- Cache-backed idempotency enforcement - -This is a known gap — consuming services implement this independently. - ---- - -## Failure Handling - -### Transaction Failure Matrix - -| Failure Point | Behavior | Recovery | -|---|---|---| -| `BeginTx` fails | Return error immediately — no retry in `dbtx` | Caller handles | -| `fn` returns error | Automatic `Rollback()` | Caller decides retry | -| `Commit` fails | Returns `"dbtx: commit failed"` error | Caller must handle — partial state possible | -| `Rollback` fails after fn error | Both errors joined via `errors.Join` | Log and alert | -| Panic inside `fn` | Rollback triggered, panic re-raised | Service-level panic handler | -| `db` is nil | Returns `"dbtx: db is nil"` immediately | Configuration error | - -### Distributed Failure: Outbox Pattern - -When a DB commit succeeds but Kafka publish fails: - -``` -WITHOUT outbox (direct publish): - ✓ DB committed - ✗ Kafka publish failed → event lost permanently - -WITH outbox: - ✓ DB committed (domain + outbox record in same tx) - → Outbox worker picks up pending record - → Retries Kafka publish with exponential backoff - ✓ Event eventually delivered (at-least-once) -``` - -### Retry Strategy - -`resilience.Do(ctx, opts, fn)` provides: -- **Exponential backoff** with configurable `BaseDelay`, `MaxDelay` -- **Crypto-random jitter** (`crypto/rand`) — avoids thundering herd -- **Retryable predicate** — services define what errors warrant retry -- **Context-aware** — respects `ctx.Done()` between attempts -- **OnRetry hook** — for logging/metrics on each retry event - -Default settings: -``` -MaxAttempts: 1 -BaseDelay: 200ms -MaxDelay: 2s -Jitter: 100ms -Retryable: context.DeadlineExceeded only -``` - -> **Note:** `DefaultRetryOptions().MaxAttempts = 1` means **no retry by default**. -> Services must explicitly set `MaxAttempts > 1` to enable retries. - -### Circuit Breaker - -`resilience.NewCircuitBreaker(opts)` wraps Sony Gobreaker: -- **Trips** after `ConsecutiveFailures > 5` (default) -- **Half-open timeout:** 60s -- **Context cancellation** is not counted as a failure -- Returns `ErrCircuitOpen` when tripped — maps to `SERVICE_UNAVAILABLE` in the error contract - ---- - -## Business Transaction Observability - -Use `logger.LogTransaction(ctx, tx)` for monitoring business-level flows. - -```go -logger.LogTransaction(ctx, logger.TransactionLog{ - Operation: "payment_process", // stable operation name - TransactionID: "TXN-20260213-0001", // business correlation ID - UserID: "user_12345", // actor; empty for system flows - Status: "failed", // "success" | "failed" | "pending" - DurationMs: 120, - ErrorCode: "PAYMENT_TIMEOUT", // stable code for alerting - Metadata: map[string]interface{}{ - "provider": "bca", - "channel": "mobile_app", - "amount": 150000, - }, -}) -``` - -**Prometheus metric emitted:** `app_transaction_total{service, operation, status}` - -### TransactionLog Rules - -- `Operation` must be a **stable, snake_case name** — used in dashboards and alerts -- `TransactionID` is the **business identifier**, not the request ID -- `Status` must be one of: `success`, `failed`, `pending` -- `ErrorCode` must be **stable** — changing it breaks alert rules -- **Never put sensitive data** (tokens, amounts without business need, PII) in `Metadata` -- `dbtx` (SQL transaction) and `TransactionLog` (observability) are **separate concerns** - ---- - -## Messaging Outbox: Transactional Delivery - -``` -Use Case: SQL write + Event publication must succeed together - -Recommended flow: - dbtx.WithTx(ctx, db, func(txCtx) error { - repo.SaveOrder(txCtx, order) ← domain write - outbox.PublishTx(txCtx, "order.created", payload) ← outbox write - return nil ← single commit - }) - - OutboxWorker (explicit, service-controlled): - loop: - SELECT pending rows - Kafka.Publish(row) - UPDATE row status = "published" -``` - -- Worker is **never started automatically** by `go-core` -- Service controls: interval, batch size, publisher, which pod runs the worker -- Worker emits: `outbox_worker` ServiceLog + `app_outbox_batch_total` Prometheus metric diff --git a/.ai/workflow.md b/.ai/workflow.md deleted file mode 100644 index 7671309..0000000 --- a/.ai/workflow.md +++ /dev/null @@ -1,112 +0,0 @@ -# go-core · AI Workflow - -## Entry Point - -Before any task: -1. Read `.ai/context.md` — project overview and critical rules -2. Read `.ai/architecture.md` — system design and layer model -3. Identify which module(s) are affected → read `.ai/modules.md` -4. For auth/security changes → read `.ai/security.md` -5. For transaction/idempotency changes → read `.ai/transactions.md` -6. For integration changes → read `.ai/integrations.md` - ---- - -## Development Workflow - -### Step 1: Scope the Change - -Confirm the change belongs in `go-core` and not in a consuming service. - -Ask: -- Is this generic infrastructure? Or service-specific logic? -- Does this introduce product-specific naming or assumptions? -- Can the consuming service own this instead? - -If service-specific → reject and keep it in the consuming service. - -### Step 2: Identify Contract Risk - -Determine whether the change affects: - -| Affected Area | Risk Level | Action Required | -|---|---|---| -| Public API surface | 🔴 HIGH | Semver review, MIGRATION.md, README update | -| Config env vars | 🔴 HIGH | Validate backward compatibility, docs update | -| Runtime behavior | 🔴 HIGH | Test coverage, migration notes | -| Metric names/labels | 🔴 HIGH | Breaking change — coordinate with dashboards | -| gRPC interceptors | 🟡 MEDIUM | Review all consumers | -| Log field names | 🟡 MEDIUM | May break log parsers | -| Internal refactor | 🟢 LOW | Tests + no API change | - -### Step 3: Implement - -- Make the **smallest change** that satisfies the task -- No behavior outside the task's `allowed_paths` -- Follow conventions in `.ai/conventions.md` -- Keep `go-core` domain-agnostic - -### Step 4: Verify - -```bash -go test ./... # all tests must pass -make quality-gate # lint + vet + format check -``` - -### Step 5: Align Documentation - -For public-contract changes: -- `README.md` — user-facing behavior description -- `docs/` — relevant framework guidance doc -- `MIGRATION.md` — upgrade instructions for consuming services -- `.ai/` — update context if module behavior or API changes - ---- - -## Acceptance Standard - -A task is **complete** only when ALL of the following are true: - -- [ ] Implementation is bounded to allowed scope -- [ ] `go test ./...` passes -- [ ] `make quality-gate` passes -- [ ] Public API docs (`README.md`) are aligned -- [ ] `MIGRATION.md` updated if upgrade behavior changes -- [ ] `.ai/` context updated if module behavior changes - ---- - -## Release Discipline - -Before releasing a version: - -1. Run CI baseline + `make quality-gate` -2. Collect release evidence from `docs/RELEASE_EVIDENCE_TEMPLATE.md` -3. Confirm `version.Version`, `version.Commit`, `version.BuildDate` are set via `ldflags` -4. Update `CHANGELOG.md` -5. Tag with `vX.Y.Z` per semver rules in `.ai/conventions.md` - ---- - -## AI Execution Principles - -- Prefer additive changes -- Allow bounded refactors that improve framework shape -- Avoid hidden side effects -- Preserve documented exported behavior as semver contract -- Keep defaults generic — no service-specific names -- Keep service-specific logic out of framework code -- Allow explicit platform-standard observability contracts (e.g., `TransactionLog`) when intentionally standardized -- Always pass the 5-gate review: compatibility · coupling · concurrency · scale · overengineering - ---- - -## Prompt Roles - -| Prompt | Role | -|---|---| -| `.ai/prompts/breakdown.md` | Task planner | -| `.ai/prompts/execute.md` | Framework engineer | -| `.ai/prompts/fix.md` | Debugger | -| `.ai/prompts/test.md` | Tester | -| `.ai/prompts/review.md` | Reviewer | diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1d91684 --- /dev/null +++ b/.env.example @@ -0,0 +1,71 @@ +# Safe local example configuration for go-core consumers. +# Copy to .env for local experiments and replace placeholders with local-only values. + +SERVICE_NAME=example-service +APP_ENV=local +LOG_LEVEL=info +LOG_TIMEZONE=UTC +SHUTDOWN_TIMEOUT=10s +GRPC_PORT=50051 +HTTP_PORT=8080 + +GRPC_TLS_ENABLED=false +GRPC_TLS_CERT_FILE= +GRPC_TLS_KEY_FILE= +HTTP_TLS_ENABLED=false +HTTP_TLS_CERT_FILE= +HTTP_TLS_KEY_FILE= + +# Leave DB_LIST empty when the consuming service does not need a database. +DB_LIST= + +# Example database configuration. Use local development credentials only. +# DB_LIST=primary +# DB_PRIMARY_DRIVER=postgres +# DB_PRIMARY_HOST=127.0.0.1 +# DB_PRIMARY_PORT=5432 +# DB_PRIMARY_NAME=example_db +# DB_PRIMARY_USER=example_user +# DB_PRIMARY_PASSWORD=change-me +# DB_PRIMARY_REQUIRED=true + +MIGRATION_AUTO_RUN=false +MIGRATION_DB= +MIGRATION_DIR= +MIGRATION_LOCK_ENABLED=true + +REDIS_ENABLED=false +REDIS_ADDRESS=127.0.0.1:6379 +REDIS_PASSWORD= +REDIS_DB=0 + +MEMCACHED_ENABLED=false +MEMCACHED_SERVERS=127.0.0.1:11211 +MEMCACHED_TIMEOUT=2s + +KAFKA_ENABLED=false +KAFKA_BROKERS=127.0.0.1:9092 +KAFKA_CLIENT_ID=example-service +KAFKA_USERNAME= +KAFKA_PASSWORD= + +OTEL_EXPORTER_OTLP_ENDPOINT= +OTEL_EXPORTER_OTLP_INSECURE=false +OTEL_EXPORTER_OTLP_CA_CERT_FILE= +TRACE_SAMPLING_RATIO=0.1 + +INTERNAL_JWT_ENABLED=false +INTERNAL_JWT_PUBLIC_KEY= +INTERNAL_JWT_JWKS_ENDPOINT= +INTERNAL_JWT_JWKS_REFRESH_INTERVAL=1h +INTERNAL_JWT_ISSUER= +INTERNAL_JWT_AUDIENCE= +INTERNAL_JWT_LEEWAY=30s +INTERNAL_JWT_INCLUDE_METHODS= +INTERNAL_JWT_EXCLUDE_METHODS= + +AUTH_SIGNATURE_ENABLED=false +AUTH_SIGNATURE_MASTER_KEY=change-me-local-only +AUTH_SIGNATURE_HEADER_KEY=x-signature +AUTH_SIGNATURE_TIMESTAMP_KEY=x-timestamp +AUTH_SIGNATURE_MAX_TIME_DRIFT=5m diff --git a/.forge/context/00-meta/conventions.md b/.forge/context/00-meta/conventions.md index 24571c6..61bb41c 100644 --- a/.forge/context/00-meta/conventions.md +++ b/.forge/context/00-meta/conventions.md @@ -82,6 +82,7 @@ Mode files are machine-resolvable context loading deltas and the authority for m ## Mode Invocation - Modes are loading deltas on top of always-loaded core. +- Read `.forge/forge.config.yaml` before mode execution and apply `runtime.non_interactive`. - Mode files are authoritative for mode-specific execution behavior. - Visible modes are limited to `planning`, `implementation` (invoked as implement), `execute`, `testing`, and `review`. - `planning` owns strategic ECP reasoning; `implementation` owns human-reviewable task decomposition; `execute` owns repository modification; `testing` owns testing strategy/test changes; `review` owns correctness and risk review. @@ -92,6 +93,19 @@ Mode files are machine-resolvable context loading deltas and the authority for m - Report loaded context, missing evidence, unresolved ambiguity, and mode sufficiency according to the selected mode. - Runtime-managed cognition lives under `.forge/context`; repository-owned cognition remains in application code, repository docs, ADRs, and human confirmations. +## Runtime Interaction Behavior + +Forge uses one runtime flag: `runtime.non_interactive`. + +| Value | Behavior | +|---|---| +| `false` | Default interactive behavior. Ask concise clarification questions for blocking decisions, governance uncertainty, missing contract authority, ambiguous runtime behavior, or dangerous/destructive execution; continue after human confirmation. | +| `true` | Automation-safe behavior. Do not ask conversational questions; emit `BLOCKED`, `NEEDS_REVIEW`, or `NEEDS_CONFIRMATION`; continue only with allowed proposed defaults. | + +Interactive prompts should offer the recommended option plus one alternative by default; use a third option only for major architecture tradeoffs. Avoid repetitive clarification loops and broad questionnaires. + +Changing `runtime.non_interactive` is runtime-managed operational behavior only. It must not re-init context, rewrite knowledge, invalidate assumptions, modify inferred context, or rewrite systems, layers, or core cognition files. + ## Unknown Decision Semantics Unknowns are classified as: diff --git a/.forge/context/modes/execute.md b/.forge/context/modes/execute.md index 310b6af..5dfe7c0 100644 --- a/.forge/context/modes/execute.md +++ b/.forge/context/modes/execute.md @@ -28,6 +28,7 @@ updated: 2026-05-24 - Implement only approved tasks or approved task subsets using scoped execution context and repository consistency rules. - Preserve repository conventions, minimize unnecessary changes, and keep proposed vs confirmed boundaries visible. - Do not perform major architecture redesign, invent topology/contracts, broad-load unrelated context, or silently redefine approved plans. +- If `runtime.non_interactive: false`, ask confirmation before dangerous, destructive, or runtime-impacting changes; if `true`, stop safely and emit a blocked report. - Run narrow implementation verification when relevant; use testing mode for test strategy, test creation, coverage, mocks/fakes/stubs, and broader regression validation. - Never copy raw secrets from configs, env files, logs, fixtures, docs, or generated output into code or Forge context. - Report modified files, task completion status, loaded context, missing evidence or ambiguity, and whether execute mode was sufficient. diff --git a/.forge/context/modes/implementation.md b/.forge/context/modes/implementation.md index 82a1e9d..a745134 100644 --- a/.forge/context/modes/implementation.md +++ b/.forge/context/modes/implementation.md @@ -29,6 +29,7 @@ updated: 2026-05-24 - Do not modify code, redesign architecture, repeat full ECP reasoning, or silently redefine approved plans. - Load only task-relevant layers, systems, decisions, and inferences; use on-demand context only when task decomposition requires it. - Keep task scope bounded; do not introduce speculative redesign, ownership, topology, contracts, or behavior not supported by evidence. +- If `runtime.non_interactive: false`, ask execution-blocking decisions before final task breakdown; if `true`, emit a blocked implementation report. - Continue on labeled proposed defaults only when low-risk, reversible, and non-authoritative; do not promote them into confirmed architecture/runtime behavior. - Never copy raw secrets from configs, env files, logs, fixtures, docs, or generated output into code or Forge context. - Report task list, likely files/components, dependencies, loaded context, missing evidence or ambiguity, proposed vs confirmed boundaries, and whether implementation mode was sufficient. diff --git a/.forge/context/modes/planning.md b/.forge/context/modes/planning.md index 1eaf05a..15a17fc 100644 --- a/.forge/context/modes/planning.md +++ b/.forge/context/modes/planning.md @@ -29,6 +29,7 @@ updated: 2026-05-24 - Do not produce detailed executable coding tasks or modify code; hand off approved phases to implementation mode for task decomposition. - Adapt sections to evidence: backend transactions/data/contracts; frontend UX/routes/components/state/accessibility/performance/analytics; infrastructure deployment/environment/reliability/security. - Prefer safe proposed defaults for low-risk operational choices; escalate only blocking decisions and keep prompts to recommended plus alternative. +- If `runtime.non_interactive: false`, ask unresolved architecture/governance decisions early; if `true`, emit a planning blocked report instead of asking. - Redact secret values in ECPs and report secret discoveries only as security findings with type/path/line/masked preview. - Include impact/risk analysis, validation approach, rollback path, loaded context, missing evidence, unresolved ambiguity, and whether planning mode was sufficient. - Separate evidence, inference, and unknowns; do not invent topology, ownership, contracts, deployability, or runtime relationships from imports alone; load extra context only for the scoped change. diff --git a/.forge/context/modes/review.md b/.forge/context/modes/review.md index 2dcb39a..859454b 100644 --- a/.forge/context/modes/review.md +++ b/.forge/context/modes/review.md @@ -29,6 +29,7 @@ updated: 2026-05-24 - Do not replace testing mode; reference test evidence when assessing regression risk and coverage gaps. - Check topology, runtime behavior, data flow, contracts, and layer/system boundaries only when relevant evidence is loaded. - Lead with evidence-based critique; keep unevidenced concerns as uncertainty, not confirmed defects. +- If `runtime.non_interactive: false`, ask review-scope clarification only when necessary; if `true`, emit a review ambiguity report. - Identify unconfirmed proposed defaults and flag any accidental promotion of proposed assumptions into confirmed behavior. - Treat raw secret exposure in diffs, reports, generated context, or comments as a security finding requiring redaction. - Report reviewed areas, loaded context, missing evidence or ambiguity, risk severity, and whether review mode was sufficient. diff --git a/.forge/context/modes/testing.md b/.forge/context/modes/testing.md index 42f2f11..b92fcf6 100644 --- a/.forge/context/modes/testing.md +++ b/.forge/context/modes/testing.md @@ -30,6 +30,7 @@ updated: 2026-05-24 - If no convention exists, colocate unit tests near target packages/files and place non-unit tests under `testing/integration`, `testing/e2e`, `testing/mocks`, `testing/fixtures`, or `testing/helpers` as appropriate. - Keep unit, integration, e2e, mocks, fakes, stubs, fixtures, and helpers distinct; avoid mixing unrelated test concerns in one folder without reason. - Reason about test isolation, mocks/fakes/stubs, fixtures, helpers, test dependencies, retry/error paths, rollback paths, and missing coverage. +- If `runtime.non_interactive: false`, ask unresolved validation expectations only when needed; if `true`, emit an unresolved validation report. - Do not become generic architecture planning, review mode, or broad implementation redesign. - Redact credentials, tokens, cookies, private keys, and credential-bearing URLs from test evidence and validation notes. - Report test strategy or test changes, loaded context, missing evidence or ambiguity, commands run or skipped, and whether testing mode was sufficient. diff --git a/.forge/forge.config.yaml b/.forge/forge.config.yaml index e51382e..926a360 100644 --- a/.forge/forge.config.yaml +++ b/.forge/forge.config.yaml @@ -13,6 +13,9 @@ loading: default_mode: implementation respect_size_budget: true +runtime: + non_interactive: true + size_budget: core_lines: 200 layer_lines: 150 diff --git a/.gitignore b/.gitignore index 4917a11..638c1d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ # Binaries for programs and plugins +bin/ +dist/ +build/ *.exe *.exe~ *.dll @@ -10,6 +13,9 @@ # Output of the go coverage tool *.out +coverage/ +coverage.* +*.coverprofile # Dependency directories (remove the comment below to include it) # vendor/ @@ -18,8 +24,18 @@ go.work go.work.sum -# env file +# Environment files .env +.env.* +!.env.example + +# Local assistant/context artifacts +.ai/ +.claude/ +AI_RULES.md +AGENTS.md +CONTEXT.md +PROJECT_CONTEXT.md # Forge context scratch/generated outputs .forge/context/temp/ @@ -30,11 +46,14 @@ go.work.sum # OS artifacts .DS_Store Thumbs.db +desktop.ini # Editor swap/backup *.swp *.swo *~ +*.bak +*.orig # IDEs .idea/ @@ -45,6 +64,15 @@ Thumbs.db *.log *.tmp .cache/ +tmp/ +temp/ + +# Local secret/certificate material +*.pem +*.key +*.crt +*.p12 +*.jks # Accidentally compiled binaries without extensions (if they match package names) httpclient_example diff --git a/AI_RULES.md b/AI_RULES.md deleted file mode 100644 index 6d31700..0000000 --- a/AI_RULES.md +++ /dev/null @@ -1,47 +0,0 @@ -# AI Rules - -> Lens utama untuk semua pekerjaan AI di repo ini. -> **Baca `.ai/context.md` dulu sebelum apapun.** - -## Hard Constraints - -- Keep `go-core` domain-agnostic — no business entities, no service-specific defaults. -- Allow explicit platform-standard technical contracts when intentionally standardized across services. -- Prefer additive changes; avoid breaking public API. -- Keep public API surface small. -- No hidden lifecycle or background behavior — all lifecycle hooks must be explicit. - -## Implementation Rules - -- Runtime functions: `ctx context.Context` always first. -- Reuse `errors.AppError` — never invent parallel error types. -- Sanitize external error responses — internal detail stays in logs only. -- Use `LogService`, `LogDB`, or `LogTransaction` — not raw string logs for structured flows. -- `dbtx.WithTx` owns commit/rollback — repositories use `dbtx.FromContext`. - -## Review Checklist (always mention) - -- **Compatibility** — does this break existing consuming services? -- **Coupling** — does this introduce product-specific knowledge? -- **Concurrency** — new goroutines or shared state? -- **Scale risk** — metric cardinality explosion or connection growth? -- **Overengineering** — simpler than the problem requires? - -## Context Navigation - -| Question | Read | -|---|---| -| What is this repo? | `.ai/context.md` | -| System design & layers | `.ai/architecture.md` | -| Module APIs & symbols | `.ai/modules.md` | -| Auth, JWT, secrets | `.ai/security.md` | -| Transactions, retry, outbox | `.ai/transactions.md` | -| Request lifecycle & metrics | `.ai/data-flow.md` | -| External services & env vars | `.ai/integrations.md` | -| Code style & naming | `.ai/conventions.md` | -| Why decisions were made | `.ai/decisions.md` | -| Dev workflow & checklist | `.ai/workflow.md` | - -## Output Style - -Short, direct, minimal tokens. No filler sentences. diff --git a/CLAUDE.md b/CLAUDE.md index aa941c5..32b92dd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ Thin adapter for AI assistants. This file stores **no context** — it points to ## Bootstrap Sequence -1. Read `.forge/forge.config.yaml` — tier, active layers, systems, default mode. +1. Read `.forge/forge.config.yaml` — tier, active layers, systems, default mode, `runtime.non_interactive`. 2. Read `.forge/context/00-meta/context-manifest.md` — index & loading rules. 3. Obey `.forge/context/00-meta/conventions.md` — AI operational contract (normative). 4. Always load: `00-meta/*` + `01-core/*`. @@ -14,7 +14,9 @@ Thin adapter for AI assistants. This file stores **no context** — it points to ## AI Operational Rules (Summary) - Never guess. `unknown` is a mandatory destination, not a guess. -- Classify unknowns as `blocking`, `proposed-default`, or `informational`; automation must emit `BLOCKED`, `NEEDS_REVIEW`, or `NEEDS_CONFIRMATION` instead of asking interactive questions. +- `runtime.non_interactive: false` is the default: ask concise clarification questions for blocking decisions, then continue after human confirmation. +- `runtime.non_interactive: true`: never ask interactive questions; emit `BLOCKED`, `NEEDS_REVIEW`, or `NEEDS_CONFIRMATION` and continue only with allowed proposed defaults. +- Classify unknowns as `blocking`, `proposed-default`, or `informational`. - Never print, copy, summarize, or store raw secrets. Redact sensitive values before any output or Forge context write. - Never write to `source: human` files. Inferences go to `knowledge/inferred.md` or `generated/`. - Never self-promote `status`. Propose only; promotion to `confirmed` requires entry in `knowledge/confirmations.md`. @@ -41,11 +43,14 @@ Thin adapter for AI assistants. This file stores **no context** — it points to ## Mode Invocation Entry -- When a Forge mode is requested, read `.forge/context/modes/.md` first. +- When a Forge mode is requested, read `.forge/forge.config.yaml` first and detect `runtime.non_interactive`. +- Apply interactive or non-interactive behavior from config before mode execution. +- Then read `.forge/context/modes/.md`. - Visible modes: `planning`, `implement` (`implementation.md`), `execute`, `testing`, `review`. - Follow that mode's `include`, `on_demand`, `exclude`, `token_budget`, and `notes`. - Load scoped context only; do not broad-load `.forge/context` by default. - Keep planning, task decomposition, code execution, testing, and review separate. +- Apply `runtime.non_interactive` consistently across all modes; changing it never rewrites repository cognition. ## Notes diff --git a/CONTEXT.md b/CONTEXT.md deleted file mode 100644 index eab6d3d..0000000 --- a/CONTEXT.md +++ /dev/null @@ -1,4 +0,0 @@ -# go-core Context - -> This file has been superseded by `.ai/context.md`. -> **Primary source of truth:** `.ai/context.md` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ce0a4e1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,53 @@ +# Contributing + +Thanks for helping improve `go-core`. This repository is a production-minded Go foundation, so changes should favor clarity, operational readability, and bounded abstractions. + +## Development Expectations + +- Keep Go idiomatic and direct. +- Preserve existing package boundaries unless there is a clear maintenance or correctness reason to change them. +- Prefer explicit behavior over hidden magic. +- Keep runtime behavior observable through logs, metrics, traces, errors, or tests where appropriate. +- Update docs when public behavior, configuration, migration behavior, or operational expectations change. +- Avoid product-specific business logic in this repository. + +## Before Opening a PR + +Run the fast local checks: + +```bash +make test +make vet +make lint +``` + +For broader changes, run: + +```bash +make quality-gate +``` + +Add or update tests when changing behavior. Narrow documentation-only changes do not require new tests, but examples and commands should remain accurate. + +## Review Guidelines + +Good changes usually: + +- have a small, understandable scope +- name concepts clearly +- keep service/domain/repository/transport responsibilities separated +- explain operational impact in the PR description +- include migration notes when public upgrade behavior changes + +Please avoid: + +- framework creep +- speculative architecture rewrites +- unnecessary abstraction layers +- giant PRs without operational justification +- fake demo flows that do not reflect actual package behavior +- adding secrets, private endpoints, or customer/production data to tests, docs, fixtures, or examples + +## Compatibility + +Public contract changes should be intentional and documented. Update `MIGRATION.md`, `CHANGELOG.md`, and relevant docs when a change affects consumers. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2a9b1fb --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 go-core contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md deleted file mode 100644 index e91d6b9..0000000 --- a/PROJECT_CONTEXT.md +++ /dev/null @@ -1,13 +0,0 @@ -# go-core Project Context - -> This file has been absorbed into the structured `.ai/` context system. -> -> **Read instead:** -> - `.ai/context.md` — project overview & critical rules -> - `.ai/architecture.md` — system design & layers -> - `.ai/security.md` — auth flow & data protection -> - `.ai/transactions.md` — transaction integrity & idempotency -> - `.ai/modules.md` — full module API reference -> - `.ai/data-flow.md` — request lifecycle & observability -> - `.ai/integrations.md` — external services & dependencies -> - `.ai/decisions.md` — architecture decision records diff --git a/README.md b/README.md index 5ac2037..99efe1f 100644 --- a/README.md +++ b/README.md @@ -1,483 +1,148 @@ -## go-core +# go-core -`go-core` is a reusable infrastructure foundation for Go services. -It is domain-agnostic and focuses on bootstrap/runtime concerns: -config, lifecycle, transport, logging, metrics, tracing, database, and messaging. +`go-core` is a reusable Go backend foundation for production-minded services. It provides common runtime building blocks for configuration, lifecycle management, transport wrappers, logging, metrics, tracing, database access, messaging, migrations, resilience, and error handling. -Module path: - -`github.com/yogayulanda/go-core` - -### Release and upgrade discipline - -`go-core` is released as stable `v1.0.0` for Go services. -`v1.0.0` is the first compatibility baseline for semver-governed adoption. -Release discipline is intentionally simple: - -- CI baseline is the fast repository gate: `make test`, `make vet`, `make lint` -- local release gate is stronger: `make quality-gate` -- staging release validation uses `make smoke-gate`, load gates, and `make failure-drill` -- public contract changes must update README, relevant docs, tests, and `MIGRATION.md` when upgrade behavior changes -- release builds should set `version.Version`, `version.Commit`, and `version.BuildDate` via `-ldflags` +The project is intentionally infrastructure-focused. It is not a service template, product domain, or toy demo. Consuming services own their business logic, persistence models, API contracts, deployment topology, and operational policy. -See: - -- `docs/PRODUCTION_SIGNOFF.md` -- `docs/CHANGE_CHECKLIST.md` -- `docs/VERSIONING.md` -- `MIGRATION.md` +Module path: -### Foundation boundary - -`go-core` has two allowed contract classes: - -- Generic foundation contracts: - bootstrap/runtime wiring, transport wrappers, config, lifecycle, infra connectors, technical errors, `dbtx`. -- Platform-standard technical contracts: - intentionally standardized technical contracts shared by a class of services. - -Current platform-standard example: - -- `logger.TransactionLog` -- `logger.Logger.LogTransaction(...)` -- `observability` metric `app_transaction_total{service,operation,status}` - -These transaction observability contracts are for transaction-oriented services. -They are not a license to move business rules into `go-core`. - -### What it provides - -- App container + graceful shutdown lifecycle (`app`). -- Config loader + validation from environment variables (`config`). -- Structured logger (`logger`) with: - - JSON in non-dev environment. - - colored console in `APP_ENV=dev|local|development`. - - timezone-aware timestamp encoding (default UTC, configurable via `LOG_TIMEZONE`). - - sensitive-field masking (keep last 2 chars for sensitive values). - - `ServiceLog` for normal technical service flow. - - `DBLog` for DB operational and query-related logging. - - optional `TransactionLog` for transaction-oriented service monitoring. -- Multi-database initialization (`database`) with named DB map and GORM support. -- Optional Redis cache dependency initialization (`cache/redis`) with fail-fast startup ping and aligned `cache_connect` `ServiceLog`. -- Optional Memcached cache dependency initialization (`cache/memcached`) with fail-fast health check, miss-tolerant readiness probe, and aligned `cache_connect` `ServiceLog`. -- gRPC server wrapper + interceptors (`server/grpc`): - - recovery - - request-id - - auth extraction / JWT verification (configurable) - - transport-aligned `ServiceLog` emission for request flow and panic recovery - - request metrics + additive service operation metrics -- gRPC-Gateway wrapper (`server/gateway`) exposing: - - `GET /health` - - `GET /ready` - - `GET /version` - - `GET /metrics` - - `GET /debug/pprof/*` (optional via `HTTP_PPROF_ENABLED`) - - HTTP panic recovery - - HTTP signature validation (optional via `AUTH_SIGNATURE_ENABLED`) - - OpenTelemetry HTTP span wrapper (`otelhttp`) - - transport-aligned request-id propagation, HTTP metrics, service metrics, and `ServiceLog` -- Startup helper (`server`): - - `Run(...)` to orchestrate gRPC + gateway + lifecycle with centralized error handling. - - `DescribeFromProto(...)` to list HTTP/gRPC routes from proto descriptors. - - `LogStartupReadiness(...)` to emit readiness `ServiceLog` for gRPC, gateway, and combined service readiness. -- OTEL tracing bootstrap (`observability`). -- Prometheus metrics (`observability`): - - `app_http_request_total{service,method,route,status}` - - `app_http_request_duration_seconds{service,method,route}` - - `app_request_total{service,method,status}` - - `app_request_duration_seconds{service,method}` - - `app_service_operation_total{service,operation,status}` - - `app_service_operation_duration_seconds{service,operation}` - - `app_db_operation_total{service,db_name,operation,status}` - - `app_db_operation_duration_seconds{service,db_name,operation}` - - `app_message_publish_total{service,topic,status}` - - `app_message_consume_total{service,topic,group,status}` - - `app_message_process_duration_seconds{service,topic,group}` - - `app_outbox_batch_total{service,status}` - - `app_outbox_batch_duration_seconds{service}` - - `app_outbox_batch_size{service}` - - `app_transaction_total{service,operation,status}` -- Kafka publisher/consumer abstraction (`messaging`) with additive logger/metrics options and app-level defaults. -- Outbox helpers (`messaging/outbox`) with driver-aware SQL (`mysql|postgres|sqlserver`), `RunOnce(...)`, and explicit `StartChecked(...)`. -- Goose migration helper (`migration`) including auto-run support. -- DB transaction helper (`dbtx`) with context propagation (`WithTx`, `WithTxOptions`). -- Outbound resilience helper (`resilience`) for timeout + retry policy, circuit breaker (`sony/gobreaker`), plus additive retry/timeout hooks for logger-backed observability. -- Resilient HTTP client (`httpclient`) with built-in: - - circuit breaker - - retries with backoff - - OpenTelemetry tracing - - structured logging (`ServiceLog`) -- Common app error contract + mapper (`errors`) with stable code and optional validation details. - -### Security scope - -- By default (`INTERNAL_JWT_ENABLED=false`), go-core extracts generic auth metadata - from incoming gRPC metadata: - - `x-subject` - - `x-session-id` - - `x-role` - - `x-claim-` (mapped into `security.Claims.Attributes`) -- If `INTERNAL_JWT_ENABLED=true`, go-core enforces bearer JWT verification in gRPC interceptor: - - RSA signature validation (`RS256/RS384/RS512`) via static key OR dynamic background polled JWKS endpoints - - standard time claims validation (`exp`, `nbf`, `iat`) - - optional issuer check (`INTERNAL_JWT_ISSUER`) - - optional audience check (`INTERNAL_JWT_AUDIENCE`) -- JWT-to-claims mapping: - - `sub` -> `Claims.Subject` - - `session_id`/`sid` -> `Claims.SessionID` - - `role` -> `Claims.Role` - - `attributes` object -> `Claims.Attributes` -- `INTERNAL_JWT_PUBLIC_KEY` is required when JWT is enabled. -- Optional transport TLS is supported for gRPC and HTTP gateway (`GRPC_TLS_*`, `HTTP_TLS_*`). - -Operational notes: - -- gRPC startup emits `auth_config` so operators can confirm whether the service is running in metadata extraction mode or JWT verification mode. -- JWT auth failures are sanitized to clients as unauthorized responses. -- internal service logs keep stable auth failure reasons such as missing authorization header, invalid token, invalid issuer, and invalid audience. - -### Configuration - -All values are loaded from environment variables. - -Configuration profiles: - -- see `docs/CONFIGURATION_PROFILES.md` for grouped onboarding guidance -- use `cfg.Validate()` for the compact public error -- use `cfg.ValidateIssues()` for structured validation issues by section and field - -Core: - -- `SERVICE_NAME` (required) -- `APP_ENV` (default: `dev`) -- `LOG_LEVEL` (default: `info`) -- `LOG_TIMEZONE` (optional IANA TZ name, default: `UTC`; example: `Asia/Jakarta`) -- `SHUTDOWN_TIMEOUT` (default: `10s`) -- `GRPC_PORT` (default: `50051`) -- `HTTP_PORT` (default: `8080`) -- `GRPC_TLS_ENABLED` (default: `false`) -- `GRPC_TLS_CERT_FILE` (required when `GRPC_TLS_ENABLED=true`) -- `GRPC_TLS_KEY_FILE` (required when `GRPC_TLS_ENABLED=true`) -- `HTTP_TLS_ENABLED` (default: `false`) -- `HTTP_TLS_CERT_FILE` (required when `HTTP_TLS_ENABLED=true`) -- `HTTP_TLS_KEY_FILE` (required when `HTTP_TLS_ENABLED=true`) - -Databases: - -- `DB_LIST` (optional, comma-separated aliases, example: `primary,ledger_history`) -- Per DB name (`` is uppercase name from `DB_LIST`): - - `DB__DRIVER` (required; `mysql|postgres|sqlserver`) - - `DB__DSN` (optional override) - - or composed fields: - - `DB__HOST` - - `DB__PORT` - - `DB__NAME` - - `DB__USER` - - `DB__PASSWORD` - - `DB__PARAMS` (optional query params) - - pool settings (optional): - - `DB__REQUIRED` (default: `true`, fail-fast on startup and affects `/ready`) - - `DB__MAX_OPEN_CONNS` (default: `20`) - - `DB__MAX_IDLE_CONNS` (default: `10`) - - `DB__CONN_MAX_IDLE_TIME` (default: `2m`) - - `DB__CONN_MAX_LIFETIME` (default: `5m`) - -Alias notes: - -- aliases come from the consuming service, not from `go-core` -- aliases may contain underscores, for example `transaction_history` -- env lookup uses uppercase alias token, for example `DB_TRANSACTION_HISTORY_DRIVER` -- runtime map keys are normalized to lowercase for deterministic lookup - -Transaction naming note: - -- `dbtx` refers to SQL transaction orchestration. -- `TransactionLog` refers to transaction-flow monitoring for transaction-oriented services. -- They solve different concerns and are intentionally separate. - -Migration: - -- `MIGRATION_AUTO_RUN` (default: `false`) -- `MIGRATION_DB` (no default; must exist in `DB_LIST` when auto-run enabled) -- `MIGRATION_DIR` (no default; required when auto-run enabled) -- `MIGRATION_LOCK_ENABLED` (default: `true`) -- `MIGRATION_LOCK_KEY` (default: empty; auto-generated as `:migration:`) -- `MIGRATION_LOCK_TIMEOUT` (default: `30s`) - -When lock is enabled, auto-migration uses DB-native locks to avoid concurrent `goose up` on multi-pod startup (`sp_getapplock` for SQL Server, `GET_LOCK` for MySQL, advisory lock for Postgres). - -Migration runtime notes: - -- `migration.AutoRunUp(cfg)` remains the compact explicit entry point. -- `migration.AutoRunUpWithLogger(cfg, log)` is available when the service wants startup migration runtime signals through `ServiceLog`. -- logger-aware autorun emits `migration_autorun` and `migration_lock` without adding hidden startup behavior. - -Observability: - -- `OTEL_EXPORTER_OTLP_ENDPOINT` (optional) -- `OTEL_EXPORTER_OTLP_INSECURE` (default: `false`; set `true` only for local/non-TLS collector) -- `OTEL_EXPORTER_OTLP_CA_CERT_FILE` (optional custom CA for OTLP TLS) -- `TRACE_SAMPLING_RATIO` (default: `0.1`) - -Redis: - -- `REDIS_ENABLED` (default: `false`) -- `REDIS_ADDRESS` (required if enabled) -- `REDIS_PASSWORD` -- `REDIS_DB` (default: `0`) - -Behavior: - -- enabling Redis means the service has chosen Redis as a required runtime dependency -- Redis initialization is fail-fast during `app.New(...)` -- `/ready` reports Redis as required when enabled and returns `503` if Redis health fails - -Memcached: - -- `MEMCACHED_ENABLED` (default: `false`) -- `MEMCACHED_SERVERS` (comma-separated, required if enabled) -- `MEMCACHED_ADDRESS` (single address fallback, optional) -- `MEMCACHE_HOST` (legacy host fallback, default: empty) -- `MEMCACHE_PORT` (legacy port fallback, default: `11211`) -- `MEMCACHED_TIMEOUT` (default: `2s`) - -Behavior: - -- enabling Memcached means the service has chosen Memcached as a required runtime dependency -- Memcached initialization is fail-fast during `app.New(...)` -- Memcached health uses a bounded `Get(...)` probe where `cache miss` is treated as healthy by design -- `/ready` reports Memcached as required when enabled and returns `503` if Memcached health fails - -Kafka: - -- `KAFKA_ENABLED` (default: `false`) -- `KAFKA_BROKERS` (required if enabled; comma-separated) -- `KAFKA_CLIENT_ID` -- `KAFKA_USERNAME` (SASL Plain username) -- `KAFKA_PASSWORD` (SASL Plain password) -- `KAFKA_JKS_FILE` (Path to JKS certificate file) -- `KAFKA_JKS_PASSWORD` (Password for JKS file) - -Auth: - -- `INTERNAL_JWT_ENABLED` -- `INTERNAL_JWT_PUBLIC_KEY` (used as static key if JWKS is not configured) -- `INTERNAL_JWT_JWKS_ENDPOINT` (enables dynamic background JWKS fetching via keyfunc) -- `INTERNAL_JWT_JWKS_REFRESH_INTERVAL` (default: `1h`) -- `INTERNAL_JWT_ISSUER` -- `INTERNAL_JWT_AUDIENCE` -- `INTERNAL_JWT_LEEWAY` (default: `30s`) -- `INTERNAL_JWT_INCLUDE_METHODS` (optional, comma-separated gRPC full methods) -- `INTERNAL_JWT_EXCLUDE_METHODS` (optional, comma-separated gRPC full methods) - -- `AUTH_SIGNATURE_ENABLED` (default: `false`, enables HTTP payload signature verification) -- `AUTH_SIGNATURE_MASTER_KEY` (secret key used for HMAC-SHA256 signature verification) -- `AUTH_SIGNATURE_HEADER_KEY` (default: `x-signature`) -- `AUTH_SIGNATURE_TIMESTAMP_KEY` (default: `x-timestamp`) -- `AUTH_SIGNATURE_MAX_TIME_DRIFT` (default: `5m`, prevents replay attacks) - -Method policy notes: - -- If `INTERNAL_JWT_INCLUDE_METHODS` is set, only listed methods enforce JWT. -- If include list is empty, all methods enforce JWT except those in exclude list. -- `INTERNAL_JWT_INCLUDE_METHODS` and `INTERNAL_JWT_EXCLUDE_METHODS` cannot be used together. - -### Recommended baseline env (production-like) - -```env -SERVICE_NAME=transaction-history-service -APP_ENV=production -LOG_LEVEL=info -SHUTDOWN_TIMEOUT=10s -GRPC_PORT=9090 -HTTP_PORT=8080 - -# Database (example) -DB_LIST=primary -DB_PRIMARY_DRIVER=sqlserver -DB_PRIMARY_HOST=127.0.0.1 -DB_PRIMARY_PORT=1433 -DB_PRIMARY_NAME=app_db -DB_PRIMARY_USER=sa -DB_PRIMARY_PASSWORD=******** -DB_PRIMARY_REQUIRED=true -DB_PRIMARY_CONN_MAX_IDLE_TIME=2m - -MIGRATION_AUTO_RUN=true -MIGRATION_DB=primary -MIGRATION_DIR=migrations/primary - -# Internal JWT -INTERNAL_JWT_ENABLED=true -INTERNAL_JWT_PUBLIC_KEY=/etc/secrets/internal-jwt-public.pem -INTERNAL_JWT_ISSUER=internal-auth -INTERNAL_JWT_AUDIENCE=internal-services -INTERNAL_JWT_LEEWAY=30s -# Choose one: -# INTERNAL_JWT_INCLUDE_METHODS=/history.v1.HistoryService/CreateTransactionHistory -# INTERNAL_JWT_EXCLUDE_METHODS=/grpc.health.v1.Health/Check,/grpc.health.v1.Health/Watch - -# Optional dependencies -REDIS_ENABLED=false -MEMCACHED_ENABLED=false -KAFKA_ENABLED=false +```text +github.com/yogayulanda/go-core ``` -### API Response Contracts - -`go-core` enforces strict JSON response envelopes at the API Gateway level to ensure a consistent experience for downstream clients (Frontend/Mobile). All gRPC responses are automatically wrapped. +## What It Solves + +- Consistent service bootstrap and graceful shutdown. +- Environment-driven configuration with validation. +- gRPC and HTTP/gRPC-Gateway server wiring. +- Request IDs, recovery, auth metadata/JWT verification, and middleware/interceptor behavior. +- Structured logging with sensitive-field redaction. +- Prometheus metrics and OpenTelemetry tracing hooks. +- SQL database initialization, migrations, and transaction helpers. +- Kafka publishing/consuming and outbox helpers. +- Redis and Memcached cache initialization. +- Resilience helpers for timeout, retry, circuit breaker, and HTTP clients. +- Stable application error contracts for transport-safe responses. + +## Architecture Overview + +`go-core` keeps infrastructure concerns separated by package: + +- `app/` owns application container setup, dependency initialization, and lifecycle hooks. +- `config/` loads and validates environment-based runtime configuration. +- `server/grpc/` provides gRPC server construction, interceptors, recovery, auth, and metrics. +- `server/gateway/` provides HTTP/gRPC-Gateway setup, envelopes, health/readiness/version/metrics endpoints, pprof, CORS, signature validation, and panic recovery. +- `database/` opens configured SQL databases through GORM. +- `dbtx/` provides explicit SQL transaction propagation helpers. +- `migration/` wraps Goose migration execution and optional startup auto-run. +- `messaging/` provides Kafka publisher/consumer abstractions. +- `messaging/outbox/` provides driver-aware SQL outbox helpers. +- `logger/` defines structured technical logging contracts and redaction. +- `observability/` contains tracing, metrics, request ID, and transaction ID helpers. +- `errors/` defines application error taxonomy and gRPC/HTTP mapping. +- `cache/`, `httpclient/`, `resilience/`, `security/`, and `version/` cover supporting runtime concerns. + +The intended service shape remains conventional Go: + +1. Transport handlers translate requests into service calls. +2. Service/domain code owns business rules. +3. Repository code owns persistence access. +4. `go-core` supplies shared runtime, transport, observability, and infrastructure glue. + +## Tech Stack + +- Go 1.24+ +- gRPC and grpc-gateway +- GORM with SQL Server support and DSN composition helpers for MySQL/PostgreSQL/SQL Server +- Goose migrations +- Kafka via `segmentio/kafka-go` +- Redis and Memcached clients +- Prometheus metrics +- OpenTelemetry tracing +- Zap logging +- JWT verification with static RSA public key or JWKS +- Resty-based resilient HTTP client + +## Development Setup + +Requirements: + +- Go 1.24 or newer +- `make` +- Optional: `golangci-lint` for linting +- Optional: `k6` for load-gate scripts + +Clone and verify: -#### Success response - -HTTP `2xx` responses are wrapped symmetrically: - -```json -{ - "success": true, - "trace_id": "req-123", - "transaction_id": "tx-123", - "timestamp": "2026-05-05T17:00:00Z", - "data": { - "id": "rec-998877", - "amount": 50000 - } -} -``` - -Notes: -- `data` contains the exact JSON translation of your Protobuf `message` definition. -- Health (`/health`), Ready (`/ready`), and Metrics endpoints are intentionally excluded from this envelope. - -#### Error response - -HTTP error response (gateway) is kept strictly structured and standardized: - -```json -{ - "success": false, - "code": "TRF-VAL-001", - "message": "invalid request", - "user_message": "User friendly message", - "trace_id": "req-123", - "transaction_id": "tx-123", - "timestamp": "2026-05-05T17:00:00Z", - "details": [ - {"field": "user_id", "reason": "required"} - ] -} +```bash +git clone https://github.com/yogayulanda/go-core.git +cd go-core +go mod download +make test +make vet ``` -Notes: - -- `details` is optional, typically used for validation errors. -- The `code` uses a strict `--` formatting logic. -- gRPC mapper automatically packs extended attributes (`domain`, `user_message`, `retryable`, `finality`) into the `ErrorInfo.Metadata`. -- HTTP Gateway extracts `trace_id` automatically from OTEL trace span, and `transaction_id` from observability context. -- Unknown external `ErrorInfo.reason` values are sanitized and fallback to gRPC status mapping. +Install the linter used by the repository: -#### Building Application Errors - -Downstream services should build errors using the `ErrorBuilder` to ensure correct taxonomy and categorization: - -```go -import coreErrors "github.com/yogayulanda/go-core/errors" - -var ErrInvalidAccount = coreErrors.Build("TRF", coreErrors.CategoryVAL, "001"). - Message("dest_account_number length is strictly 10 digits"). // Technical log - UserMessage("Nomor rekening tujuan tidak valid."). // Safe for frontend - Finality(coreErrors.FinalityBusiness). // E.g., Business, TechnicalRecoverable - Done() +```bash +go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.8 +make lint ``` -Categories: `VAL` (Validation), `AUTH` (Auth), `SES` (Session), `SWI` (Switch/Partner), `DB` (Database), `REC` (Recoverable/Technical). - -### Readiness behavior - -`GET /ready` returns JSON with per-component checks. -HTTP status is: - -- `200` when all required dependencies are ready. -- `503` when any required dependency is not ready. +Run the standard local gate: -Required dependencies: - -- all required databases (`DB__REQUIRED=true`) -- Redis (if `REDIS_ENABLED=true`) -- Memcached (if `MEMCACHED_ENABLED=true`) -- Kafka broker reachability (if `KAFKA_ENABLED=true`) - -Cache notes: - -- enabling Redis or Memcached is an explicit service choice, not passive configuration -- cache initialization is fail-fast during app bootstrap -- cache runtime startup emits `ServiceLog` with `operation=cache_connect` - -Example response: - -```json -{ - "status": "not_ready", - "checks": { - "database.primary": {"status": "up", "required": true}, - "redis": {"status": "down", "required": true, "message": "health check failed"}, - "memcached": {"status": "skipped", "required": false, "message": "disabled"}, - "kafka": {"status": "skipped", "required": false, "message": "disabled"} - } -} +```bash +make check ``` -If your service does not use database, you can keep `DB_LIST` empty. -When `MIGRATION_AUTO_RUN=true`, `MIGRATION_DB` and `MIGRATION_DIR` must be set explicitly, and `MIGRATION_DB` must exist in `DB_LIST`. +Run the stronger release-oriented local gate: -### Golden path for a new service - -Canonical startup flow: +```bash +make quality-gate +``` -1. `config.Load(...)` -2. `cfg.Validate()` -3. optional `migration.AutoRunUp(cfg)` -4. `app.New(ctx, cfg)` -5. build gRPC and/or gateway transport -6. `server.Run(ctx, application, ...)` +## Environment Configuration -Use: +Configuration is loaded from environment variables. For local development, copy `.env.example` to `.env` and adjust values for your local dependencies. -- `errors.AppError` for service error contract -- `dbtx.WithTx(...)` for SQL transaction orchestration -- `logger.ServiceLog` for structured service-flow logging -- `logger.DBLog` for structured DB logging when the service touches a database -- `TransactionLog` only when the service belongs to the transaction-oriented class -- Redis, Memcached, Kafka, and migration only when the service explicitly chooses them -- rely on `server.Run(...)` lifecycle/service logs for startup, shutdown, and component failure orchestration -- rely on gateway/gRPC transport wrappers for aligned request ID, request metrics, and additive service metrics -- rely on `app.NewKafkaPublisher(...)` / `app.NewKafkaConsumer(...)` for default messaging logger + metrics wiring when Kafka is enabled -- keep outbox worker startup explicit through `outbox.Worker.StartChecked(ctx)` or service-controlled `RunOnce(ctx)` +Core variables: -### Logging flavors +- `SERVICE_NAME` +- `APP_ENV` +- `LOG_LEVEL` +- `LOG_TIMEZONE` +- `SHUTDOWN_TIMEOUT` +- `GRPC_PORT` +- `HTTP_PORT` +- `GRPC_TLS_ENABLED`, `GRPC_TLS_CERT_FILE`, `GRPC_TLS_KEY_FILE` +- `HTTP_TLS_ENABLED`, `HTTP_TLS_CERT_FILE`, `HTTP_TLS_KEY_FILE` -`go-core` supports three intentional logging flavors: +Database variables: -- `ServiceLog`: - standard structured log for normal technical service flow -- `DBLog`: - standard structured log for DB connect/ping/query/timeout/failure reporting -- `TransactionLog`: - platform-standard structured log for transaction-oriented services +- `DB_LIST` +- `DB__DRIVER` +- `DB__DSN` +- `DB__HOST` +- `DB__PORT` +- `DB__NAME` +- `DB__USER` +- `DB__PASSWORD` +- `DB__PARAMS` +- `DB__REQUIRED` -Keep using `Info/Error/Debug/Warn` for flexible low-level technical logs and framework internals. -Keep using `EventLog` for important event/compliance-style logging. +Optional runtime dependencies: -Runtime and transport alignment now means: +- Redis: `REDIS_ENABLED`, `REDIS_ADDRESS`, `REDIS_PASSWORD`, `REDIS_DB` +- Memcached: `MEMCACHED_ENABLED`, `MEMCACHED_SERVERS`, `MEMCACHED_TIMEOUT` +- Kafka: `KAFKA_ENABLED`, `KAFKA_BROKERS`, `KAFKA_CLIENT_ID`, `KAFKA_USERNAME`, `KAFKA_PASSWORD` +- Migrations: `MIGRATION_AUTO_RUN`, `MIGRATION_DB`, `MIGRATION_DIR`, `MIGRATION_LOCK_ENABLED` +- Tracing: `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_INSECURE`, `OTEL_EXPORTER_OTLP_CA_CERT_FILE`, `TRACE_SAMPLING_RATIO` +- Auth: `INTERNAL_JWT_ENABLED`, `INTERNAL_JWT_PUBLIC_KEY`, `INTERNAL_JWT_JWKS_ENDPOINT`, `INTERNAL_JWT_ISSUER`, `INTERNAL_JWT_AUDIENCE` +- HTTP signatures: `AUTH_SIGNATURE_ENABLED`, `AUTH_SIGNATURE_MASTER_KEY` -- `app.New(...)`, `app.Start(...)`, lifecycle shutdown, and `server.Run(...)` emit structured `ServiceLog` for orchestration milestones. -- `server.LogStartupReadiness(...)` emits readiness `ServiceLog` instead of ad hoc startup strings. -- gRPC request flow emits both request metrics and additive service metrics under `grpc_request`. -- HTTP gateway flow emits both HTTP metrics and additive service metrics under `http_request`. -- publisher flow emits `message_publish` service logs plus `app_message_publish_total`. -- consumer flow emits `message_consume` service logs plus consume/process metrics. -- outbox worker emits `outbox_worker` and `outbox_batch` service logs plus outbox batch metrics. +See `docs/CONFIGURATION_PROFILES.md` for grouped configuration guidance. -### Minimal integration flow in a service +## Minimal Service Bootstrap ```go package main @@ -488,7 +153,6 @@ import ( "os" "os/signal" "syscall" - "time" coreapp "github.com/yogayulanda/go-core/app" coreconfig "github.com/yogayulanda/go-core/config" @@ -519,100 +183,128 @@ func main() { if err != nil { log.Fatal(err) } + grpcServer, err := coregrpc.New(application) if err != nil { log.Fatal(err) } + grpcServer.Register(func(s *grpc.Server) { + // Register service implementations here. + }) + gatewayServer, err := coregateway.New(application, func(ctx context.Context, mux *runtime.ServeMux) error { - // register grpc-gateway handlers here + // Register grpc-gateway handlers here. return nil }) if err != nil { log.Fatal(err) } - grpcServer.Register(func(s *grpc.Server) { - // register grpc service handlers here - }) - - go coreserver.LogStartupReadiness(ctx, application.Logger(), cfg.GRPC.Port, cfg.HTTP.Port, 10*time.Second, cfg.HTTP.TLSEnabled) - if err := coreserver.Run(ctx, application, grpcServer, gatewayServer); err != nil { log.Fatal(err) } } ``` -More guidance: +## Error Handling Philosophy + +External responses should be predictable and safe. Internal details belong in logs and traces, not client payloads. + +- Use `errors.AppError` and the builder APIs for application-visible failures. +- Keep validation details explicit and structured. +- Sanitize unknown/internal transport errors to stable public messages. +- Preserve richer technical context through structured logs and observability signals. + +## Observability + +The repository provides additive observability primitives rather than a mandatory platform: + +- structured service, DB, event, and transaction-oriented logs +- sensitive key redaction for common secret fields +- Prometheus metrics for HTTP, gRPC, service operations, DB operations, messaging, outbox, and transaction-oriented flows +- OpenTelemetry tracing bootstrap and transport wrappers +- health, readiness, metrics, version, and optional pprof endpoints + +## Repository Structure + +```text +app/ Application container and lifecycle +cache/ Redis and Memcached helpers +config/ Environment configuration loading and validation +database/ SQL database initialization +dbtx/ SQL transaction propagation helpers +docs/ Architecture, operations, reliability, and release docs +errors/ Application error contract and transport mapping +examples/ Focused integration examples +httpclient/ Resilient outbound HTTP client +logger/ Structured logging contracts and redaction +messaging/ Kafka abstractions and outbox support +migration/ Goose migration helpers +observability/ Metrics, tracing, request ID, transaction ID +resilience/ Timeout, retry, and circuit breaker helpers +scripts/ Quality, smoke, load, and failure-drill scripts +security/ Auth metadata and JWT verification helpers +server/ gRPC, gateway, and startup orchestration +templates/ Reference package templates +version/ Build/version metadata +``` + +## Engineering Principles + +- Prefer pragmatic, idiomatic Go over framework-heavy abstractions. +- Keep repository behavior as the source of truth; docs should describe actual implementation. +- Make boundaries explicit between transport, service/domain logic, repositories, and infrastructure. +- Keep operational behavior readable in code, logs, metrics, and release evidence. +- Add abstractions only when they remove real duplication or clarify ownership. +- Treat validation honestly: fail fast for required runtime dependencies and surface actionable configuration errors. +- Avoid hidden magic, speculative rewrites, and product-specific business logic in the foundation. + +## Documentation + +Useful starting points: +- `docs/ARCHITECTURE.md` - `docs/SERVICE_BOOTSTRAP.md` -- `docs/TRANSACTION_OBSERVABILITY.md` -- `docs/FOUNDATION_BOUNDARY.md` -- `docs/MESSAGING_PATTERN.md` - `docs/CONFIGURATION_PROFILES.md` +- `docs/ERROR_HANDLING.md` +- `docs/OBSERVABILITY.md` +- `docs/MESSAGING_PATTERN.md` +- `docs/RELIABILITY.md` +- `docs/SECURITY.md` +- `docs/VERSIONING.md` +- `MIGRATION.md` -### Quality checks - -Install linter: +Architecture diagram placeholder: -```bash -go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.8 +```text +Client -> Gateway/gRPC -> Handler -> Service -> Repository -> Database + | | | + | | +-> Outbox/Kafka + | +-> Logger/Metrics/Tracing + +-> Middleware/Interceptors/Auth/Recovery ``` -Run checks: +## Release Discipline + +CI should remain the fast baseline: ```bash make test make vet make lint -# or run all: -make check ``` -CI: - -- `.github/workflows/ci.yml` runs `go test ./...`, `go vet ./...`, and `golangci-lint run` - -Foundation repo change discipline: - -- review `docs/CHANGE_CHECKLIST.md` -- update `MIGRATION.md` whenever public upgrade behavior changes -- update `CHANGELOG.md` for each tagged release - -### Production sign-off - -Detailed checklist: -- `docs/PRODUCTION_SIGNOFF.md` - -Evidence template: -- `docs/RELEASE_EVIDENCE_TEMPLATE.md` - -Gate commands: +Release candidates should use the stronger local/release checks where applicable: ```bash -# full local quality/security gate make quality-gate - -# staging smoke gate BASE_URL=https://staging.example.com make smoke-gate - -# staging load gates (requires k6) -BASE_URL=https://staging.example.com TARGET_PATH=/v1/your-endpoint make load-steady -BASE_URL=https://staging.example.com TARGET_PATH=/v1/your-endpoint make load-spike -BASE_URL=https://staging.example.com TARGET_PATH=/v1/your-endpoint make load-soak - -# staging failure drill (example with kubectl) -BASE_URL=https://staging.example.com \ -STOP_DB_CMD="kubectl scale deploy/db --replicas=0 -n staging" \ -START_DB_CMD="kubectl scale deploy/db --replicas=1 -n staging" \ -STOP_KAFKA_CMD="kubectl scale sts/kafka --replicas=0 -n staging" \ -START_KAFKA_CMD="kubectl scale sts/kafka --replicas=1 -n staging" \ -make failure-drill +BASE_URL=https://staging.example.com TARGET_PATH=/health make load-steady ``` -### Version metadata +Use `docs/PRODUCTION_SIGNOFF.md` and `docs/RELEASE_EVIDENCE_TEMPLATE.md` when preparing a production service release that consumes this module. -Set build-time values with `-ldflags` for `/version` endpoint data: +Set build metadata with `-ldflags` when building a service that exposes `/version`: ```bash go build -ldflags "\ @@ -621,8 +313,21 @@ go build -ldflags "\ -X 'github.com/yogayulanda/go-core/version.BuildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" ``` -### Release notes and changelog +## Roadmap + +- Keep compatibility and migration guidance clear for public releases. +- Continue tightening docs around service bootstrap and operational behavior. +- Expand examples only when they reflect real implementation patterns. +- Avoid adding product-specific assumptions to the foundation. + +## Contributing + +See `CONTRIBUTING.md`. + +## Security + +See `SECURITY.md` for responsible disclosure guidance. + +## License -- use GitHub Release for announcement-style release notes -- use `CHANGELOG.md` for repository version history -- use `docs/RELEASING.md` for the repeatable release process +Apache License 2.0. See `LICENSE`. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..b5de9c4 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,26 @@ +# Security Policy + +`go-core` is an open source engineering library, not a hosted SaaS product or managed runtime service. Security reports should focus on repository code, examples, documentation, dependency behavior, and unsafe defaults that could affect consuming services. + +## Reporting a Vulnerability + +Please report suspected vulnerabilities privately through GitHub Security Advisories for this repository when available. If advisories are not available, contact the repository maintainer through a private channel and include only the minimum technical detail needed to reproduce the issue. + +Do not open a public issue for active vulnerabilities, leaked secrets, or exploit details before maintainers have had a reasonable chance to investigate. + +## What to Include + +- affected package, file, or behavior +- reproduction steps or proof of concept using synthetic data +- expected impact +- affected versions or commits if known +- suggested mitigation if available + +## Operational Security Expectations + +- Do not include production credentials, private keys, customer data, internal URLs, or proprietary infrastructure details in reports, issues, PRs, examples, or tests. +- Redact tokens, passwords, DSNs, certificates, and authorization headers. +- Use local or synthetic fixtures when demonstrating security behavior. +- Rotate any real secret that may have been committed, logged, or shared publicly. + +Security fixes should preserve transport-safe error responses and keep sensitive details in protected logs or traces only. diff --git a/docs/PRODUCTION_SIGNOFF.md b/docs/PRODUCTION_SIGNOFF.md index 45f8c0f..e615c57 100644 --- a/docs/PRODUCTION_SIGNOFF.md +++ b/docs/PRODUCTION_SIGNOFF.md @@ -31,7 +31,7 @@ CI baseline: - `.github/workflows/ci.yml` runs `make test`, `make vet`, and `make lint` on push and pull request - local `make quality-gate` remains the stronger release gate because it also includes race testing and `gosec` -## 2) Smoke Gate (mandatory, staging) +## 2) Smoke Gate (mandatory, release environment) Run: @@ -45,7 +45,7 @@ Pass criteria: - `/version` returns 200 - `/version` matches the injected `version`, `commit`, and `build_date` for the release candidate -## 3) Performance Gate (mandatory, staging) +## 3) Performance Gate (mandatory, release environment) Use k6 scenario runner: @@ -70,16 +70,16 @@ Tune thresholds with env: - `P99_MS` - `FAIL_RATE` -## 4) Failure Drill Gate (mandatory, staging) +## 4) Failure Drill Gate (mandatory, release environment) -Run with dependency stop/start commands (usually `kubectl` commands): +Run with dependency stop/start commands supplied by the consuming service environment: ```bash BASE_URL=https://staging.example.com \ -STOP_DB_CMD="kubectl scale deploy/db --replicas=0 -n staging" \ -START_DB_CMD="kubectl scale deploy/db --replicas=1 -n staging" \ -STOP_KAFKA_CMD="kubectl scale sts/kafka --replicas=0 -n staging" \ -START_KAFKA_CMD="kubectl scale sts/kafka --replicas=1 -n staging" \ +STOP_DB_CMD="" \ +START_DB_CMD="" \ +STOP_KAFKA_CMD="" \ +START_KAFKA_CMD="" \ make failure-drill ``` diff --git a/docs/RELEASE_EVIDENCE_TEMPLATE.md b/docs/RELEASE_EVIDENCE_TEMPLATE.md index 9f7676f..cb653da 100644 --- a/docs/RELEASE_EVIDENCE_TEMPLATE.md +++ b/docs/RELEASE_EVIDENCE_TEMPLATE.md @@ -12,7 +12,7 @@ - [ ] `docs/CHANGE_CHECKLIST.md` reviewed - Notes: -## Smoke Gate (staging) +## Smoke Gate - [ ] `/health` PASS - [ ] `/ready` PASS @@ -21,7 +21,7 @@ - Base URL: - Notes: -## Performance Gate (staging) +## Performance Gate - [ ] Steady PASS - [ ] Spike PASS diff --git a/migration/goose_autorun_test.go b/migration/goose_autorun_test.go index f60db29..77afefc 100644 --- a/migration/goose_autorun_test.go +++ b/migration/goose_autorun_test.go @@ -355,7 +355,7 @@ func TestAutoRunUpWithRunner_CustomLockKey_UseCustomLockKey(t *testing.T) { func minimalAutoRunConfig() *config.Config { return &config.Config{ App: config.AppConfig{ - ServiceName: "transaction-history-service", + ServiceName: "example-service", }, Databases: map[string]config.DBConfig{ "transaction_history": { diff --git a/migration/goose_lock_test.go b/migration/goose_lock_test.go index d838120..f9f673b 100644 --- a/migration/goose_lock_test.go +++ b/migration/goose_lock_test.go @@ -35,10 +35,10 @@ func TestDefaultMigrationLockKey_ServiceAndDB_ReturnExpectedKey(t *testing.T) { { name: "service and db name set", cfg: &config.Config{ - App: config.AppConfig{ServiceName: "transaction-history-service"}, + App: config.AppConfig{ServiceName: "example-service"}, }, dbName: "history", - want: "transaction-history-service:migration:history", + want: "example-service:migration:history", }, }