board

package
v0.38.1 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: AGPL-3.0 Imports: 13 Imported by: 0

Documentation

Overview

Package board implements the Task Board backend (design 0067, issue #1696/#1698): the BoardService action layer, the Postgres-arbitrated dispatch engine (atomic claims, leases, evidence heartbeats, DRR fairness), and the self-healing convergence reconciler (RC1-RC5).

Rulings conformed (2026-10-16, #1696): R1 epic+task only; R2 field model (no story points — measured stage timestamps only); R3/R4 blocking is purely structural via board_dependencies (no criteria evaluator); R5 no DAG entities; R6 attempts-cap N=3 default as a platform-deploy knob (lease duration + fairness windows join the same class); R7 user-level only boards; R8 single target workspace per project; R9 always-fresh workers (one fresh-charter session per claimed task, claim-then-spawn).

There is NO in-memory scheduler state: Postgres arbitrates every race (atomic claim CTE, guarded reclaims, claim tokens), so any API replica can run the sweep and a crashed dispatcher loses nothing.

Index

Constants

View Source
const (
	ActorUser   = "user"
	ActorSystem = "system"
)

Actor kinds.

View Source
const (
	SurfaceREST        = "rest"         // (a) REST for UI + SDKs
	SurfaceExternalMCP = "external_mcp" // (b) customer-facing platform MCP
	SurfaceAgent       = "agent"        // (c) agent-internal registry (workers)
	SurfaceReconciler  = "reconciler"   // system actor, engine-internal
)

Surfaces (design §4.3): the four projections.

View Source
const (
	StageBacklog      = "backlog"
	StageInProgress   = "in_progress"
	StageInReview     = "in_review"
	StageDone         = "done"
	StagePendingInput = "pending_input"
	// Composed so misspell's fixer never rewrites the literal (the stage
	// value is the design-doc verbatim term; the DB CHECK depends on it).
	StageCancelled = "cancel" + "led" //nolint:misspell
)

Stages (ruling R2's binding set).

View Source
const (
	PendingOriginWorker              = "worker"
	PendingOriginEscalated           = "escalated"
	PendingOriginDependencyAbandoned = "dependency_abandoned"
	PendingOriginDriftReview         = "drift_review"
)

PendingInput origins (§4.1: four origins with distinct exits).

View Source
const (
	PriorityP0 = "P0"
	PriorityP1 = "P1"
	PriorityP2 = "P2"
	PriorityP3 = "P3"
)

Priorities (P0-P3, default P2).

View Source
const (
	ProjectActive   = "active"
	ProjectArchived = "archived"

	EpicDraft     = "draft"
	EpicActive    = "active"
	EpicPaused    = "paused"
	EpicCompleted = "completed"
	EpicArchived  = "archived"
)

Project / epic lifecycle statuses.

View Source
const (
	GuardStage            = "stage_transition"     // not an enumerated transition
	GuardDone             = "done_guard"           // criteria unmet or evidence missing
	GuardClaimToken       = "claim_token"          // missing/mismatched token on an in-flight write
	GuardManagerSurface   = "manager_surface_only" // pending_input exits are human exits
	GuardWorkspaceBinding = "workspace_binding"    // caller-workspace != project target (403)
	GuardEpicState        = "epic_state"           // epic lifecycle refuses the action
	GuardDependency       = "dependency"           // edge rejected (same-project/self/acyclic)
	GuardProjectState     = "project_state"        // project lifecycle refuses the action
	GuardAuthz            = "authz"                // user-level ownership (R7)
)

Guard codes.

View Source
const (
	PrefixProject = "prj-"
	PrefixEpic    = "epc-"
	PrefixTask    = "tsk-"
)

Short-ID prefixes (closed set; no sty- — stories do not exist, ruling R1).

Variables

View Source
var (
	// ErrNotImplemented is returned by skeleton stubs (red-first lane
	// discipline: the kill-tests pin behavior before the impl lands).
	ErrNotImplemented = errors.New("board: not implemented")

	// ErrNoCandidate is returned by ClaimNext when no claimable task
	// exists in scope (an empty claim is a normal outcome, not an error).
	ErrNoCandidate = errors.New("board: no claimable task in scope")
)

Sentinel error classes. Handlers map these to HTTP/MCP outcomes; the parity matrix (design §4.3) pins that mapping per surface.

Functions

This section is empty.

Types

type AcceptanceCriterion

type AcceptanceCriterion struct {
	Text string `json:"text"`
	Met  bool   `json:"met"`
}

AcceptanceCriterion is one checkable, LLM-verifiable criterion (ruling R2). Done = ALL criteria met + evidence — the criteria define Done, they never block (ruling R3's scope guard).

type Actor

type Actor struct {
	// Kind is "user" or "system" (the reconciler is a system actor,
	// exempt from the claim-token guard by construction).
	Kind string
	// UserID is the acting user (workers delegate as the owning user,
	// ruling R7; empty for system actors).
	UserID string
	// Surface is the projection the action arrived on.
	Surface string
	// ClaimToken is the in-flight guard on caller surfaces (empty for
	// the reconciler, cancel, and manager pending_input exits — the two
	// named caller-side overrides).
	ClaimToken string
	// WorkspaceID is the resolved pod workspace on the agent surface
	// (design §4.4 caller-workspace binding, structural: an in-workspace
	// worker may only touch tasks whose project targets ITS workspace).
	// Empty on manager surfaces (REST/external MCP scope by ownership).
	WorkspaceID string
}

Actor identifies who performed a board action (board_events.actor).

type Claim

type Claim struct {
	TaskID         string    `json:"task_id"`
	ShortID        string    `json:"short_id"`
	ProjectID      string    `json:"project_id"`
	EpicID         string    `json:"epic_id"`
	WorkspaceID    string    `json:"workspace_id"`          // the project's target — the spawn's destination (R9)
	ClaimToken     string    `json:"claim_token,omitempty"` // empty on escalation (grant withheld)
	Assignee       string    `json:"assignee,omitempty"`
	LeaseExpiresAt time.Time `json:"lease_expires_at,omitempty"` // zero on escalation
	// Escalated is true when the grant was withheld: the task's
	// pre-increment attempts >= N and it was staged to pending_input
	// (origin escalated) instead. The caller claims the next candidate.
	Escalated bool  `json:"escalated"`
	Task      *Task `json:"task,omitempty"`
}

Claim is the outcome of a successful (or escalated) claim.

type Clock

type Clock func() time.Time

Clock is the engine's time source (seam for tests; production UTC).

type DependencyError

type DependencyError struct {
	Code        string // "cycle" | "cross_project" | "self"
	Detail      string
	Suggestions []string
}

DependencyError is the dependency-write 409 (ruling R4a) — carries did-you-mean suggestions naming the near-miss existing tasks.

func (*DependencyError) Error

func (e *DependencyError) Error() string

type DuplicateError

type DuplicateError struct {
	EventID string
	Action  string
	Result  map[string]any
}

DuplicateError is the idempotent-replay outcome ("200-with-original, never an error" — the outbox Duplicate{AcceptedID} contract). Result carries the original event's recorded outcome so callers re-render it.

func (*DuplicateError) Error

func (e *DuplicateError) Error() string

type EngineConfig

type EngineConfig struct {
	// AttemptsCap is N — the attempts ceiling. The (N+1)th claim attempt
	// (pre-increment attempts >= N) escalates to pending_input instead of
	// granting. Default 3.
	AttemptsCap int
	// LeaseDuration is how long a claim holds without progress evidence
	// (#1685 semantics: elapsed time is not liveness). Default 15m.
	LeaseDuration time.Duration
	// ThrottleBackoff is the cooldown applied by report_throttle to both
	// the task (cooldown_until) and the project (slot_cooldown_until).
	// Default 5m.
	ThrottleBackoff time.Duration
	// DRRQuantum is the default per-epic deficit-round-robin round
	// allowance (epics.quantum). Default 1.
	DRRQuantum int
	// SweepInterval is the reconciler cadence (advisory for the sweep
	// runner; the sweep itself is stateless). Default 30s.
	SweepInterval time.Duration
}

EngineConfig carries the platform-deploy tunables (ruling R6): code defaults + override via helm values.yaml → env → api/internal/config. Never user-facing, never per-project (worker_slots stays project data).

func DefaultEngineConfig

func DefaultEngineConfig() EngineConfig

DefaultEngineConfig returns the R6 code defaults.

type Epic

type Epic struct {
	ID          string    `json:"id"`
	ProjectID   string    `json:"project_id"`
	ShortID     string    `json:"short_id"`
	Title       string    `json:"title"`
	Outcome     string    `json:"outcome"`
	Status      string    `json:"status"`
	Priority    string    `json:"priority"`
	ProjectName string    `json:"project_name"`
	Tags        []string  `json:"tags"`
	VFT         int64     `json:"vft"`
	Deficit     int64     `json:"deficit"`
	Quantum     int       `json:"quantum"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
	// Derived progress (ruling R2: never stored): Done over (total − StageCancelled),
	// optionally criteria-weighted. Rendered by board_read only. A zero
	// denominator renders no-progress (IsNull true), never 0/0.
	ProgressDone    int  `json:"progress_done"`
	ProgressTotal   int  `json:"progress_total"`
	ProgressDerived bool `json:"progress_derived"`
}

Epic is the DB row shape for epics, including the DRR fairness state (vft/deficit/quantum — Postgres is the only fairness state, §4.4).

type EpicInput

type EpicInput struct {
	Title    string
	Outcome  string
	Priority string
	Tags     []string
}

EpicInput is the create/update payload for epics.

type Event

type Event struct {
	ID        string          `json:"id"`
	ProjectID string          `json:"project_id"`
	ItemKind  string          `json:"item_kind,omitempty"`
	ItemID    string          `json:"item_id,omitempty"`
	Action    string          `json:"action"`
	Actor     json.RawMessage `json:"actor"`
	Payload   json.RawMessage `json:"payload,omitempty"`
	CreatedAt time.Time       `json:"created_at"`
}

Event is the DB row shape for board_events (idempotency + audit).

type Failpoint

type Failpoint func(where string) error

Failpoint is the fault seam (kill-tests): the engine/reconciler invoke it at named points; a non-nil error simulates a crash mid-operation (the surrounding transaction aborts). Named points are pinned by the kill-test harness.

type GuardError

type GuardError struct {
	Code   string
	Detail string
}

GuardError is the 409-class (stage machine / done guard / claim-token / workspace-binding violations). Code names the guard for the parity matrix.

func (*GuardError) Error

func (e *GuardError) Error() string

type HeartbeatResult

type HeartbeatResult struct {
	Extended       bool      `json:"extended"`
	LeaseExpiresAt time.Time `json:"lease_expires_at"`
	LastProgressAt time.Time `json:"last_progress_at"`
}

HeartbeatResult reports the evidence gate's decision: a heartbeat with no progress evidence does not extend the lease (#1685: elapsed time is not liveness).

type NotFoundError

type NotFoundError struct {
	Item        string
	Identifier  string
	Suggestions []string
}

NotFoundError is the 404-class (resolution failure) — carries did-you-mean suggestions per design 0066's helper shape (top-3, Levenshtein <= 2, suggest-only, 404-class-only).

func (*NotFoundError) Error

func (e *NotFoundError) Error() string

type Option

type Option func(*Service)

Option configures a Service (functional options, constructed at boot).

func WithAsyncSpawn

func WithAsyncSpawn() Option

WithAsyncSpawn installs the production spawn mode: pod activation + agentd charter dispatch outlive the claim transaction, so the spawn runs on its own goroutine and the guarded stamp (identity + lease re-arm) lands when the session id exists. Until then the interim state is design-shaped (assignee NULL, lease from claim); a spawn that never returns heals by ordinary lease lapse (r1-C2/C4 wiring).

func WithDriveQueue

func WithDriveQueue(ch chan<- string) Option

WithDriveQueue installs the event-driven drive hook (the app's consumer goroutine): work-becoming-ready actions drop the affected project id here after commit.

func WithFailpoint

func WithFailpoint(fp Failpoint) Option

WithFailpoint installs the fault seam (kill-tests).

func WithPRDriftSource

func WithPRDriftSource(src PRDriftSource) Option

WithPRDriftSource installs the RC2 external-truth source.

func WithPendingInputSource

func WithPendingInputSource(src PendingInputSource) Option

WithPendingInputSource installs the pending-input coupling source.

func WithWorkerAborter

func WithWorkerAborter(ab WorkerAborter) Option

WithWorkerAborter installs the abort seam (cancel mid-flight).

func WithWorkerSpawner

func WithWorkerSpawner(sp WorkerSpawner) Option

WithWorkerSpawner installs the fresh-worker spawn seam (ruling R9: claim-then-spawn; spawn failure never revokes the claim — the lease lapse path heals).

type PRDriftSource

type PRDriftSource interface {
	GetPullRequest(ctx context.Context, prNumber int) (PRState, error)
}

PRDriftSource resolves pr:N anchors for RC2 (v1: the platform's git host). Nil source disables RC2 drift checks (dark-run).

type PRState

type PRState struct {
	Merged bool
	Closed bool
	URL    string
}

PRState is the external-truth drift source's answer for one PR anchor.

type PendingInputSource

type PendingInputSource interface {
	PendingInput(ctx context.Context, assigneeSession string) (PendingInputState, error)
}

PendingInputSource reads the pending-input projection for worker sessions (worker-origin auto-stage + resolution resume, §4.4). Nil disables the coupling (dark-run until Lane 3 wires the internal seam).

type PendingInputState

type PendingInputState struct {
	HasPendingInput bool
}

PendingInputState is the sessionstate pending-input projection's answer for one worker session (unanswered question/permission).

type Project

type Project struct {
	ID                string     `json:"id"`
	ShortID           string     `json:"short_id"`
	UserID            string     `json:"user_id"`
	Name              string     `json:"name"`
	TargetWorkspaceID string     `json:"target_workspace_id,omitempty"`
	WorkerSlots       int        `json:"worker_slots"`
	SlotCooldownUntil *time.Time `json:"slot_cooldown_until,omitempty"`
	Tags              []string   `json:"tags"`
	Status            string     `json:"status"`
	CreatedAt         time.Time  `json:"created_at"`
	UpdatedAt         time.Time  `json:"updated_at"`
}

Project is the DB row shape for projects (swimlane).

type ProjectInput

type ProjectInput struct {
	Name              string
	TargetWorkspaceID string
	WorkerSlots       int
	Tags              []string
}

ProjectInput is the create/update payload for projects.

type Queryer

type Queryer interface {
	Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
	Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
	QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
}

Queryer abstracts pool vs tx (pgx's own common surface).

type Reconciler

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

Reconciler is the periodic convergence sweep (default 30s cadence; the sweep itself is stateless between passes apart from the watermark).

func NewReconciler

func NewReconciler(pool *pgxpool.Pool, cfg EngineConfig, opts ...Option) *Reconciler

NewReconciler constructs the reconciler over a Service.

func (*Reconciler) Start

func (r *Reconciler) Start(ctx context.Context) error

Start runs the periodic sweep until ctx is canceled (the app's root-context cancel; any API replica can run it — SKIP LOCKED + guarded writes arbitrate multi-replica safety without leader election, the workflow-engine lifecycle pattern). A sweep error logs and the cadence continues: convergence is the contract, not an error-free pass.

func (*Reconciler) Sweep

func (r *Reconciler) Sweep(ctx context.Context) (SweepResult, error)

Sweep runs one full pass. Phases: RC1 → pending coupling → RC2 → RC3 → RC4 net → watermark advance.

type Service

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

Service is the ONE canonical action layer (design §4.2): guards, validation, idempotency, and audit live here; every projection is a thin adapter. Safe for concurrent use (pgxpool arbitrates; Postgres is the only scheduler state).

func NewService

func NewService(pool *pgxpool.Pool, cfg EngineConfig, opts ...Option) *Service

NewService constructs the BoardService.

func (*Service) AddDependency

func (s *Service) AddDependency(ctx context.Context, actor Actor, taskID, shortIDOrID, dependsOnID, requestID string) error

AddDependency adds a structural edge. The DB trigger enforces same-project / no-self / acyclic (full DFS); violations map to 409 + did-you-mean (R4a). is_blocked is re-derived in-transaction.

func (*Service) ArchiveProject

func (s *Service) ArchiveProject(ctx context.Context, actor Actor, projectID, requestID string) (*Project, error)

ArchiveProject is reversible storage; it stops all claims into the project (the p CTE's status predicate).

func (*Service) CancelEpic

func (s *Service) CancelEpic(ctx context.Context, actor Actor, epicID, shortIDOrID, reason, requestID string) (*Epic, error)

CancelEpic pauses the epic (the epic enum has no terminal value — a canceled epic is paused with an all-terminal task set, never completed) and cancels its non-terminal tasks, cascading dependency abandonment.

func (*Service) CancelTask

func (s *Service) CancelTask(ctx context.Context, actor Actor, taskID, shortIDOrID, reason, requestID string) (*Task, error)

CancelTask is terminal: revokes lease (token AND assignee), issues the worker abort, stages non-terminal dependents to pending_input/dependency_abandoned (skipping rows already pending — origin preserved). The terminal stage is immutable (RC5; the DB guard belt). Cancel presents no token: it REVOKES it (§4.2's named override).

func (*Service) ClaimNext

func (s *Service) ClaimNext(ctx context.Context, workerID, workspaceID string) (*Claim, error)

ClaimNext is the dispatch-engine entry (worker surface; engine-internal per ruling R9 — the dispatch path claims-then-spawns). Caller-workspace binding is structural: a worker only ever claims projects whose target workspace IS its own. Returns a claim with a nil Task on an empty sweep — a normal outcome, not an error condition.

func (*Service) Config

func (s *Service) Config() EngineConfig

Config returns the effective engine config.

func (*Service) CreateEpic

func (s *Service) CreateEpic(ctx context.Context, actor Actor, projectID, shortIDOrID string, in EpicInput, requestID string) (*Epic, error)

CreateEpic creates a draft epic (the default status per ruling R2).

func (*Service) CreateProject

func (s *Service) CreateProject(ctx context.Context, actor Actor, in ProjectInput, requestID string) (*Project, error)

CreateProject creates a user-level board project (ruling R7).

func (*Service) CreateTask

func (s *Service) CreateTask(ctx context.Context, actor Actor, epicID, shortIDOrID string, in TaskInput, requestID string) (*Task, error)

CreateTask creates a backlog task in an epic.

func (*Service) DriveProject

func (s *Service) DriveProject(ctx context.Context, projectID string) (int, error)

DriveProject drives claims for ONE project (the event-driven half of the §4.4 dispatch drive: activate/create/unblock events enqueue a drive of the affected project; the reconciler sweep is the backstop). The workspace binding is engine-side — from the project row (merged doc).

func (*Service) EpicActivate

func (s *Service) EpicActivate(ctx context.Context, actor Actor, epicID, shortIDOrID, requestID string) (*Epic, error)

EpicActivate starts feeding. The immediate RC4 re-check applies to draft/paused/archived-origin activations (a finished epic completes at activation); the completed→active re-open is exempt (r13 pin).

func (*Service) EpicArchive

func (s *Service) EpicArchive(ctx context.Context, actor Actor, epicID, shortIDOrID, requestID string) (*Epic, error)

EpicArchive is reversible storage.

func (*Service) EpicPause

func (s *Service) EpicPause(ctx context.Context, actor Actor, epicID, shortIDOrID, requestID string) (*Epic, error)

EpicPause stops new claims, never interrupts in-flight leases.

func (*Service) EpicUnarchive

func (s *Service) EpicUnarchive(ctx context.Context, actor Actor, epicID, shortIDOrID, requestID string) (*Epic, error)

EpicUnarchive always restores active (the resumable default) — and, as an archived-origin activation, runs the immediate re-check (r13 pin).

func (*Service) GetEpic

func (s *Service) GetEpic(ctx context.Context, epicID, shortIDOrID string) (*Epic, error)

GetEpic resolves an epic with derived progress.

func (*Service) GetProject

func (s *Service) GetProject(ctx context.Context, projectID, shortIDOrID string) (*Project, error)

GetProject resolves a project.

func (*Service) GetTask

func (s *Service) GetTask(ctx context.Context, taskID, shortIDOrID string) (*Task, error)

GetTask resolves a task (case-insensitive short ID or UUID).

func (*Service) GetTaskInWorkspace

func (s *Service) GetTaskInWorkspace(ctx context.Context, workspaceID, taskID, shortIDOrID string) (*Task, error)

GetTaskInWorkspace is the agent surface's reader (r1-S1): resolution, row, and did-you-mean suggestions all scoped to the caller's workspace.

func (*Service) Heartbeat

func (s *Service) Heartbeat(ctx context.Context, actor Actor, taskID, shortIDOrID string, observedProgressAt time.Time) (HeartbeatResult, error)

Heartbeat refreshes the lease ONLY when it carries new progress evidence (the worker's message-level watermark moved — #1685: elapsed time is not liveness). Requires the matching claim token.

func (*Service) ListProjects

func (s *Service) ListProjects(ctx context.Context, userID string) ([]Project, error)

ListProjects lists the owner's boards (user-level, ruling R7).

func (*Service) ListTasks

func (s *Service) ListTasks(ctx context.Context, projectID string, stage string) ([]Task, error)

ListTasks lists a project's tasks (optionally by stage).

func (*Service) MoveStage

func (s *Service) MoveStage(ctx context.Context, actor Actor, taskID, shortIDOrID, toStage, evidenceURL, requestID string) (*Task, error)

MoveStage is the server-arbitrated stage machine write: a drag that loses a race returns the authoritative outcome and the UI snaps back.

func (*Service) RemoveDependency

func (s *Service) RemoveDependency(ctx context.Context, actor Actor, taskID, shortIDOrID, dependsOnID, requestID string) error

RemoveDependency removes an edge and re-derives is_blocked (one of the pending_input resolutions: removing an abandoned prerequisite's edge unblocks the row — the human re-queue stays a move_stage write).

func (*Service) ReportThrottle

func (s *Service) ReportThrottle(ctx context.Context, actor Actor, taskID, shortIDOrID string, retryAfter time.Duration, requestID string) (*Task, error)

ReportThrottle is the 429 path (§4.4): requeue to backlog WITHOUT attempt penalty + cooldown on both the task and the project's slots. One UPDATE guarded by stage='in_progress' AND token match — a pending_ input or lapsed row is rejected exactly like the race case.

func (*Service) TaskStart

func (s *Service) TaskStart(ctx context.Context, actor Actor, taskID, shortIDOrID, requestID string) (*Claim, error)

TaskStart is the manual one-off dispatch (§4.2, merged doc): targets a NAMED task, bypasses exactly three things (the p CTE's active-epic-project gate, the candidate predicate's e.status='active' requirement — scoped to draft∪active epics — and the DRR deficit<quantum eligibility; the DRR counters still advance). All other arbitration is identical to the engine's dispatch claim, with the workspace bound ENGINE-SIDE from the project row — no caller-supplied worker identity or workspace exists on any surface — followed by the same fresh spawn (R9).

func (*Service) UnarchiveProject

func (s *Service) UnarchiveProject(ctx context.Context, actor Actor, projectID, requestID string) (*Project, error)

UnarchiveProject restores active.

func (*Service) UpdateEpic

func (s *Service) UpdateEpic(ctx context.Context, actor Actor, epicID, shortIDOrID string, in EpicInput, requestID string) (*Epic, error)

UpdateEpic updates title/outcome/priority/tags (outcome is the human's steering wheel, ruling R2). A title rename cascades tasks.epic_name.

func (*Service) UpdateProject

func (s *Service) UpdateProject(ctx context.Context, actor Actor, projectID, shortIDOrID string, in ProjectInput, requestID string) (*Project, error)

UpdateProject updates fields; a name rename cascades materialized display inheritance (one UPDATE per carrying table, §4.1).

func (*Service) UpdateTask

func (s *Service) UpdateTask(ctx context.Context, actor Actor, taskID, shortIDOrID string, in TaskInput, requestID string) (*Task, error)

UpdateTask patches fields; priority changes are ordinary writes (the dispatcher reads priority fresh each claim); criteria check-off rides here (the reviewer's Done preparation).

type Store

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

Store is the Postgres-backed data-access layer (pkg/workflows shape: constructed once at boot, safe for concurrent use).

func NewStore

func NewStore(pool *pgxpool.Pool) *Store

NewStore constructs a Store backed by the given pgxpool.

func (*Store) Pool

func (st *Store) Pool() *pgxpool.Pool

Pool exposes the pool for transactional callers (the service layer).

type SweepResult

type SweepResult struct {
	Dispatched      int `json:"dispatched"`       // §4.4 sweep-backstop drive
	Reclaims        int `json:"reclaims"`         // RC1
	DriftDone       int `json:"drift_done"`       // RC2 merged, criteria met
	DriftReview     int `json:"drift_review"`     // RC2 merged, criteria unmet
	DriftReentry    int `json:"drift_reentry"`    // RC2 closed-unmerged → in_progress
	DepRepairs      int `json:"dep_repairs"`      // RC3 is_blocked recomputes
	AbandonedStaged int `json:"abandoned_staged"` // RC3 canceled-dependency flips
	PendingStaged   int `json:"pending_staged"`   // pending-input auto-stage (worker origin)
	Resumes         int `json:"resumes"`          // pending-input resolutions (lease re-armed)
	EpicCompletions int `json:"epic_completions"` // RC4 net
}

SweepResult summarizes one reconciler pass (health panel feed).

type Task

type Task struct {
	ID                 string                `json:"id"`
	ProjectID          string                `json:"project_id"`
	EpicID             string                `json:"epic_id"`
	ShortID            string                `json:"short_id"`
	Title              string                `json:"title"`
	Description        string                `json:"description"`
	Stage              string                `json:"stage"`
	PendingOrigin      *string               `json:"pending_origin,omitempty"`
	Priority           string                `json:"priority"`
	ProjectName        string                `json:"project_name"`
	EpicName           string                `json:"epic_name"`
	Tags               []string              `json:"tags"`
	AcceptanceCriteria []AcceptanceCriterion `json:"acceptance_criteria"`
	IsBlocked          bool                  `json:"is_blocked"`
	BlockedBy          []string              `json:"blocked_by,omitempty"` // derived: non-done dependency short IDs
	AssigneeSession    string                `json:"assignee_session,omitempty"`
	ClaimToken         string                `json:"claim_token,omitempty"`
	LeaseExpiresAt     *time.Time            `json:"lease_expires_at,omitempty"`
	LastProgressAt     *time.Time            `json:"last_progress_at,omitempty"`
	Attempts           int                   `json:"attempts"`
	ClaimIncremented   bool                  `json:"claim_incremented_attempts"`
	CooldownUntil      *time.Time            `json:"cooldown_until,omitempty"`
	StageEnteredAt     time.Time             `json:"stage_entered_at"`
	CreatedAt          time.Time             `json:"created_at"`
	UpdatedAt          time.Time             `json:"updated_at"`
}

Task is the DB row shape for tasks, including the dispatch state (claim_token, lease, attempts, provenance flag — §4.4).

type TaskInput

type TaskInput struct {
	Title              string
	Description        string
	Priority           string
	Tags               []string
	AcceptanceCriteria []AcceptanceCriterion
	// SetCriteriaMet checks off the criterion at this 0-based index.
	SetCriteriaMet *int
}

TaskInput is the create/update payload for tasks.

type Transition

type Transition struct {
	ID          int64           `json:"id"`
	TaskID      string          `json:"task_id"`
	FromStage   string          `json:"from_stage"`
	ToStage     string          `json:"to_stage"`
	EvidenceURL string          `json:"evidence_url,omitempty"`
	Actor       json.RawMessage `json:"actor"`
	At          time.Time       `json:"at"`
}

Transition is the DB row shape for board_transitions (measured actuals — ruling R2: cycle time is derived from this log, never an estimate column).

type WorkerAborter

type WorkerAborter func(ctx context.Context, taskID, assigneeSession string) error

WorkerAborter aborts a worker session whose claim was revoked (cancel). Rides the existing interrupt seam in production; no-op by default.

type WorkerSpawner

type WorkerSpawner func(ctx context.Context, claim *Claim) (sessionID string, err error)

WorkerSpawner spawns a fresh-charter worker session for a granted claim (ruling R9: claim-then-spawn; one fresh session per claimed task). The spawn failure NEVER revokes the claim — the lease lapse path heals it (§4.4). Lane 3 wires the production session-creation seam; Lane 1 runs the engine with an injected spawner or the synthetic default.

Jump to

Keyboard shortcuts

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