Files
OpenFlare/docs/superpowers/plans/2026-08-28-cordis-architecture-alignment.md
T

8.1 KiB

Cordis Architecture Alignment & Refactoring Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Implement Cordis spatiotemporal composability (scoped revertible effects and reactive fiber lifecycle state machine) in backend/core, and eliminate cross-plugin direct imports in domain repositories.

Architecture:

  1. Build scoped extension proxies on core.Context that automatically attach unregister callbacks to ctx.OnDispose in LIFO order upon registration.
  2. Introduce core/fiber.go implementing the Fiber state machine (PENDING -> LOADING -> ACTIVE -> UNLOADING -> DISPOSED) with a reactive reconciler in App/Container ensuring dependency confluence.
  3. Clean up defensive boundaries in backend/plugins/domain/user by removing direct database.DB(ctx) imports in favor of contracts.DBService.

Tech Stack: Go 1.24+, GORM, Gin, Asynq, Cordis micro-kernel paradigm.

Global Constraints

  • Strictly preserve backend/pkg/util/ purity (no Gin/GORM imports).
  • Zero physically hardcoded temp directories in tests (use t.TempDir()).
  • All Go error returns and logging must adhere to project standards.
  • Follow Conventional Commits (feat(core): ..., refactor(user): ...).

Task 1: Scoped Revertible Effects for Core Extpoints

Files:

  • Create: backend/core/scoped_extpoints.go
  • Modify: backend/core/context.go
  • Modify: backend/core/extpoints/task.go
  • Modify: backend/core/extpoints/schedule.go
  • Modify: backend/core/extpoints/setting.go
  • Test: backend/core/context_test.go

Interfaces:

  • Consumes: core.Context, extpoints.RouterExtension, extpoints.TaskExtension, extpoints.ScheduleExtension, extpoints.SettingExtension, core.EventBus

  • Produces: Scoped extension methods on Context that automatically register LIFO disposers when routes, tasks, schedules, settings, and events are registered.

  • Step 1: Write the failing test for scoped extpoints automatic teardown

In backend/core/context_test.go, add test cases verifying that registering routes, tasks, schedules, settings, and event listeners on a child context automatically registers unregister callbacks, and calling childCtx.Dispose() completely rolls them back:

func TestContext_ScopedExtpoints_RevertibleEffects(t *testing.T) {
	root := NewContext(context.Background())
	child := root.Fork()

	// Register route, task, schedule, setting, event on child
	rd := child.Router().GET("/test-route", func() {})
	assert.Equal(t, 1, len(root.Router().Routes()))

	child.Events().On("test:event", func() {})
	assert.Equal(t, 1, root.Events().Listeners("test:event"))

	// Dispose child
	err := child.Dispose()
	assert.NoError(t, err)

	// All child effects should be revoked
	assert.Equal(t, 0, len(root.Router().Routes()))
	assert.Equal(t, 0, root.Events().Listeners("test:event"))
}
  • Step 2: Run test to verify it fails

Run: go test -v ./backend/core -run TestContext_ScopedExtpoints_RevertibleEffects Expected: FAIL (because current Router().GET() does not bind unregistration to child.OnDispose).

  • Step 3: Implement Scoped Extpoints and Context bindings
  1. In backend/core/extpoints/task.go, ensure Unregister(taskType string) bool exists.
  2. In backend/core/extpoints/schedule.go, ensure Unregister(name string) bool exists.
  3. In backend/core/extpoints/setting.go, ensure Unregister(key string) bool exists.
  4. In backend/core/scoped_extpoints.go (or context.go), create scoped wrappers for RouterExtension, TaskExtension, ScheduleExtension, SettingExtension and EventBus that tie registrations to ctx.OnDispose.
  • Step 4: Run tests to verify they pass

Run: go test -v ./backend/core -run TestContext_ScopedExtpoints_RevertibleEffects Expected: PASS

  • Step 5: Commit
git add backend/core/
git commit -m "feat(core): implement scoped revertible effects for context extpoints"

Task 2: Plugin Fiber State Machine and Reactive Coeffects (Confluence)

