application

package
v0.12.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 26, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

README

application

Orchestrates domain work via a two-phase execution model.

How it works

Scheduler
  ├── Phase 1: Preflights (one-shot, all must pass)
  └── Phase 2: Workers (long-running interval tasks)

Scheduler runs phases sequentially. If any preflight fails, workers never start.

Planner owns the per-repository concept. It takes domain specs and multiplies them by the configured repositories, producing ready-to-schedule units with closures that capture tenant context (owner, name, cached repoID).

Planner also centralizes retry configuration — domain only declares which errors are transient ([]error), Planner decides how to retry them (max retries, backoff).

Key types

Type File Role
Preflight contracts.go Interface for one-shot tasks
Worker contracts.go Interface for interval tasks
Planner planner.go Builds per-repo task units from domain specs
Scheduler schedule.go Executes preflight → worker phases
NewPreflights registry.go Registers all preflight implementations
NewWorkers registry.go Registers all worker implementations

Adding a new task

  1. Create a domain type that implements Preflight or Worker (return a PreflightSpec or WorkerSpec from TaskSpec()).
  2. Register it in registry.go — add as a parameter to NewPreflights() or NewWorkers().
  3. Add the constructor to internal/bindings.go Wire set.
  4. Run task generate.

Task naming

Names are formatted by Scheduler as {phase}:{resource}:{owner}/{name}:

preflight:repository:thumbrise/autosolve
worker:issues:thumbrise/autosolve
worker:issues:thumbrise/otelext

Extension points: tenants

The current tenant is RepoTenant (owner + name + repoID) — all work revolves around repositories. But this is not a dogma.

RepoTenant is defined in domain/spec/tenants/ and passed to domain work functions via Planner closures. If a future task needs a different unit of work (e.g. an org-level tenant, a user-level tenant), the path is:

  1. Define a new tenant type in domain/spec/tenants/.
  2. Define a new spec type with Work func(ctx, YourTenant) error.
  3. Add a new method to Planner that iterates the appropriate config and builds units.
  4. Scheduler calls the new Planner method in the right phase.

Planner is the only place that knows how to map config → tenants → closures. Domain types never know how many tenants exist or where they come from.

Future: module system

The current architecture has an implicit pattern: a tenant acts as a gravity point for a cluster of related components — lifecycle phases (preflights, workers), domain logic, and DAL repositories. Today this cluster is RepoTenant with its validator, issue parser, and repository/issue stores.

This naturally evolves into a module system where each tenant type defines a self-contained module:

modules/
  repo/                        ← "repository" module
    tenant.go                  ← RepoTenant
    preflights.go              ← validator, migrations, etc.
    workers.go                 ← issue poller, comment poller, etc.
    dal/                       ← module-specific repositories
    module.go                  ← Module.Register(app)

A module would implement a single interface:

type Module interface {
    Preflights() []Preflight
    Workers() []Worker
}

The application loads modules explicitly:

modules := []Module{repo.New(), analytics.New(), notifications.New()}

This is the same pattern as Linux loadable modules, PHP extensions (ext-curl, ext-pdo), or Git subcommands — each module brings its own lifecycle, domain logic, and storage, and the application provides the execution framework.

Not implemented yet. The current flat structure works for the current scale. When a second tenant type appears, that's the signal to extract the module system.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Planner added in v0.12.0

type Planner struct {
	// contains filtered or unexported fields
}

Planner builds per-repository task units from domain specs. It owns the per-repo concept: each repository in config produces a set of preflight and worker units.

func NewPlanner added in v0.12.0

func NewPlanner(
	cfg *config.Github,
	preflights []Preflight,
	workers []Worker,
	repoRepo *repositories.RepositoryRepository,
) *Planner

func (*Planner) Preflights added in v0.12.0

func (p *Planner) Preflights() []PreflightUnit

Preflights returns one-shot units for all repositories × all preflight specs.

func (*Planner) Workers added in v0.12.0

func (p *Planner) Workers() []WorkerUnit

Workers returns interval units for all repositories × all worker specs. Each unit caches the repository ID on first invocation to avoid repeated lookups.

type Preflight added in v0.12.0

type Preflight interface {
	TaskSpec() spec.PreflightSpec
}

Preflight is a one-shot task that must complete before workers start. Implementations return a PreflightSpec describing the work to be done per tenant.

Task naming convention: preflight:{Resource}:{owner}/{name} Examples:

  • preflight:repository:thumbrise/autosolve

func NewPreflights added in v0.12.0

func NewPreflights(
	repoValidator *repository.Validator,
) []Preflight

NewPreflights registers all preflight tasks. Add new preflights here when extending the system.

type PreflightUnit added in v0.12.0

type PreflightUnit struct {
	Resource string
	Repo     config.Repository
	Rules    []longrun.TransientRule
	Work     longrun.WorkFunc
}

PreflightUnit is a ready-to-schedule one-shot task produced by Planner.

type Scheduler

type Scheduler struct {
	// contains filtered or unexported fields
}

Scheduler orchestrates execution in two phases:

  1. Preflights — one-shot tasks, all must pass before workers start.
  2. Workers — long-running interval tasks.

Scheduler is generic — it doesn't know about repositories, GitHub, or issues. It only knows phases and task units provided by Planner.

func NewScheduler

func NewScheduler(planner *Planner, logger *slog.Logger) *Scheduler

func (*Scheduler) Run

func (s *Scheduler) Run(ctx context.Context) error

type Worker added in v0.12.0

type Worker interface {
	TaskSpec() spec.WorkerSpec
}

Worker is a long-running interval task. Implementations return a WorkerSpec describing the work to be done per tenant.

Task naming convention: worker:{Resource}:{owner}/{name} Examples:

  • worker:issues:thumbrise/autosolve
  • worker:comments:thumbrise/otelext

func NewWorkers added in v0.12.0

func NewWorkers(
	issueParser *issue.Parser,
) []Worker

NewWorkers registers all worker tasks. Add new workers here when extending the system.

type WorkerUnit added in v0.12.0

type WorkerUnit struct {
	Resource string
	Repo     config.Repository
	Interval time.Duration
	Rules    []longrun.TransientRule
	Work     longrun.WorkFunc
}

WorkerUnit is a ready-to-schedule interval task produced by Planner.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL