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
- Variables
- type AcceptanceCriterion
- type Actor
- type Claim
- type Clock
- type DependencyError
- type DuplicateError
- type EngineConfig
- type Epic
- type EpicInput
- type Event
- type Failpoint
- type GuardError
- type HeartbeatResult
- type NotFoundError
- type Option
- func WithAsyncSpawn() Option
- func WithDriveQueue(ch chan<- string) Option
- func WithFailpoint(fp Failpoint) Option
- func WithPRDriftSource(src PRDriftSource) Option
- func WithPendingInputSource(src PendingInputSource) Option
- func WithWorkerAborter(ab WorkerAborter) Option
- func WithWorkerSpawner(sp WorkerSpawner) Option
- type PRDriftSource
- type PRState
- type PendingInputSource
- type PendingInputState
- type Project
- type ProjectInput
- type Queryer
- type Reconciler
- type Service
- func (s *Service) AddDependency(ctx context.Context, actor Actor, ...) error
- func (s *Service) ArchiveProject(ctx context.Context, actor Actor, projectID, requestID string) (*Project, error)
- func (s *Service) CancelEpic(ctx context.Context, actor Actor, ...) (*Epic, error)
- func (s *Service) CancelTask(ctx context.Context, actor Actor, ...) (*Task, error)
- func (s *Service) ClaimNext(ctx context.Context, workerID, workspaceID string) (*Claim, error)
- func (s *Service) Config() EngineConfig
- func (s *Service) CreateEpic(ctx context.Context, actor Actor, projectID, shortIDOrID string, in EpicInput, ...) (*Epic, error)
- func (s *Service) CreateProject(ctx context.Context, actor Actor, in ProjectInput, requestID string) (*Project, error)
- func (s *Service) CreateTask(ctx context.Context, actor Actor, epicID, shortIDOrID string, in TaskInput, ...) (*Task, error)
- func (s *Service) DriveProject(ctx context.Context, projectID string) (int, error)
- func (s *Service) EpicActivate(ctx context.Context, actor Actor, epicID, shortIDOrID, requestID string) (*Epic, error)
- func (s *Service) EpicArchive(ctx context.Context, actor Actor, epicID, shortIDOrID, requestID string) (*Epic, error)
- func (s *Service) EpicPause(ctx context.Context, actor Actor, epicID, shortIDOrID, requestID string) (*Epic, error)
- func (s *Service) EpicUnarchive(ctx context.Context, actor Actor, epicID, shortIDOrID, requestID string) (*Epic, error)
- func (s *Service) GetEpic(ctx context.Context, epicID, shortIDOrID string) (*Epic, error)
- func (s *Service) GetProject(ctx context.Context, projectID, shortIDOrID string) (*Project, error)
- func (s *Service) GetTask(ctx context.Context, taskID, shortIDOrID string) (*Task, error)
- func (s *Service) GetTaskInWorkspace(ctx context.Context, workspaceID, taskID, shortIDOrID string) (*Task, error)
- func (s *Service) Heartbeat(ctx context.Context, actor Actor, taskID, shortIDOrID string, ...) (HeartbeatResult, error)
- func (s *Service) ListProjects(ctx context.Context, userID string) ([]Project, error)
- func (s *Service) ListTasks(ctx context.Context, projectID string, stage string) ([]Task, error)
- func (s *Service) MoveStage(ctx context.Context, actor Actor, ...) (*Task, error)
- func (s *Service) RemoveDependency(ctx context.Context, actor Actor, ...) error
- func (s *Service) ReportThrottle(ctx context.Context, actor Actor, taskID, shortIDOrID string, ...) (*Task, error)
- func (s *Service) TaskStart(ctx context.Context, actor Actor, taskID, shortIDOrID, requestID string) (*Claim, error)
- func (s *Service) UnarchiveProject(ctx context.Context, actor Actor, projectID, requestID string) (*Project, error)
- func (s *Service) UpdateEpic(ctx context.Context, actor Actor, epicID, shortIDOrID string, in EpicInput, ...) (*Epic, error)
- func (s *Service) UpdateProject(ctx context.Context, actor Actor, projectID, shortIDOrID string, ...) (*Project, error)
- func (s *Service) UpdateTask(ctx context.Context, actor Actor, taskID, shortIDOrID string, in TaskInput, ...) (*Task, error)
- type Store
- type SweepResult
- type Task
- type TaskInput
- type Transition
- type WorkerAborter
- type WorkerSpawner
Constants ¶
const ( ActorUser = "user" ActorSystem = "system" )
Actor kinds.
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.
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).
const ( PendingOriginWorker = "worker" PendingOriginEscalated = "escalated" PendingOriginDependencyAbandoned = "dependency_abandoned" PendingOriginDriftReview = "drift_review" )
PendingInput origins (§4.1: four origins with distinct exits).
const ( PriorityP0 = "P0" PriorityP1 = "P1" PriorityP2 = "P2" PriorityP3 = "P3" )
Priorities (P0-P3, default P2).
const ( ProjectActive = "active" ProjectArchived = "archived" EpicDraft = "draft" EpicActive = "active" EpicPaused = "paused" EpicCompleted = "completed" EpicArchived = "archived" )
Project / epic lifecycle statuses.
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.
const ( PrefixProject = "prj-" PrefixEpic = "epc-" PrefixTask = "tsk-" )
Short-ID prefixes (closed set; no sty- — stories do not exist, ruling R1).
Variables ¶
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 ¶
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 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 ¶
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 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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
PRDriftSource resolves pr:N anchors for RC2 (v1: the platform's git host). Nil source disables RC2 drift checks (dark-run).
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 ¶
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 ¶
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 ¶
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) GetProject ¶
GetProject resolves a project.
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 ¶
ListProjects lists the owner's boards (user-level, ruling R7).
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).
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 ¶
WorkerAborter aborts a worker session whose claim was revoked (cancel). Rides the existing interrupt seam in production; no-op by default.
type WorkerSpawner ¶
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.