Files:

  • Create: backend/core/fiber.go
  • Create: backend/core/fiber_test.go
  • Modify: backend/core/app.go
  • Modify: backend/core/types.go
  • Modify: backend/core/container.go

Interfaces:

  • Consumes: core.Plugin, core.Context, core.Container

  • Produces: core.DependentPlugin, core.Fiber, core.FiberState, App.Reconcile()

  • Step 1: Write the failing test for Fiber state machine and out-of-order registration confluence

In backend/core/fiber_test.go:

func TestFiber_ConfluenceAndReactiveActivation(t *testing.T) {
	app := NewApp()

	// Plugin B depends on contracts.DBService, but is registered BEFORE DatabasePlugin (Plugin A)
	pluginB := &mockDependentPlugin{
		name: "plugin-b",
		deps: []reflect.Type{reflect.TypeFor[contracts.DBService]()},
	}
	pluginA := &mockDBPlugin{name: "database"}

	app.Use(pluginB, pluginA)

	err := app.Start(context.Background())
	assert.NoError(t, err)

	// Verify both plugins reached FiberActive state and B executed Apply successfully after A provided DBService
	assert.True(t, pluginB.applied)
	assert.True(t, pluginA.applied)

	_ = app.Stop()
}
  • Step 2: Run test to verify it fails

Run: go test -v ./backend/core -run TestFiber_ConfluenceAndReactiveActivation Expected: FAIL (because current app.ApplyPlugins() applies in static slice order without dependency reconciliation).

  • Step 3: Implement Fiber State Machine and Reconciler
  1. In backend/core/types.go, declare:
type DependentPlugin interface {
	Plugin
	Inject() []reflect.Type
}
  1. In backend/core/fiber.go, implement Fiber with states (FiberPending, FiberLoading, FiberActive, FiberUnloading, FiberDisposed), child scoped context, and state transition methods.
  2. In backend/core/app.go, integrate Fibers into App and implement iterative dependency reconciliation during ApplyPlugins and on dynamic Provide.
  • Step 4: Run tests to verify they pass

Run: go test -v ./backend/core Expected: ALL PASS

  • Step 5: Commit
git add backend/core/
git commit -m "feat(core): implement plugin fiber state machine and reactive dependency reconciler"

Task 3: Domain Plugin Isolation & Boundary Enforcement

Files:

  • Modify: backend/plugins/domain/user/repository.go
  • Modify: backend/plugins/domain/user/service.go
  • Modify: backend/plugins/domain/user/handlers.go
  • Modify: backend/plugins/domain/user/plugin.go
  • Test: backend/plugins/domain/user/plugin_test.go

Interfaces:

  • Consumes: contracts.DBService via core.Inject / ctx.DB()

  • Produces: Decoupled User repository without direct Wavelet/plugins/infra/database imports.

  • Step 1: Write/update test verifying User repository works with injected DBService

In backend/plugins/domain/user/plugin_test.go, test user CRUD operations resolving contracts.DBService through Context.

  • Step 2: Run test to verify current state

Run: go test -v ./backend/plugins/domain/user/...

  • Step 3: Refactor user repository to eliminate direct plugins/infra/database imports

In backend/plugins/domain/user/repository.go:

  • Remove import "Wavelet/plugins/infra/database".

  • Obtain *gorm.DB via ctx (e.g. from context using contracts.DBService or context value).

  • Step 4: Run tests to verify they pass

Run: go test -v ./backend/plugins/domain/user/... Expected: PASS

  • Step 5: Commit
git add backend/plugins/domain/user/
git commit -m "refactor(user): decouple repository from direct database infra import"

Task 4: Full Suite Verification & Quality Gate

Files:

  • Entire repository

  • Step 1: Run all backend tests

Run: cd backend && go test -v ./... Expected: ALL PASS

  • Step 2: Run code-check and format

Run: make code-check && make format Expected: 0 lint errors, clean formatting.

  • Step 3: Commit any formatting or lint fixes
git commit -am "chore: format and verify code quality"