Skip to content

Commit d0a62e8

Browse files
intel352claude
andauthored
feat: Modular v2 enhancements — 12 gaps + modernization (#83)
* docs: revise reimplementation plans with gap analysis 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> * feat: add aggregate health service with providers, caching, and event 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> * feat: add TenantGuard enforcement layer for multi-tenant isolation 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> * feat: add dynamic reload manager with orchestrator, circuit breaker, 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> * feat: add contract verifier and performance benchmarks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add BDD contract tests for reload and health subsystems Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve CI lint failures and letsencrypt checksum mismatch - 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> * fix: address all PR review comments 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> * fix: address second round of PR review comments - 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> * fix: address remaining PR review comments (round 3) - 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> * fix: address round 4 PR review comments - 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> * fix: resolve contextcheck lint error in handleReload 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> * fix: address round 5 PR review comments - 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> * fix: resolve err113 and wrapcheck lint errors - 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> * docs: add modular v2 enhancements design doc 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> * docs: add modular v2 enhancements implementation plan 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> * chore: upgrade to Go 1.26, fix plan tech stack reference Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add WithModuleDependency builder option for config-driven dependency 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> * feat: add Drainable interface with PreStop drain phase Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add application phase tracking with lifecycle transitions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add WithParallelInit for concurrent module initialization Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add RegisterTypedService/GetTypedService generic helpers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add OnServiceReady callback for service readiness events Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add Plugin interface with WithPlugins builder option Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add WithDynamicReload to wire ReloadOrchestrator into app lifecycle Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add SecretResolver interface and ExpandSecrets utility Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add configwatcher module with fsnotify file watching Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add SlogAdapter wrapping *slog.Logger for Logger interface Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add MetricsProvider interface and CollectAllMetrics Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address code review findings (C1, C2, I1-I3) - 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> * refactor: modernize codebase with gopls quickfixes Applied gopls modernization across 63 files: - Replace interface{} with any (Go 1.18+) - Replace reflect.TypeOf((*T)(nil)).Elem() with reflect.TypeFor[T]() (Go 1.22+) - Replace reflect.Ptr with reflect.Pointer - Replace manual loops with slices.Contains and maps.Copy - Replace C-style for loops with range N syntax (Go 1.22+) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address Copilot review findings from PR #83 - Remove discarded error vars assigned to _ in logger_decorator_base_bdd_test.go - Fix indentation in config_field_tracking_test.go switch case - Fix orphaned indentation from slices.Contains refactoring in observer_test.go and tenant_service.go - Restore tagValidate and tagDesc constants in config_validation.go - Propagate fsnotify.Watcher.Close() error in configwatcher Stop method - Benchmark GetService through registry lock instead of pre-copied map - Add performance note to GetTypedService doc comment Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: eliminate currentModule race in parallel init Add RegisterServiceForModule() to EnhancedServiceRegistry that takes the module directly instead of relying on the shared currentModule field. initModule() now uses this method, making parallel init safe from the SetCurrentModule/ClearCurrentModule race where concurrent goroutines could associate services with the wrong module. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ci: upgrade Go version from 1.25 to 1.26 in all workflows All CI workflows now use Go ^1.26 to match go.mod requirements. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: upgrade Go 1.25 to 1.26 in all sub-module and example go.mod files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address Go 1.26 lint issues in reverseproxy, scheduler, eventbus - reverseproxy: replace deprecated proxy.Director (SA1019) with proxy.Rewrite using httputil.ProxyRequest API, including all proxy copy sites - scheduler: add G118 nolint for context.WithCancel where cancel is stored in struct field and called in Stop() - eventbus: add G118 nolint for context.WithCancel in memory, nats, redis Start() methods where cancel is stored and called in Stop() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve core lint issues (gofmt, errcheck, wrapcheck) - Fix gofmt formatting in application.go, config_field_tracking.go, health_service.go - Wrap external errors in configwatcher (fsnotify), secret_resolver, service_typed - Check errcheck on stopWatching() call in configwatcher Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve go vet false positives on Debug interface in feeders/yaml.go Go 1.26's stricter vet treats Debug(msg string, args ...any) interface methods as printf-like, flagging structured logging calls. Added debugLog helper that formats key-value pairs into the message string and routes through a func(string) wrapper to avoid the vet's printf detection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct gofmt formatting and nolint directive syntax - Fix gofmt alignment in feeders/yaml.go, phase.go, tenant_service.go - Change //nolint:G118 to //nolint:gosec (golangci-lint uses linter names) - Add G118 suppression for durable_memory.go and kafka.go - Fix contextcheck nolint in scheduler.go - Suppress SA1019 on proxy.Director = nil (required when using Rewrite) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address Copilot review — mutex safety, slice copy, infinite loop guard - Restructure registerServiceLocked to isolate the critical section in registerServiceInner, ensuring the lock is always released even on panic - GetServicesByModule now returns a copy of the internal slice for thread safety - computeDepthLevels breaks out of the loop when no progress is made instead of spinning indefinitely on unresolvable dependencies - configwatcher uses clear(changedPaths) instead of reassigning the map variable in the AfterFunc closure Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add gosec G118 suppression for kinesis and custom_memory eventbus Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: pass full dependency graph to computeDepthLevels for accurate parallel init computeDepthLevels previously only considered DependencyAware.Dependencies() and dependencyHints, missing implicit service-based dependencies resolved by resolveDependencies. Now resolveDependencies returns the complete graph and computeDepthLevels uses it directly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: reverseproxy proxy copies must preserve Director for backwards compatibility Go 1.26 enforces that ReverseProxy has exactly one of Director or Rewrite set. Proxy copies in createBackendProxyHandler were only copying Rewrite, causing failures when the source proxy used Director (e.g. from NewSingleHostReverseProxy). Now copies preserve both fields. Also switched createReverseProxyForBackend to use bare &httputil.ReverseProxy{} with Rewrite instead of NewSingleHostReverseProxy + Director=nil. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve CI failures in examples, configwatcher, contract check, and differ - Examples (verbose-debug, instance-aware-db): migrate database import from v1 to v2 path, fix go.mod require/replace directives - examples-ci.yml: fix grep pattern to match `../..` without trailing slash - configwatcher: add go.mod/go.sum for standalone module CI - contract-check.yml: use commit SHA instead of branch name for PR checkout - contract differ: normalize `interface{}` and `any` as equivalent types to prevent false breaking-change reports during Go modernization Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address remaining review comments — races, locking, interfaces, 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> * fix: unwrap decorators for builder options, validate dependency hints - 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> * fix: use wrapped static error for dependency hint validation (err113) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7866c7c commit d0a62e8

150 files changed

Lines changed: 4842 additions & 686 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/auto-bump-modules.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
- name: Set up Go
4444
uses: actions/setup-go@v6
4545
with:
46-
go-version: '^1.25'
46+
go-version: '^1.26'
4747
check-latest: true
4848

4949
- name: Determine version

.github/workflows/bdd-matrix.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ on:
2121
workflow_dispatch:
2222

2323
env:
24-
GO_VERSION: '^1.25'
24+
GO_VERSION: '^1.26'
2525

2626
jobs:
2727
# Discover modules (reused for matrix)

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ on:
1010
branches: [ main ]
1111

1212
env:
13-
GO_VERSION: '^1.25'
13+
GO_VERSION: '^1.26'
1414

1515
jobs:
1616
test:

.github/workflows/cli-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ on:
2222
default: 'patch'
2323

2424
env:
25-
GO_VERSION: '^1.25'
25+
GO_VERSION: '^1.26'
2626

2727
permissions:
2828
contents: write

.github/workflows/contract-check.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ permissions:
1616
actions: read
1717

1818
env:
19-
GO_VERSION: '^1.25'
19+
GO_VERSION: '^1.26'
2020

2121
jobs:
2222
contract-check:
@@ -59,7 +59,7 @@ jobs:
5959
6060
- name: Checkout PR branch
6161
run: |
62-
git checkout ${{ github.head_ref }}
62+
git checkout ${{ github.event.pull_request.head.sha }}
6363
6464
- name: Extract contracts from PR branch
6565
run: |

.github/workflows/copilot-setup-steps.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
- name: Setup Go
3333
uses: actions/setup-go@v6
3434
with:
35-
go-version: '^1.25'
35+
go-version: '^1.26'
3636
cache-dependency-path: go.sum
3737

3838
# Install Go dependencies and development tools

.github/workflows/examples-ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ on:
1212
workflow_dispatch:
1313

1414
env:
15-
GO_VERSION: '^1.25'
15+
GO_VERSION: '^1.26'
1616

1717
jobs:
1818
validate-examples:
@@ -448,10 +448,10 @@ jobs:
448448
449449
echo "🔍 Verifying go.mod configuration for ${{ matrix.example }}..."
450450
451-
# Check that replace directives point to correct paths
452-
if ! grep -q "replace.*=> ../../" go.mod; then
451+
# Check that replace directives point to correct local paths
452+
if ! grep -q "replace.*=> \.\./\.\." go.mod; then
453453
echo "❌ Missing or incorrect replace directive in ${{ matrix.example }}/go.mod"
454-
echo "Expected: replace github.com/GoCodeAlone/modular => ../../"
454+
echo "Expected: replace github.com/GoCodeAlone/modular => ../.."
455455
cat go.mod
456456
exit 1
457457
fi

.github/workflows/module-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ jobs:
138138
if: steps.skipcheck.outputs.changed == 'true'
139139
uses: actions/setup-go@v6
140140
with:
141-
go-version: '^1.25'
141+
go-version: '^1.26'
142142
check-latest: true
143143

144144
- name: Build modcli

.github/workflows/modules-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ on:
2020
workflow_dispatch:
2121

2222
env:
23-
GO_VERSION: '^1.25'
23+
GO_VERSION: '^1.26'
2424

2525
jobs:
2626
# This job identifies which modules have been modified

.github/workflows/release-all.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ jobs:
172172
- name: Set up Go
173173
uses: actions/setup-go@v6
174174
with:
175-
go-version: '^1.25'
175+
go-version: '^1.26'
176176
check-latest: true
177177
- name: Build modcli
178178
run: |
@@ -275,7 +275,7 @@ jobs:
275275
- name: Set up Go
276276
uses: actions/setup-go@v6
277277
with:
278-
go-version: '^1.25'
278+
go-version: '^1.26'
279279
check-latest: true
280280
- name: Build modcli
281281
run: |
@@ -354,7 +354,7 @@ jobs:
354354
- name: Set up Go
355355
uses: actions/setup-go@v6
356356
with:
357-
go-version: '^1.25'
357+
go-version: '^1.26'
358358
check-latest: true
359359
- name: Build modcli
360360
run: |

0 commit comments

Comments
 (0)