feat: implement v2 interfaces across modules (v1.12.1)#85
Conversation
Analyzed existing codebase against all 4 plans: - TenantGuard: ~50% exists (context, service, config, decorators) - Dynamic Reload: ~25% exists (observer, field tracking, config providers) - Aggregate Health: ~15% exists (reverseproxy health checker) - BDD/Contract Testing: ~65% exists (121 tests, contract CLI, CI) Revised checklists to only cover remaining work. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… emission Implements HealthProvider interface, HealthReport/AggregatedHealth types, provider adapters (simple, static, composite), and AggregateHealthService with fan-out evaluation, panic recovery, cache TTL, temporary error detection, and CloudEvent emission on status changes. 17 tests including concurrency and race-detector verified. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduces TenantGuard interface and StandardTenantGuard implementation with strict/lenient/disabled modes, whitelist support, ring buffer violation tracking, and CloudEvents emission on violations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…and rollback Add Reloadable interface to module.go for modules that support runtime config reloading. Implement ReloadOrchestrator with single-flight execution, exponential backoff circuit breaker, reverse-order rollback on partial failure, and CloudEvents emission for reload lifecycle. New files: - reload.go: ChangeType, ConfigChange, FieldChange, ConfigDiff, ReloadTrigger types - reload_orchestrator.go: ReloadOrchestrator with queue, circuit breaker, rollback - reload_test.go: comprehensive tests (ConfigDiff, orchestrator, rollback, circuit breaker, concurrency) Also fix pre-existing WithLogger name collision in health_service.go -> WithHealthLogger. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace dynamic errors.New() with sentinel errors (err113) - Add StatusUnknown case to exhaustive switch (exhaustive) - Derive request context from parent context (contextcheck) - Wrap interface method error return (wrapcheck) - Fix gofmt alignment in builder.go, contract_verifier.go, observer.go - Update letsencrypt go.sum for httpserver@v1.12.0 checksum Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reload orchestrator: - Sort targets by module name for deterministic reload/rollback order - Make Stop() idempotent with sync.Once, reject requests after stop - Move noop check before started event to avoid misleading event stream - Apply default 30s timeout when ReloadTimeout() returns <= 0 - Add doc comment noting application integration is a follow-up Health service: - Switch from *log.Logger to modular.Logger for consistent structured logging - Return deep copies from Check() to prevent cache mutation by callers - Use NewCloudEvent helper for proper event ID/specversion - Prefer StatusUnhealthy over StatusUnknown on tie-breaks Tenant guard: - Switch from *log.Logger to modular.Logger for consistent structured logging - Deep-copy whitelist in constructor, convert to set for O(1) lookups - Use NewCloudEvent helper for proper event ID/specversion - Use structured logging (Warn with key-value pairs) instead of Printf - Wire guard into application service registry via builder Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ErrReloadStopped for stopped orchestrator (distinct from channel full) - Guard against nil logger with nopLogger fallback in NewReloadOrchestrator - Fix BenchmarkReload to call processReload directly instead of queue+drain - Update rollback test to assert deterministic sorted order Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- reload_orchestrator: add recover guard in RequestReload to prevent panic on send-to-closed-channel race with Stop() - builder: use app.RegisterService() instead of direct SvcRegistry map mutation for tenant guard registration - reload_contract_bdd_test: make rollback assertion deterministic now that targets are sorted by name Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- reload_orchestrator: extract handleReload() to properly scope context lifetime (no defer cancel leak in loop), propagate req.Ctx values and cancellation to module Reload calls instead of only copying deadline - health_service: worstStatus now maps StatusUnknown → StatusUnhealthy in output, consistent with documented aggregation behavior - reload_test: replace all time.Sleep waits with polling waitFor helper for deterministic CI-safe synchronization - reload_contract_bdd_test: replace time.Sleep waits with event/call polling helpers (bddWaitForEvent, bddWaitForCalls) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Keep context chain rooted in parentCtx and apply request deadline via context.WithDeadline instead of swapping the base context. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- contract_verifier: guard checkReloadIdempotent with goroutine+select so a misbehaving module cannot block the verifier indefinitely - health_service: Check now selects on ctx.Done() when collecting provider results, returning ctx.Err() on cancellation/timeout - tenant_guard: log NotifyObservers errors instead of silently dropping; update TenantGuardLenient doc to clarify logging is best-effort - reload_contract_bdd_test: simulate elapsed backoff by backdating lastFailure instead of manually clearing circuit breaker state - reload_orchestrator: propagate req.Ctx cancellation via background goroutine watching req.Ctx.Done(), not just deadline Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ErrReloadTimeout sentinel error, use in contract verifier - Wrap ctx.Err() in health_service.Check for wrapcheck compliance Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Covers 12 gaps identified in framework audit: config-driven deps, drainable shutdown, phase tracking, parallel init, type-safe services, service readiness events, plugin interface, reload integration, config file watcher, secret resolution, slog adapter, metrics hooks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
12 tasks covering all audit gaps: config-driven deps, drainable shutdown, phase tracking, parallel init, type-safe services, service readiness, plugin interface, reload integration, secret resolver, config watcher, slog adapter, metrics hooks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…hints Allow injecting dependency edges into the module dependency graph from the builder/config level, without requiring modules to implement the DependencyAware interface. Hints are merged into the graph before topological sort, enabling correct init ordering and cycle detection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ycle Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add sync.RWMutex to EnhancedServiceRegistry for thread-safe concurrent access - Protect all registry methods (RegisterService, GetService, OnServiceReady, etc.) - Fire readiness callbacks outside the lock to prevent deadlocks - Add Init(modular.Application) to ConfigWatcher to satisfy Module interface - Add ErrDynamicReloadNotEnabled sentinel error - Add []any slice handling to ExpandSecrets for YAML array configs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…events - application.go: fix moduleRegistry race in initModule log line (use local var) - application.go: guard CollectAllMetrics with initMu snapshot - application.go: emit EventTypeAppPhaseChanged via phaseChangeHook in ObservableApplication - application.go: add PhaseAware, ReloadableApp, MetricsCollector optional interfaces so callers don't need type assertions to *StdApplication - service.go: restructure locking — registerAndNotify acquires/releases lock with defer-safe pattern, removing fragile caller-must-hold contract - configwatcher: log watcher errors instead of silently discarding them - configwatcher: check stopCh in AfterFunc callback to avoid firing onChange after shutdown - configwatcher: add WithLogger option, capture app logger in Init - configwatcher_test: check os.WriteFile errors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- builder.go: unwrap ApplicationDecorator chain to find the underlying StdApplication/ObservableApplication before propagating options (dependencyHints, drainTimeout, dynamicReload, parallelInit) - application.go: validate that both From and To modules in dependency hints exist in moduleRegistry before merging into the graph Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds v2 enhancement interfaces to the eventbus module: - MetricsProvider: exposes delivery stats (delivered, dropped, topics, subscribers) - Drainable: PreStop signals drain phase before shutdown Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds v2 enhancement interfaces to the cache module: - MetricsProvider: exposes item count, max items, connected status - Reloadable: hot-reload TTL, max items, cleanup interval Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds v2 enhancement interfaces to the scheduler module: - MetricsProvider: exposes job counts, worker count, running state - Drainable: PreStop persists jobs and signals drain before shutdown Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…interfaces Adds v2 enhancement interfaces to the database module: - MetricsProvider: exposes sql.DBStats (connections, pool utilization) - Drainable: PreStop drains active connections before shutdown - Reloadable: hot-reload pool settings without reconnecting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds v2 enhancement interfaces to the reverseproxy module: - MetricsProvider: exposes request counts, error counts, backend count - Drainable: PreStop stops health checker during drain phase Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds v2 enhancement interfaces to the httpserver module: - Drainable: PreStop signals drain phase before graceful shutdown - Reloadable: hot-reload server timeouts without restart - MetricsProvider: exposes server state (started, port) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR rolls out the new “v2 enhancement” interfaces (metrics, drain/pre-stop, reload) across the modular core and multiple modules, along with a broad modernization sweep (e.g., any, maps/slices, typed reflection) and accompanying tests/examples.
Changes:
- Adds core support types/utilities (e.g.,
MetricsProvider,Drainable,Plugin, typed service helpers, secret expansion) and extends service registry behavior. - Implements v2 interfaces across several modules (scheduler/cache/eventbus/httpserver/reverseproxy/database/eventlogger) with new test coverage.
- Updates Go/toolchain versions and CI workflows; updates modules/examples
go.modfiles and dependencies.
Reviewed changes
Copilot reviewed 164 out of 168 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| go.mod | Bumps Go/toolchain; adds deps |
| go.sum | Updates dependency checksums |
| service.go | Adds locking + readiness callbacks + module registration helper |
| service_readiness_test.go | Tests readiness callbacks |
| service_typed.go | Adds typed service helpers |
| service_typed_test.go | Tests typed service helpers |
| metrics.go | Introduces module metrics interface/types |
| metrics_test.go | Tests metrics aggregation |
| drainable.go | Introduces drain phase interface + default timeout |
| drainable_test.go | Tests drain ordering/timeout option |
| plugin.go | Adds plugin interfaces + service definitions |
| plugin_test.go | Tests plugin wiring (modules/services/hooks) |
| phase.go | Adds lifecycle phase enum |
| phase_test.go | Tests phase transitions/stringer |
| secret_resolver.go | Adds secret reference expansion utility |
| secret_resolver_test.go | Tests secret expansion behavior |
| builder.go | Adds builder options (dependency hints/drain timeout/parallel init/dynamic reload/plugins) |
| builder_dependency_test.go | Tests config-driven dependency edges |
| reload_orchestrator.go | Updates event payload types to any |
| reload_test.go | Updates tests (contexts/slices, concurrency helpers) |
| reload_contract_bdd_test.go | Updates BDD assertions/concurrency patterns |
| reload_integration_test.go | Adds integration coverage for dynamic reload wiring |
| contract_verifier.go | Updates concurrency checks for reload contract |
| application_observer.go | Adds phase-change CloudEvent emission + any updates |
| observer.go | Adds app phase event type constant |
| observer_cloudevents.go | Switches CloudEvent constructors to any/map[string]any |
| observer_cloudevents_test.go | Updates tests for any payloads |
| observer_test.go | Updates tests to any + slices.Contains |
| decorator_observable.go | Updates event metadata types to any |
| config_feeders.go | Switches feeder interfaces to any |
| config_provider.go | Switches internal config maps/temps to any |
| config_validation.go | Switches config APIs to any + typed reflection helpers |
| tenant_service.go | Uses slices.Contains for duplicate module detection |
| tenant_guard_test.go | Updates loops to integer ranging |
| tenant_config_loader_test.go | Uses maps.Copy for map cloning |
| health_service.go | Uses maps.Copy for snapshot/deep-copy |
| health_test.go | Updates concurrency loops/helpers |
| modules/configwatcher/configwatcher.go | Adds config file watcher module (fsnotify + debounce) |
| modules/configwatcher/configwatcher_test.go | Tests file change + debounce behavior |
| modules/configwatcher/go.mod | New module go.mod updates |
| modules/scheduler/module.go | Implements MetricsProvider + Drainable (PreStop) |
| modules/scheduler/scheduler.go | Adds gosec/contextcheck nolints |
| modules/scheduler/v2_interfaces_test.go | Tests scheduler metrics + drain behavior |
| modules/scheduler/go.mod | Bumps Go/toolchain + modular version |
| modules/cache/engine.go | Extends cache engine with Stats() |
| modules/cache/memory.go | Implements Stats() |
| modules/cache/redis.go | Implements Stats() |
| modules/cache/v2_interfaces.go | Adds metrics + reload support |
| modules/cache/v2_interfaces_test.go | Tests cache metrics + reload |
| modules/cache/go.mod | Bumps Go/toolchain + modular version |
| modules/eventbus/v2_interfaces.go | Adds metrics + drain behavior |
| modules/eventbus/v2_interfaces_test.go | Tests eventbus metrics + PreStop |
| modules/eventbus/* (memory/redis/nats/kafka/kinesis/etc.) | Adds gosec nolints around cancel storage |
| modules/eventbus/go.mod | Bumps Go/toolchain + modular version |
| modules/httpserver/module.go | Adds draining flag field |
| modules/httpserver/v2_interfaces.go | Adds drain/reload/metrics implementations |
| modules/httpserver/v2_interfaces_test.go | Tests drain/reload/metrics behaviors |
| modules/httpserver/go.mod | Bumps Go version + modular version |
| modules/reverseproxy/v2_interfaces.go | Adds metrics + drain behavior |
| modules/reverseproxy/v2_interfaces_test.go | Tests reverseproxy metrics + PreStop |
| modules/reverseproxy/module.go | Moves proxy setup to Rewrite and adjusts proxy cloning |
| modules/reverseproxy/go.mod | Bumps Go version + modular version |
| modules/database/v2_interfaces.go | Adds metrics + drain + reload behavior |
| modules/database/v2_interfaces_test.go | Tests db metrics/drain/reload |
| modules/database/go.mod | Bumps Go version + modular version |
| modules/eventlogger/module.go | Adds PreStop flush behavior |
| modules/eventlogger/v2_interfaces_test.go | Tests drainable compliance |
| modules/eventlogger/go.mod | Bumps Go/toolchain + modular version |
| modules/auth/go.mod | Bumps Go version + modular version |
| modules/chimux/go.mod | Bumps Go version + modular version |
| modules/httpclient/go.mod | Bumps Go version + modular version |
| modules/jsonschema/go.mod | Bumps Go/toolchain + modular version |
| modules/letsencrypt/go.mod | Bumps Go version + modular/httpserver versions |
| modules/logmasker/go.mod | Bumps Go version + modular version |
| examples/*/go.mod | Bumps Go/toolchain versions in examples |
| examples/*/main.go | Updates imports to database/v2 where applicable |
| .github/workflows/*.yml | Updates CI Go version + workflow tweaks |
| docs/plans/2026-03-09-modular-v2-enhancements-design.md | Adds design plan doc for v2 enhancements |
Fix duplicate imports (slices, maps) introduced by merge and resolve whitespace alignment conflicts in test files. Keep v1.12.1 dependency + replace directives for all modules. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📋 API Contract Changes Summary✅ No breaking changes detected - only additions and non-breaking modifications Changed Components:Core FrameworkContract diff saved to artifacts/diffs/core.json Module: authContract diff saved to artifacts/diffs/auth.json Module: cacheContract diff saved to artifacts/diffs/cache.json Module: chimuxContract diff saved to artifacts/diffs/chimux.json Module: configwatcherContract diff saved to artifacts/diffs/configwatcher.json Module: databaseContract diff saved to artifacts/diffs/database.json Module: eventbusContract diff saved to artifacts/diffs/eventbus.json Module: eventloggerContract diff saved to artifacts/diffs/eventlogger.json Module: httpclientContract diff saved to artifacts/diffs/httpclient.json Module: httpserverContract diff saved to artifacts/diffs/httpserver.json Module: jsonschemaContract diff saved to artifacts/diffs/jsonschema.json Module: letsencryptContract diff saved to artifacts/diffs/letsencrypt.json Module: logmaskerContract diff saved to artifacts/diffs/logmasker.json Module: reverseproxyContract diff saved to artifacts/diffs/reverseproxy.json Module: schedulerContract diff saved to artifacts/diffs/scheduler.json Artifacts📁 Full contract diffs and JSON artifacts are available in the workflow artifacts. |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove all `replace` directives from module go.mod files; local dev should use go.work (already gitignored) instead of shipping replace directives that break downstream consumers - Run go mod tidy on all 14 modules to update go.sum - Fix letsencrypt httpserver dep back to v1.12.0 (no v1.12.1 tag yet) - Guard reflect.Type.Elem() in generateUniqueName for non-pointer types to prevent panic when module is passed as a value (not a pointer) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Cache: protect config field reads/writes with configMu to prevent races between Reload() and concurrent Set()/Stats() calls. Lock MemoryCache mutex when updating MaxItems to synchronize with engine reads. - Cache: remove cleanupInterval from Reload (ticker already running). - httpserver: stop mutating m.server timeout fields in Reload to avoid data races on a running http.Server; update config only. - reverseproxy: remove slog fallback in PreStop to use framework logger. - Redis: replace PING-based Stats with pool statistics (no network I/O). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- httpserver: use static ErrServerNotStarted instead of dynamic fmt.Errorf to satisfy err113 lint rule. - eventlogger: increase buffer sizes in SynchronousStartupConfigFlag test (5→50) and metadata BDD test (10→50) to absorb framework lifecycle events that fill small buffers under race-instrumented CI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- httpserver: protect m.started and m.config reads/writes with m.mu in CanReload, Reload, and CollectMetrics - database: fix PreStop SetMaxOpenConns(0) which means unlimited, not zero — cap at max(stats.InUse, 1) to actually restrict the pool - database: add connMu RWMutex to protect m.connections map iteration in CollectMetrics and PreStop - cache: move c.config.MaxItems read inside c.mutex.RLock in Stats Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Implements the new v2 enhancement interfaces (
MetricsProvider,Drainable,Reloadable) across all applicable modules, targeting modular core v1.12.1.Module Enhancements
Other Changes
modular v1.12.1Test plan
go build ./...)go test ./...)🤖 Generated with Claude Code