Documentation
¶
Overview ¶
Package flowstore persists task-centric Flow records beside the agent-session store.
Agents persist Flow changes through the `approach flow` CLI (cmd/approach/flow.go), never by editing approach.db — direct edits bypass the store's transactions, validation, and phase-ID normalization.
SQLite schema versions: v3 adds prepared_at + receipt trigger; v4 requires boolean `done` in epic progression JSON; v5 fences the progression-claim marker; v6 adds the protected flows.preparation_nonce generation projection; v7 fences recovered-launch capabilities from older writers; v8 projects and fences the durable phase-untracked owner identity.
Index ¶
- Constants
- Variables
- func AgentSettablePhaseStatuses() []string
- func AllowedNextPhaseStatuses(current string) []string
- func BeadFlowSlotOccupied(record FlowRecord) bool
- func DatabaseSchemaVersion() int
- func DefaultRoot() (string, error)
- func DeriveStatus(record FlowRecord) string
- func EffectiveAutoMerge(record FlowRecord, global bool) bool
- func FlowClosed(record FlowRecord) bool
- func HasIssueTarget(issue Issue) bool
- func HasPRTarget(pr PullRequest) bool
- func IsAutoLaunchOutdated(err error) bool
- func IsBeadFlowActive(err error) bool
- func IsBeadFlowRefusal(err error) bool
- func IsBeadFlowUnreadable(err error) bool
- func IsFlowClosed(err error) bool
- func IsMigrationBlockedByOwners(err error) bool
- func IsNotFound(err error) bool
- func IsPreparationIncomplete(err error) bool
- func IsPreparationReservation(err error) bool
- func IsPreparationStale(err error) bool
- func IsPreparationUnknown(err error) bool
- func IsPreparedEpicProgressionCommitUnknown(err error) bool
- func IsSchemaCompatibilityRefusal(err error) bool
- func LatestPhaseLaunchID(phase FlowPhase) string
- func PhaseAwaitingSession(phase FlowPhase) bool
- func PhaseGateSatisfied(record FlowRecord, phase FlowPhase) bool
- func PhaseGraphLaunchEligible(record FlowRecord, orderedIndex int) bool
- func PhaseIsActionable(phase FlowPhase) bool
- func PhaseLatestLaunchEnded(phase FlowPhase) bool
- func PhaseLaunchEligible(record FlowRecord, orderedIndex int) bool
- func PhasePredecessorsSatisfied(record FlowRecord, phaseID string) bool
- func PhaseSessionLaunchMismatch(phase FlowPhase) bool
- func PhaseStatusTerminal(status string) bool
- func PreparationLaunchBlocked(record FlowRecord) bool
- func RecoverableRunningPhaseResetReason(phase FlowPhase) (string, bool)
- func ResolvePhaseAgentSettings(prefs agent.Preferences, raw PhaseAgentSettings) (agent.Settings, error)
- func SamePreparationIdentity(a, b FlowRecord) bool
- func ValidatePreset(preset Preset) error
- type AutoMergeUpdate
- type AutoModeUpdate
- type BeadFlowActiveError
- type BeadFlowUnreadableError
- type BeadLink
- type ChildPhaseUpdate
- type Closure
- type ClosureUpdate
- type CreateOptions
- type EpicProgression
- type EpicProgressionHalt
- type EpicProgressionHaltUpdate
- type EpicProgressionKey
- type EpicProgressionSuccessorOutcome
- type EpicProgressionSuccessorResult
- type EpicProgressionSuccessorUpdate
- type EpicProgressionUpdate
- type FlowFilter
- type FlowPhase
- type FlowRecord
- type GraphRecoveryState
- type HeadlessUpdate
- type InspectExecutable
- type InspectMigrationOwner
- type InspectOwner
- type InspectReport
- type InspectWAL
- type Issue
- type IssueUpdate
- type ManualMergeUpdate
- type Merge
- type MergeUpdate
- type OpenDiagnostics
- type PRUpdate
- type PartialListEntry
- type PartialListError
- type PhaseAgentSettings
- type PhaseAgentSettingsUpdate
- type PhaseKind
- type PhaseLaunchEndUpdate
- type PhaseLaunchFence
- type PhaseLaunchUpdate
- type PhaseReconciliation
- type PhaseRecoveryUpdate
- type PhaseResetUpdate
- type PhaseRestartUpdate
- type PhaseSpec
- type PhaseStatus
- type PhaseUpdate
- type PlanLinkUpdate
- type PreparationFinalizer
- type PreparedEpicProgressionUpdate
- type Preset
- type PullRequest
- type ReconciliationDemotionUpdate
- type RestoreOptions
- type RestoreResult
- type Role
- type Session
- type SessionAttachUpdate
- type StartMetadataUpdate
- type Store
- func (s *Store) ActivateUntrackedOwner(update UntrackedOwnerActivation) (FlowRecord, error)
- func (s *Store) AddChildPhase(update ChildPhaseUpdate) (FlowRecord, error)
- func (s *Store) AddPhaseLaunchID(update PhaseLaunchUpdate) (FlowRecord, error)
- func (s *Store) AllocateID(title string) (string, error)
- func (s *Store) AttachSession(update SessionAttachUpdate) (FlowRecord, error)
- func (s *Store) ClaimUntrackedOwner(update UntrackedOwnerClaim) (FlowRecord, error)
- func (s *Store) Close() error
- func (s *Store) CloseFlow(update ClosureUpdate) (FlowRecord, error)
- func (s *Store) Create(record FlowRecord) (FlowRecord, error)
- func (s *Store) CreatePreparation(record FlowRecord, opts CreateOptions) (FlowRecord, PreparationFinalizer, error)
- func (s *Store) CreateWithOptions(record FlowRecord, opts CreateOptions) (FlowRecord, error)
- func (s *Store) Delete(flowID string) error
- func (s *Store) DemoteReconciledPhase(update ReconciliationDemotionUpdate) (FlowRecord, error)
- func (s *Store) EnableEpicProgressionForPreparedFlow(update PreparedEpicProgressionUpdate) (EpicProgression, FlowRecord, error)
- func (s *Store) HaltEpicProgression(update EpicProgressionHaltUpdate) (EpicProgression, error)
- func (s *Store) List(filter FlowFilter) ([]FlowRecord, error)
- func (s *Store) MarkManualMerge(update ManualMergeUpdate) (FlowRecord, error)
- func (s *Store) MarkPhaseLaunchEnded(update PhaseLaunchEndUpdate) (FlowRecord, error)
- func (s *Store) OpenDiagnostics() OpenDiagnostics
- func (s *Store) PrepareUntrackedOwnerTransport(update UntrackedOwnerActivation) (FlowRecord, error)
- func (s *Store) Read(flowID string) (FlowRecord, error)
- func (s *Store) ReadEpicProgression(key EpicProgressionKey) (EpicProgression, bool, error)
- func (s *Store) ReconcileEpicProgressionSuccessor(update EpicProgressionSuccessorUpdate) (EpicProgressionSuccessorResult, error)
- func (s *Store) RecoverReconciledPhase(update PhaseRecoveryUpdate) (FlowRecord, error)
- func (s *Store) ReleaseUntrackedOwner(update UntrackedOwnerRelease) (FlowRecord, error)
- func (s *Store) ReopenFlow(flowID string) (FlowRecord, error)
- func (s *Store) ReplaceUntrackedOwner(update UntrackedOwnerReplacement) (FlowRecord, error)
- func (s *Store) ReserveAgentLaunch(flowID string) (FlowRecord, func(), error)
- func (s *Store) ReserveEpicProgressionSuccessor(flowID string) (func(), error)
- func (s *Store) ReserveRepairLaunch(flowID string) (FlowRecord, func(), error)
- func (s *Store) ResetAwaitingSessionPhase(update PhaseResetUpdate) (FlowRecord, error)
- func (s *Store) ResetRecoverableRunningPhase(update PhaseResetUpdate) (FlowRecord, error)
- func (s *Store) RestartPhase(update PhaseRestartUpdate) (FlowRecord, error)
- func (s *Store) Revalidate() error
- func (s *Store) Root() string
- func (s *Store) SetAutoMerge(update AutoMergeUpdate) (FlowRecord, error)
- func (s *Store) SetAutoMode(update AutoModeUpdate) (FlowRecord, error)
- func (s *Store) SetEpicProgression(update EpicProgressionUpdate) (EpicProgression, error)
- func (s *Store) SetHeadless(update HeadlessUpdate) (FlowRecord, error)
- func (s *Store) SetIssue(update IssueUpdate) (FlowRecord, error)
- func (s *Store) SetMerge(update MergeUpdate) (FlowRecord, error)
- func (s *Store) SetPR(update PRUpdate) (FlowRecord, error)
- func (s *Store) SetPhase(update PhaseUpdate) (FlowRecord, error)
- func (s *Store) SetPhaseAgentSettings(update PhaseAgentSettingsUpdate) (FlowRecord, error)
- func (s *Store) SetPlanLink(update PlanLinkUpdate) (FlowRecord, error)
- func (s *Store) SetStartMetadata(update StartMetadataUpdate) (FlowRecord, error)
- type StoreOptions
- type UntrackedOwner
- type UntrackedOwnerActivation
- type UntrackedOwnerClaim
- type UntrackedOwnerRelease
- type UntrackedOwnerReplacement
- type UntrackedOwnerRole
- type UntrackedOwnerState
- type UntrackedOwnerTransport
- type UntrackedTransportKind
Constants ¶
const ( TierMissing = "missing" TierOpen = "open" TierNotWritable = "not_writable" TierMalformed = "malformed" TierNotADatabase = "not_a_database" TierHeader = "header" )
The six tiers Inspect can land in. They are values, not step indexes: two separate conditions report not_a_database and two report not_writable, so a caller discriminates on Reason.
const ( PhaseResetReasonAwaitSession = "await-session" PhaseResetReasonEndedSession = "ended-session" )
const ( StatusPending = "pending" StatusInProgress = "in_progress" StatusNeedsAttention = "needs_attention" StatusBlocked = "blocked" StatusCompleted = "completed" StatusMerged = "merged" StatusAbandoned = "abandoned" StatusClosed = "closed" )
const ( MergePending = "pending" MergeMerged = "merged" MergeBlocked = "blocked" )
const ( OutcomeApproved = "approved" OutcomeApprovedWithConcerns = "approved_with_concerns" OutcomeChangesRequested = "changes_requested" OutcomeBlocked = "blocked" )
const ( GraphRecoveryPresetEdgesRestored = "preset_edges_restored" GraphRecoveryMissingEdgesUnresolved = "missing_edges_unresolved" )
const ( OutcomePhaseResultMissing = "phase_result_missing" OutcomePhaseResultStale = "phase_result_stale" )
Variables ¶
var ( // ErrPreparationIncomplete means authoritative state confirms that no // protected preparation receipt was persisted. ErrPreparationIncomplete = errors.New("flow preparation is incomplete") // ErrPreparationUnknown means a failed persistence attempt could not be // reconciled by an authoritative read. ErrPreparationUnknown = errors.New("flow preparation outcome is unknown") // ErrPreparationStale means the Flow ID now names a different preparation // generation, so callers must not compensate the returned replacement. ErrPreparationStale = errors.New("flow preparation generation is stale") // ErrPreparationReservation means Compensate could not acquire the // launch/close reservation, so the one-shot finalizer was not consumed and // remains retryable. ErrPreparationReservation = errors.New("flow preparation reservation is unavailable") )
var ( ErrRestoreBackupUnusable = errors.New("flow database backup is not usable") ErrRestoreBlockedByOwners = errors.New("flow database restore blocked by a live owner") ErrRestoreGenerationMismatch = errors.New("flow database backup is from a different generation") )
The refusals Restore returns. Each is typed because each has a different remedy: fix or re-take the backup, close the named processes, or acknowledge with --force.
var ErrAutoLaunchOutdated = errors.New("auto launch outdated")
ErrAutoLaunchOutdated is the sentinel every outdated-auto-launch rejection wraps. It is exported so callers outside this package can build the rejection their AutoMode handling has to survive.
var ErrBeadFlowActive = errors.New("bead already has an active flow")
ErrBeadFlowActive is the sentinel every duplicate Bead-linked creation refusal wraps.
var ErrBeadFlowUnreadable = errors.New("bead may have an unreadable flow")
ErrBeadFlowUnreadable is the sentinel for a refusal caused by a row that claims the requested Bead but whose stored record cannot be decoded.
var ErrDatabaseGenerationChanged = errors.New("flow database changed generation underneath this handle")
ErrDatabaseGenerationChanged marks a handle whose database was replaced or migrated underneath it.
Distinct from every other write failure because the remedy is distinct: the request was fine, the database is fine, and this PROCESS is holding a handle on state that no longer exists. Retrying cannot help, and a caller that treated it as a transient error would loop.
var ErrEpicProgressionActivationChanged = errors.New("epic progression activation changed")
ErrEpicProgressionActivationChanged reports that a progression edge was observed under an activation other than the one currently stored.
var ErrFlowClosed = errors.New("flow is closed")
ErrFlowClosed is the sentinel every closed-Flow mutation refusal wraps. A reservation reports a closed Flow and an unreadable one through the same error return, and only the first may block a caller: callers that must stay permissive when a Flow is missing test for this rather than for any error.
var ErrFlowNotFound = errors.New("flow not found")
ErrFlowNotFound is the sentinel every missing-record error wraps. It is exported alongside IsNotFound so callers and their test doubles can build the same error the store returns.
var ErrFlowUntrackedOwned = errors.New("flow worktree is owned by a phase-untracked launch")
ErrFlowUntrackedOwned reports that a live or reserved phase-untracked launch already owns the Flow worktree.
var ErrMigrationBlockedByOwners = errors.New("flow database migration blocked by a live owner")
ErrMigrationBlockedByOwners marks a migration refused because a long-lived process is holding this database open at a build that could not read the result.
Distinguishable from every other refusal because the remedy is different and entirely in the operator's hands: nothing is wrong with the database, and the action is to close the named processes.
var ErrUntrackedOwnerChanged = errors.New("phase-untracked Flow owner changed")
ErrUntrackedOwnerChanged reports that an identity-fenced owner mutation no longer names the persisted launch.
Functions ¶
func AgentSettablePhaseStatuses ¶
func AgentSettablePhaseStatuses() []string
AgentSettablePhaseStatuses returns the canonical list of statuses agents may set on a Flow phase, in canonical order.
func AllowedNextPhaseStatuses ¶
AllowedNextPhaseStatuses returns the canonical agent-settable statuses a phase may transition to from current, in canonical order. It returns nil for unknown statuses. Same-status updates are idempotent no-ops handled separately and are not listed.
func BeadFlowSlotOccupied ¶ added in v0.10.6
func BeadFlowSlotOccupied(record FlowRecord) bool
BeadFlowSlotOccupied reports whether record holds its repository+Bead slot.
A Flow occupies the slot for its Bead unless it reached a terminal status: completed, merged, abandoned, and closed release it, because a follow-up Flow for the same Bead is then legitimate. Everything else — including blocked and needs_attention — holds it, because those are the states where a human is meant to intervene on the existing Flow rather than fork a second one.
Occupancy is a function of the derived status alone, deliberately including receipt-less preparations: PreparationNonce is never consulted. A prepared Flow holds its slot because seeded phases derive pending, not because it lacks a receipt — and nothing clears the nonce on close, so exempting receipt-less records from the terminal cases would make the documented remedy ("close it from the Flows view with C, then press f/F") leave the Bead occupied forever. Creating a Flow whose supplied phases already derive terminal is not a production path; if it were, releasing the slot is what the terminal rule asks for.
DeriveStatus returns exactly eight values, so the switch below is total.
func DatabaseSchemaVersion ¶ added in v0.10.5
func DatabaseSchemaVersion() int
DatabaseSchemaVersion is the physical flow database schema this build writes. It is exported so launch and diagnostic surfaces can report the schema an agent's binary must be able to write without importing store internals.
func DefaultRoot ¶
DefaultRoot returns the default artifact root, matching sessions and plans.
func DeriveStatus ¶
func DeriveStatus(record FlowRecord) string
DeriveStatus computes the flow-level status from phase and merge state.
func EffectiveAutoMerge ¶ added in v0.10.8
func EffectiveAutoMerge(record FlowRecord, global bool) bool
EffectiveAutoMerge resolves the per-Flow override against the global setting.
func FlowClosed ¶ added in v0.10.1
func FlowClosed(record FlowRecord) bool
FlowClosed reports whether a Flow was deliberately closed.
func HasIssueTarget ¶
HasIssueTarget reports whether issue metadata contains enough target context to open the issue in a browser.
func HasPRTarget ¶
func HasPRTarget(pr PullRequest) bool
HasPRTarget reports whether PR metadata contains enough target context for downstream Autoreview work.
func IsAutoLaunchOutdated ¶
IsAutoLaunchOutdated reports whether err means an automatic launch request lost its race with newer Flow state and should be ignored.
func IsBeadFlowActive ¶ added in v0.10.6
IsBeadFlowActive reports the duplicate refusal.
func IsBeadFlowRefusal ¶ added in v0.10.6
IsBeadFlowRefusal reports whether the Bead-slot guard refused a creation, in either of its two forms. Both refusals are raised before anything is written, so a caller can use this to distinguish "nothing was created" from a Flow that exists but failed later preparation.
func IsBeadFlowUnreadable ¶ added in v0.10.6
IsBeadFlowUnreadable reports the unreadable-candidate refusal.
func IsFlowClosed ¶ added in v0.10.1
IsFlowClosed reports whether err is a refusal to mutate or launch a closed Flow, as opposed to a missing record or a failed read.
func IsMigrationBlockedByOwners ¶ added in v0.10.6
IsMigrationBlockedByOwners reports whether err is the owners-lease refusal. Like the compatibility refusals it must stay distinguishable: the database is healthy, so any advice about moving it aside or restoring a backup would be a wrong diagnosis.
func IsNotFound ¶
IsNotFound reports whether err means the requested Flow record does not exist.
func IsPreparationIncomplete ¶ added in v0.10.2
IsPreparationIncomplete reports a confirmed receipt-less outcome.
func IsPreparationReservation ¶ added in v0.10.3
IsPreparationReservation reports that Compensate could not acquire the launch/close reservation and therefore left the one-shot finalizer usable.
func IsPreparationStale ¶ added in v0.10.2
IsPreparationStale reports that the Flow ID now names another preparation generation and is therefore unsafe to mutate as compensation.
func IsPreparationUnknown ¶ added in v0.10.2
IsPreparationUnknown reports a persistence outcome that could not be read authoritatively after a failed receipt write.
func IsPreparedEpicProgressionCommitUnknown ¶ added in v0.10.2
IsPreparedEpicProgressionCommitUnknown reports that an atomic enable reached commit but SQLite could not confirm whether that commit became durable.
func IsSchemaCompatibilityRefusal ¶ added in v0.10.6
IsSchemaCompatibilityRefusal reports whether err means "this binary must not touch this database": the database was written by a newer build, or this opener's role forbids the migration it needs. Both are refusals about the pairing of build and database, not about the request, so a caller that can defer its write to a compatible process (the launch controller's spool path) should do that rather than fail. Every other error — a locked file, a corrupt page, a typo'd root — is not one of these.
func LatestPhaseLaunchID ¶
func PhaseAwaitingSession ¶
PhaseAwaitingSession reports whether the newest phase launch has not attached any session record yet. A malformed attached record is handled separately by callers as missing session metadata.
func PhaseGateSatisfied ¶
func PhaseGateSatisfied(record FlowRecord, phase FlowPhase) bool
PhaseGateSatisfied reports whether phase satisfies the semantic gate that unlocks downstream phases in the Flow graph.
func PhaseGraphLaunchEligible ¶ added in v0.10.3
func PhaseGraphLaunchEligible(record FlowRecord, orderedIndex int) bool
PhaseGraphLaunchEligible is PhaseLaunchEligible without the preparation receipt fence. Create-time root snapshots and compensation still need the graph-ready set while a nonce-bearing Flow is receipt-less.
func PhaseIsActionable ¶ added in v0.10.1
PhaseIsActionable reports whether a phase is one the Flow is currently sitting on: ready, running, needs_attention, or blocked. It is deliberately broader than "executing" — blocked and needs-attention phases are actionable because they are where a human or agent must intervene next.
func PhaseLatestLaunchEnded ¶
PhaseLatestLaunchEnded reports whether the newest phase launch attached only ended session records. It ignores older launch history.
func PhaseLaunchEligible ¶
func PhaseLaunchEligible(record FlowRecord, orderedIndex int) bool
PhaseLaunchEligible reports whether the phase at orderedIndex can be offered for launch. It is index-aware so stale duplicate rows from hand-authored records cannot be launched even when their stored status is ready.
func PhasePredecessorsSatisfied ¶
func PhasePredecessorsSatisfied(record FlowRecord, phaseID string) bool
PhasePredecessorsSatisfied reports whether all graph prerequisites for phaseID satisfy the Flow gate rules used to derive downstream readiness.
func PhaseSessionLaunchMismatch ¶
PhaseSessionLaunchMismatch reports whether any attached session cannot be matched back to one of the phase launch attempts.
func PhaseStatusTerminal ¶
PhaseStatusTerminal reports whether a phase has finished (successfully or by being skipped), as opposed to states that still expect agent work.
func PreparationLaunchBlocked ¶ added in v0.10.3
func PreparationLaunchBlocked(record FlowRecord) bool
PreparationLaunchBlocked reports that a nonce-bearing preparation has not yet stamped its receipt, so another process must not launch or persist a launch ID. Migrated records without a nonce stay launchable.
func RecoverableRunningPhaseResetReason ¶
RecoverableRunningPhaseResetReason reports whether a running phase can be reset by approach-owned stale-session recovery.
func ResolvePhaseAgentSettings ¶ added in v0.10.1
func ResolvePhaseAgentSettings(prefs agent.Preferences, raw PhaseAgentSettings) (agent.Settings, error)
ResolvePhaseAgentSettings validates a phase's raw persisted stamp before applying field-by-field fallbacks from the current provider preferences. An empty raw stamp follows the globally selected command. A non-empty agent selects that provider's global model and effort, even when another provider is selected globally. The literal "default" is non-empty and therefore remains an explicit provider-default choice.
func SamePreparationIdentity ¶ added in v0.10.3
func SamePreparationIdentity(a, b FlowRecord) bool
SamePreparationIdentity reports whether two records name the same receipt-less preparation generation. Prefer the storage-only nonce whenever either record carries one. Compare the legacy PreparationGeneration field only for migrated records that have no nonce.
func ValidatePreset ¶
ValidatePreset checks whether preset can seed a Flow phase graph.
Types ¶
type AutoMergeUpdate ¶ added in v0.10.8
AutoMergeUpdate sets a Flow override for automatic merge-phase launches. A nil value inherits the global setting; false and true are explicit policy.
type AutoModeUpdate ¶
AutoModeUpdate changes whether the TUI may automatically launch ready phases for a single Flow after successful phase completion.
type BeadFlowActiveError ¶ added in v0.10.6
type BeadFlowActiveError struct {
RepoPath string
BeadID string
Existing FlowRecord
}
BeadFlowActiveError names the Flow that already holds the repository+Bead slot so callers can surface it instead of creating a second Flow.
func (*BeadFlowActiveError) Error ¶ added in v0.10.6
func (e *BeadFlowActiveError) Error() string
func (*BeadFlowActiveError) Unwrap ¶ added in v0.10.6
func (e *BeadFlowActiveError) Unwrap() error
type BeadFlowUnreadableError ¶ added in v0.10.6
BeadFlowUnreadableError names the Flow whose record could not be decoded, so the caller can point a human at the row that needs repair.
Such a row cannot report a derived status, so the guard cannot tell whether it still holds the Bead slot. Creating anyway is the worse answer: it is precisely how a duplicate Flow and a duplicate worktree get made. Unreadable rows for other Beads remain ignorable, so this never becomes a whole-repository outage.
func (*BeadFlowUnreadableError) Error ¶ added in v0.10.6
func (e *BeadFlowUnreadableError) Error() string
func (*BeadFlowUnreadableError) Unwrap ¶ added in v0.10.6
func (e *BeadFlowUnreadableError) Unwrap() error
type BeadLink ¶ added in v0.10.1
BeadLink stores the independent Beads issue associated with a Flow. EpicID identifies the selected issue's parent epic when that relationship is known.
func (*BeadLink) UnmarshalJSON ¶ added in v0.10.1
UnmarshalJSON distinguishes an omitted link from a present but incomplete object. The zero value remains valid on pre-Bead records because this method is not called when the containing field is absent.
type ChildPhaseUpdate ¶
type ChildPhaseUpdate struct {
FlowID string
ParentPhaseID string
PhaseID string
Title string
Order int
Fence PhaseLaunchFence
}
ChildPhaseUpdate creates or updates a stable child phase under Implementation.
type Closure ¶ added in v0.10.1
type Closure struct {
Reason string `json:"reason,omitempty"`
ClosedAt *time.Time `json:"closed_at,omitempty"`
}
Closure records why and when a Flow was deliberately closed. Derivation keys on ClosedAt, never on Reason, so a record can never drift into closed because of an empty-string comparison.
type ClosureUpdate ¶ added in v0.10.1
ClosureUpdate records a deliberate close of a Flow with its required reason.
type CreateOptions ¶
type CreateOptions struct {
Preset *Preset
Headless *bool
// PhaseAgent is stamped onto every seeded phase, and onto declared phases
// that carry no settings of their own.
PhaseAgent PhaseAgentSettings
// contains filtered or unexported fields
}
CreateOptions configures Flow creation defaults and phase seeding.
type EpicProgression ¶ added in v0.10.2
type EpicProgression struct {
SchemaVersion int
RepoPath string
EpicID string
Enabled bool
Done bool
Halt *EpicProgressionHalt
CreatedAt time.Time
UpdatedAt time.Time
}
EpicProgression is the persisted auto-progression state for one epic.
type EpicProgressionHalt ¶ added in v0.10.2
type EpicProgressionHalt struct {
ChildBeadID string `json:"child_bead_id"`
Status string `json:"status"`
Message string `json:"message"`
}
EpicProgressionHalt is the durable reason progression stopped.
type EpicProgressionHaltUpdate ¶ added in v0.10.5
type EpicProgressionHaltUpdate struct {
Key EpicProgressionKey
Halt EpicProgressionHalt
ExpectedActivation time.Time
}
EpicProgressionHaltUpdate records why progression stopped. Only authoritative active state may halt, and the first halt tuple is the one that sticks.
type EpicProgressionKey ¶ added in v0.10.2
EpicProgressionKey identifies one repository-local Beads epic.
type EpicProgressionSuccessorOutcome ¶ added in v0.10.2
type EpicProgressionSuccessorOutcome string
EpicProgressionSuccessorOutcome classifies the authoritative relationship between an already-enabled epic and one prepared sequential successor.
const ( EpicProgressionSuccessorInactive EpicProgressionSuccessorOutcome = "inactive" EpicProgressionSuccessorReleased EpicProgressionSuccessorOutcome = "released" EpicProgressionSuccessorOwnedObstruction EpicProgressionSuccessorOutcome = "owned_obstruction" EpicProgressionSuccessorAccepted EpicProgressionSuccessorOutcome = "accepted" EpicProgressionSuccessorRetryable EpicProgressionSuccessorOutcome = "retryable" )
type EpicProgressionSuccessorResult ¶ added in v0.10.2
type EpicProgressionSuccessorResult struct {
Outcome EpicProgressionSuccessorOutcome
Progression EpicProgression
Flow FlowRecord
}
EpicProgressionSuccessorResult returns the records observed in the same writer transaction that produced Outcome. Flow is empty for inactive and for an absent released Flow.
type EpicProgressionSuccessorUpdate ¶ added in v0.10.2
type EpicProgressionSuccessorUpdate struct {
FlowID string
Key EpicProgressionKey
Bead BeadLink
ExpectedActivation time.Time
}
EpicProgressionSuccessorUpdate names the exact prepared Flow that sequential progression owns. Callers hold its launch/close reservation while reconciling.
type EpicProgressionUpdate ¶ added in v0.10.2
type EpicProgressionUpdate struct {
Key EpicProgressionKey
Enabled bool
Done bool
ExpectedActivation time.Time
}
EpicProgressionUpdate requests one complete target state. Enabling clears done and halt; explicit normal off clears done and retains a sticky halt; done may be newly established only from authoritative active state.
type FlowFilter ¶
type FlowFilter struct {
RepoPath string
}
FlowFilter narrows records returned by List.
type FlowPhase ¶
type FlowPhase struct {
PhaseID string `json:"phase_id"`
ParentPhaseID string `json:"parent_phase_id,omitempty"`
Title string `json:"title"`
Kind PhaseKind `json:"kind"`
// Agent, Model, and ReasoningEffort are optional launch overrides; see
// PhaseAgentSettings and ResolvePhaseAgentSettings.
Agent string `json:"agent,omitempty"`
Model string `json:"model,omitempty"`
ReasoningEffort string `json:"reasoning_effort,omitempty"`
DependsOn []string `json:"depends_on"`
Status PhaseStatus `json:"status"`
Order int `json:"order"`
Outcome string `json:"outcome,omitempty"`
Notes string `json:"notes,omitempty"`
Summary string `json:"summary,omitempty"`
Reconciliation *PhaseReconciliation `json:"reconciliation,omitempty"`
LaunchIDs []string `json:"launch_ids,omitempty"`
RecoveredLaunchIDs []string `json:"recovered_launch_ids,omitempty"`
Sessions []Session `json:"sessions,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
FlowPhase is one phase in the persisted Flow pipeline.
func FindPhaseByKind ¶
func FindPhaseByKind(record FlowRecord, kind string) (FlowPhase, bool)
FindPhaseByKind returns the first phase with the requested semantic kind.
func FirstLaunchablePhase ¶
func FirstLaunchablePhase(record FlowRecord) (FlowPhase, int, bool)
FirstLaunchablePhase returns the first ordered phase that can be launched.
func NextActionablePhase ¶ added in v0.10.1
func NextActionablePhase(record FlowRecord) (FlowPhase, bool)
NextActionablePhase returns the first actionable phase in graph order (OrderedPhases), reporting false when the Flow has none.
func OrderedPhases ¶
OrderedPhases returns phases with child phases grouped directly below their parent, sorting siblings by Order and then phase id. Top-level phase order is otherwise preserved for backward compatibility with existing records.
func (FlowPhase) AgentSettings ¶ added in v0.10.1
func (p FlowPhase) AgentSettings() PhaseAgentSettings
AgentSettings returns the agent selection captured for this phase.
type FlowRecord ¶
type FlowRecord struct {
SchemaVersion int `json:"schema_version"`
FlowID string `json:"flow_id"`
Title string `json:"title"`
Instructions string `json:"instructions"`
Status string `json:"status"`
RepoPath string `json:"repo_path"`
WorktreePath string `json:"worktree_path,omitempty"`
Branch string `json:"branch,omitempty"`
BaseRef string `json:"base_ref,omitempty"`
Commit string `json:"commit,omitempty"`
PresetName string `json:"preset_name,omitempty"`
PlanID string `json:"plan_id,omitempty"`
PlanPath string `json:"plan_path,omitempty"`
Bead BeadLink `json:"bead,omitzero"`
Issue Issue `json:"issue,omitempty"`
PR PullRequest `json:"pr,omitempty"`
Merge Merge `json:"merge,omitempty"`
Closed Closure `json:"closed,omitzero"`
AutoMode bool `json:"auto_mode,omitempty"`
AutoMerge *bool `json:"auto_merge,omitempty"`
// Headless is the per-Flow manual-launch preference. Like AutoMode, it is
// forced on at creation and can only be changed afterwards through
// SetHeadless or CreateOptions.Headless — a value set on a record passed to
// Create is ignored. It is written without omitempty so an explicit false
// stays distinguishable from a legacy record that predates the field.
Headless bool `json:"headless"`
Phases []FlowPhase `json:"phases"`
// UntrackedOwner is the durable Flow-level owner for worktree-writing
// launches that deliberately do not attach to a phase.
UntrackedOwner *UntrackedOwner `json:"untracked_owner,omitempty"`
// ProgressionClaim marks an identity created for the external Beads claim
// protocol. Recovery repeats the idempotent claim; the marker is retry
// provenance, not proof that ownership already landed. SQLite schema v5
// fences this JSON marker with a compatibility trigger.
ProgressionClaim bool `json:"progression_claim,omitempty"`
PreparationGeneration string `json:"preparation_generation,omitempty"`
PreparedAt *time.Time `json:"prepared_at,omitempty"`
// PreparationNonce is storage-only generation identity minted by
// CreatePreparation. General creation strips caller-supplied values.
// SQLite schema v6 projects it onto flows.preparation_nonce and fences
// older writers with a compatibility trigger. Schema v4 separately
// requires boolean `done` in epic progression JSON.
PreparationNonce string `json:"-"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
GraphRecovery GraphRecoveryState `json:"-"`
}
FlowRecord is the persisted task workflow record.
func ActiveBeadFlow ¶ added in v0.10.6
func ActiveBeadFlow(err error) (FlowRecord, bool)
ActiveBeadFlow returns the Flow named by a duplicate refusal.
type GraphRecoveryState ¶
type GraphRecoveryState struct {
Status string
}
GraphRecoveryState reports an unresolved graph discovered during one-time legacy migration. The SQLite storage codec persists the unresolved marker.
type HeadlessUpdate ¶ added in v0.10.0
HeadlessUpdate changes the manual launch preference for one Flow.
type InspectExecutable ¶ added in v0.10.5
type InspectExecutable struct {
Path string `json:"path"`
BuildVersion string `json:"build_version"`
Schema int `json:"schema"`
}
InspectExecutable names the binary that produced the report.
type InspectMigrationOwner ¶ added in v0.10.5
InspectMigrationOwner is the PID the bootstrap lock file carries. Verified is always false: a stale PID from a crashed migrator is indistinguishable from a live one, so this is evidence for a human and never drives a refusal.
type InspectOwner ¶ added in v0.10.6
type InspectOwner struct {
PID int `json:"pid"`
BuildVersion string `json:"build_version"`
Commit string `json:"commit,omitempty"`
Executable string `json:"executable,omitempty"`
SchemaVersion int `json:"schema_version"`
StartedAt string `json:"started_at,omitempty"`
Verified bool `json:"verified"`
}
InspectOwner is one live holder of the owners lease.
Verified is always TRUE, and that is the difference from InspectMigrationOwner above: this record was published by a process that still holds an exclusive flock on it, so its liveness was proved rather than inferred from a PID that a crash could have left behind.
type InspectReport ¶ added in v0.10.5
type InspectReport struct {
SchemaVersion int64 `json:"schema_version"`
Path string `json:"path"`
Tier string `json:"tier"`
Readable bool `json:"readable"`
UserVersion *int64 `json:"user_version"`
CheckpointedUserVersion *int64 `json:"checkpointed_user_version"`
WAL InspectWAL `json:"wal"`
JournalMode *string `json:"journal_mode"`
DirectoryMode *string `json:"directory_mode"`
GenerationID *string `json:"generation_id"`
MinReaderGeneration *int64 `json:"min_reader_generation"`
MinWriterGeneration *int64 `json:"min_writer_generation"`
// FirstCompatibleRelease is the earliest release that opens a database at
// the version found here, taken from this build's compatibility manifest.
// Null when the version is one this build does not declare — a database
// from a newer build, or a predecessor the manifest cannot map honestly.
FirstCompatibleRelease *string `json:"first_compatible_release"`
SidecarStale *bool `json:"sidecar_stale"`
Executable InspectExecutable `json:"executable"`
MigrationOwner *InspectMigrationOwner `json:"migration_owner"`
// Owners are the live holders of the owners lease, each verified. Always an
// array, never null: "no live holders" is an answer, and a consumer must
// not have to distinguish it from "not checked".
Owners []InspectOwner `json:"owners"`
Warnings []string `json:"warnings"`
Reason *string `json:"reason"`
NextAction *string `json:"next_action"`
}
InspectReport is the answer `approach db inspect --json` prints. Every nullable field is a pointer so "not applicable" and "zero" stay distinct — reporting sidecar_stale: false in a tier that never compared anything would assert agreement nobody checked.
func Inspect ¶ added in v0.10.5
func Inspect(root string) (InspectReport, error)
Inspect answers "what is in this state root, and can approach open it" for an operator who is already stuck.
It never constructs a Store, never takes the bootstrap lock — that lock is held for up to two minutes by a running migration, and this is the command you reach for while one is running — never opens with immutable=1, which would hide uncheckpointed WAL content behind a stale checkpointed view, and never routes through SecureCanonicalRoot, which would chmod the very directory the not_writable tier exists to report.
Its error return is reserved for the cases where it cannot produce a report at all. A database it merely could not open is a report, not an error.
type InspectWAL ¶ added in v0.10.5
type InspectWAL struct {
Present bool `json:"present"`
ShmPresent bool `json:"shm_present"`
// Dirty means a -wal file exists with non-zero size.
Dirty bool `json:"dirty"`
}
InspectWAL reports the write-ahead log's state on disk.
type Issue ¶
type Issue struct {
Provider string `json:"provider,omitempty"`
Number int `json:"number,omitempty"`
URL string `json:"url,omitempty"`
}
Issue stores agent-reported GitHub issue metadata.
type IssueUpdate ¶
type IssueUpdate struct {
FlowID string
Provider string
Number int
URL string
Fence PhaseLaunchFence
}
IssueUpdate records metadata for the issue referenced by a Flow.
type ManualMergeUpdate ¶
type ManualMergeUpdate struct {
FlowID string
PRNumber int
PRURL string
Commit string
MergedAt time.Time
Summary string
}
ManualMergeUpdate records metadata for a PR that was manually merged in GitHub.
type Merge ¶
type Merge struct {
Status string `json:"status,omitempty"`
Commit string `json:"commit,omitempty"`
MergedAt *time.Time `json:"merged_at,omitempty"`
}
Merge stores agent-reported merge metadata.
type MergeUpdate ¶
type MergeUpdate struct {
FlowID string
Status string
Commit string
MergedAt time.Time
Fence PhaseLaunchFence
}
MergeUpdate records metadata for the merge that completed or blocked a Flow.
type OpenDiagnostics ¶ added in v0.10.5
type OpenDiagnostics struct {
// DirectoryMode is the resolved root's permission bits as found.
DirectoryMode os.FileMode
// JournalMode is what PRAGMA journal_mode reported, lowercased.
JournalMode string
// SidecarStale reports an approach.db.meta.json that disagrees with
// user_version. Only a RoleMigrator repairs it; every other role reports it.
SidecarStale bool
// Warnings are operator-facing notices that are not failures.
Warnings []string
}
OpenDiagnostics reports what an open observed about the root and the database without repairing it. It is the replacement surface for the two checks a RoleReader skips — SecureCanonicalRoot's 0700 assertion and openSQLiteBackend's journal_mode=WAL assertion — so that dropping them is a substitution rather than a deletion.
type PRUpdate ¶
type PRUpdate struct {
FlowID string
Provider string
Number int
URL string
HeadBranch string
BaseBranch string
Status string
Fence PhaseLaunchFence
}
PRUpdate records metadata for the pull request created by a Flow.
type PartialListEntry ¶ added in v0.10.1
PartialListEntry identifies one authoritative Flow row that could be scanned but not decoded while listing. Cause retains the row-local corruption error.
func (PartialListEntry) DiagnosticFlowID ¶ added in v0.10.1
func (e PartialListEntry) DiagnosticFlowID() string
DiagnosticFlowID renders the authoritative row ID without allowing corrupt control bytes or an ambiguous blank value to reach terminal and log sinks. FlowID itself remains unchanged so callers can inspect the exact stored key.
type PartialListError ¶ added in v0.10.1
type PartialListError struct {
Entries []PartialListEntry
}
PartialListError reports rows omitted from an otherwise usable List result. Entries retain the list query's deterministic SQL order.
func AsPartialList ¶ added in v0.10.1
func AsPartialList(err error) (*PartialListError, bool)
AsPartialList classifies a valid standalone partial Flow-list result without requiring callers to match diagnostic text. Ordinary single-error wrapping is accepted, but aggregate error trees are rejected so a joined fatal error can never be downgraded merely because it also contains a partial diagnostic.
func (*PartialListError) Error ¶ added in v0.10.1
func (e *PartialListError) Error() string
func (*PartialListError) Unwrap ¶ added in v0.10.1
func (e *PartialListError) Unwrap() []error
Unwrap exposes every retained row-local cause to errors.Is and errors.As.
type PhaseAgentSettings ¶ added in v0.10.1
PhaseAgentSettings is the agent selection captured for a Flow phase.
Each field is read independently: an empty field means nothing was captured and the value must be resolved from the global setting in effect at launch, while "default" means the provider default was captured explicitly. Writing them is not independent — see Validate.
func PhaseAgentSettingsFrom ¶ added in v0.10.1
func PhaseAgentSettingsFrom(settings agent.Settings) PhaseAgentSettings
PhaseAgentSettingsFrom converts a resolved launch selection into the persistence shape. It is the only conversion between the two triples, so callers holding an agent.Settings should route through it rather than assigning the three fields by hand.
func (PhaseAgentSettings) IsZero ¶ added in v0.10.1
func (s PhaseAgentSettings) IsZero() bool
IsZero reports whether no setting was captured. Emptiness is evaluated after normalization, so whitespace-only fields count as empty.
func (PhaseAgentSettings) Normalize ¶ added in v0.10.1
func (s PhaseAgentSettings) Normalize() PhaseAgentSettings
Normalize lowercases and trims every field.
func (PhaseAgentSettings) Validate ¶ added in v0.10.1
func (s PhaseAgentSettings) Validate() error
Validate checks the normalized triple as a unit: an unset selection is allowed, but a model or reasoning effort without an agent is not, because a model cannot be interpreted without knowing its agent.
type PhaseAgentSettingsUpdate ¶ added in v0.10.1
type PhaseAgentSettingsUpdate struct {
FlowID string
PhaseID string
Settings PhaseAgentSettings
Fence PhaseLaunchFence
}
PhaseAgentSettingsUpdate replaces one phase's complete persisted agent settings stamp. Empty settings clear the stamp and restore global fallback.
type PhaseKind ¶ added in v0.10.7
type PhaseKind string
PhaseKind is a persisted Flow phase kind. JSON keeps the string values.
const ( KindPlan PhaseKind = "plan" KindPlanReview PhaseKind = "plan_review" KindImplementation PhaseKind = "implementation" KindReviewLoop PhaseKind = "review_loop" KindPRCreation PhaseKind = "pr_creation" KindAutoreview PhaseKind = "autoreview" KindMerge PhaseKind = "merge" KindImplementationChild PhaseKind = "implementation_child" )
func SemanticKind ¶
SemanticKind returns the normalized semantic kind for a phase. Persisted kind wins; otherwise default preset phase IDs are inferred for legacy records.
type PhaseLaunchEndUpdate ¶
PhaseLaunchEndUpdate records that a tracked Flow launch has ended.
type PhaseLaunchFence ¶ added in v0.10.7
PhaseLaunchFence revokes writes from a phase launch removed by recovery and, when OwnerPhaseID is set, from a launch that no longer owns that phase. Store mutations check it inside the same transaction that persists the write.
type PhaseLaunchUpdate ¶
type PhaseLaunchUpdate struct {
FlowID string
PhaseID string
LaunchID string
Resume bool
AutoLaunch bool
AutoMerge bool
GlobalAutoMerge bool
}
PhaseLaunchUpdate records one agent launch attempt against a Flow phase. Resume marks the launch as a session resume: resuming a phase in a terminal status (completed, skipped) records the launch without reopening the phase, while non-resume launches always mark the phase running.
type PhaseReconciliation ¶ added in v0.10.7
type PhaseReconciliation struct {
Reason string `json:"reason"`
LaunchID string `json:"launch_id"`
}
PhaseReconciliation authenticates a demotion written by launch control.
type PhaseRecoveryUpdate ¶ added in v0.10.7
type PhaseRecoveryUpdate struct {
FlowID string
PhaseID string
ExpectedStatus PhaseStatus
ExpectedOutcome string
ExpectedLaunchID string
ExpectedUpdatedAt time.Time
Fence PhaseLaunchFence
}
PhaseRecoveryUpdate identifies one reconciliation-demoted phase snapshot. Recovery compares every field before removing the stale launch so a request can never apply to phase state newer than the command observed.
type PhaseResetUpdate ¶
type PhaseResetUpdate struct {
FlowID string
PhaseID string
Fence PhaseLaunchFence
}
PhaseResetUpdate identifies one UI-owned phase recovery mutation.
type PhaseRestartUpdate ¶
type PhaseRestartUpdate struct {
FlowID string
PhaseID string
Notes string
Fence PhaseLaunchFence
}
PhaseRestartUpdate restarts a blocked or needs-attention phase as running.
type PhaseSpec ¶
type PhaseSpec struct {
ID string `toml:"id"`
Title string `toml:"title"`
Kind PhaseKind `toml:"kind"`
DependsOn []string `toml:"depends_on"`
}
PhaseSpec is a data-only phase declaration used by Flow phase graph presets.
type PhaseStatus ¶ added in v0.10.7
type PhaseStatus string
PhaseStatus is a persisted Flow phase status. JSON keeps the string values.
const ( PhasePending PhaseStatus = "pending" PhaseReady PhaseStatus = "ready" PhaseRunning PhaseStatus = "running" PhaseNeedsAttention PhaseStatus = "needs_attention" PhaseCompleted PhaseStatus = "completed" PhaseBlocked PhaseStatus = "blocked" PhaseSkipped PhaseStatus = "skipped" )
type PhaseUpdate ¶
type PhaseUpdate struct {
FlowID string
PhaseID string
Status PhaseStatus
Outcome string
Notes string
Summary string
Fence PhaseLaunchFence
}
PhaseUpdate describes one persisted phase status update.
type PlanLinkUpdate ¶
type PlanLinkUpdate struct {
FlowID string
PlanID string
PlanPath string
Fence PhaseLaunchFence
}
PlanLinkUpdate links a saved approach plan artifact to an existing Flow.
type PreparationFinalizer ¶ added in v0.10.2
type PreparationFinalizer interface {
Finalize(func() error) (FlowRecord, error)
Compensate(notes string) (FlowRecord, error)
// CompensateUnderReservation is Compensate for a caller that already holds
// this Flow's launch/close reservation. Compensate acquires that reservation
// itself and must not be used while one is already held.
CompensateUnderReservation(notes string) (FlowRecord, error)
}
PreparationFinalizer is a Flow-bound one-shot capability. The concrete type is private so callers cannot construct a capability for an arbitrary Flow ID.
type PreparedEpicProgressionUpdate ¶ added in v0.10.2
type PreparedEpicProgressionUpdate struct {
FlowID string
Key EpicProgressionKey
Bead BeadLink
}
PreparedEpicProgressionUpdate binds enablement to one exact prepared child Flow. Callers hold that Flow's launch/close reservation around this update.
type Preset ¶
Preset declares a reusable graph of top-level Flow phases.
func DefaultPreset ¶
func DefaultPreset() Preset
DefaultPreset returns the built-in phase graph used when callers do not provide a custom graph.
type PullRequest ¶
type PullRequest struct {
Provider string `json:"provider,omitempty"`
Number int `json:"number,omitempty"`
URL string `json:"url,omitempty"`
HeadBranch string `json:"head_branch,omitempty"`
BaseBranch string `json:"base_branch,omitempty"`
Status string `json:"status,omitempty"`
}
PullRequest stores agent-reported PR metadata.
type ReconciliationDemotionUpdate ¶ added in v0.10.7
type ReconciliationDemotionUpdate struct {
PhaseUpdate
Reason string
LaunchID string
}
ReconciliationDemotionUpdate is the launch-control-owned form of PhaseUpdate.
type RestoreOptions ¶ added in v0.10.6
type RestoreOptions struct {
Root string
BackupPath string
// Force proceeds past a generation mismatch. It does not skip the backup
// verification or the owners refusal: those are not acknowledgements, they
// are the difference between a restore and data loss.
Force bool
// LockTimeout bounds the bootstrap lease wait. Zero uses the package default.
LockTimeout time.Duration
}
RestoreOptions names what to put back and where.
type RestoreResult ¶ added in v0.10.6
type RestoreResult struct {
SchemaVersion int64 `json:"schema_version"`
Path string `json:"path"`
RestoredFrom string `json:"restored_from"`
// PreRestoreBackup is the copy of the database this restore replaced.
PreRestoreBackup string `json:"pre_restore_backup"`
UserVersion int64 `json:"user_version"`
GenerationID string `json:"generation_id"`
Forced bool `json:"forced"`
}
RestoreResult is what `approach db restore --json` prints. Keys match `db inspect`'s style so an operator reading both does not have to switch vocabularies.
func Restore ¶ added in v0.10.6
func Restore(opts RestoreOptions) (RestoreResult, error)
Restore puts a verified backup back in place of the live flow database.
The order below is the whole safety argument and must not be rearranged:
- verify the backup, before anything is touched — a restore that replaced a working database with an unreadable copy is strictly worse than the problem it was called for;
- scan the owners lease and refuse while any process holds the database open, naming each. Restoring under a live handle is strictly worse than migrating under one: the file is replaced outright, and the holder's open descriptor keeps referring to an unlinked inode;
- take the bootstrap lock — after the lease scan, the same total order the migration path uses;
- compare generations, and refuse a mismatch without --force;
- copy the live database aside, then replace it, then drop the stale WAL.
type Role ¶ added in v0.10.5
type Role int
Role names what an opener is allowed to do to the database it opens.
The zero value is RoleMigrator, deliberately: every bare NewStore(StoreOptions{Root: root}) in the tree — and there are well over a hundred of them in tests alone — keeps today's behaviour byte for byte. A restriction that is inherited by default is a restriction nobody can reason about; naming the role at the entry point is the whole point of this seam.
const ( // RoleMigrator may advance the schema. Exactly one process-level entry point // should hold it: `approach db migrate` and TUI startup. RoleMigrator Role = iota // RoleReader opens read-only. It never migrates, never repairs the root's // mode, never discards a staged database, and refuses writes in Go before // SQLite ever sees them. RoleReader // RoleWriter reads and writes records but will not advance the schema. RoleWriter )
type Session ¶
type Session struct {
Provider string `json:"provider,omitempty"`
SessionID string `json:"session_id,omitempty"`
LaunchID string `json:"launch_id,omitempty"`
Status string `json:"status,omitempty"`
StartedAt time.Time `json:"started_at,omitempty"`
EndedAt time.Time `json:"ended_at,omitempty"`
TranscriptPath string `json:"transcript_path,omitempty"`
}
Session references a provider session without duplicating transcript contents.
func LatestPhaseSession ¶
LatestPhaseSession returns the display/latest session for a phase. A session attached to the latest non-empty launch ID wins; otherwise timestamps and slice order provide deterministic legacy-record fallback.
type SessionAttachUpdate ¶
SessionAttachUpdate attaches a captured provider session to a Flow phase.
type StartMetadataUpdate ¶
type StartMetadataUpdate struct {
FlowID string
WorktreePath string
Branch string
BaseRef string
Commit string
PlanID string
PlanPath string
}
StartMetadataUpdate adds launch-start metadata that is only known after a Flow record has been allocated.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store reads and writes Flow rows in the artifact root's approach.db.
func NewStore ¶
func NewStore(opts StoreOptions) (*Store, error)
NewStore creates a Store rooted at an absolute artifact root.
func (*Store) ActivateUntrackedOwner ¶ added in v0.10.8
func (s *Store) ActivateUntrackedOwner(update UntrackedOwnerActivation) (FlowRecord, error)
func (*Store) AddChildPhase ¶
func (s *Store) AddChildPhase(update ChildPhaseUpdate) (FlowRecord, error)
AddChildPhase creates or updates a stable child phase under Implementation.
func (*Store) AddPhaseLaunchID ¶
func (s *Store) AddPhaseLaunchID(update PhaseLaunchUpdate) (FlowRecord, error)
AddPhaseLaunchID records a launch attempt. Fresh launches mark the phase running; resume launches of terminal phases preserve the terminal status.
func (*Store) AllocateID ¶ added in v0.10.2
AllocateID returns the canonical timestamped Flow ID CreateWithOptions would choose for title at this instant. Allocation is intentionally non-durable: it does not create or reserve a record, and concurrent callers may receive the same candidate. CreateWithOptions remains the atomic collision arbiter when the caller supplies the returned ID in FlowRecord.FlowID.
func (*Store) AttachSession ¶
func (s *Store) AttachSession(update SessionAttachUpdate) (FlowRecord, error)
AttachSession records a provider session against a phase. Re-attaching the same provider/session id updates the existing reference in place.
func (*Store) ClaimUntrackedOwner ¶ added in v0.10.8
func (s *Store) ClaimUntrackedOwner(update UntrackedOwnerClaim) (FlowRecord, error)
func (*Store) Close ¶ added in v0.10.0
Close releases the Store's database handles. A Store holds a pooled connection for its whole life, so callers that build one per operation must Close it or leak descriptors; long-lived callers may Close at shutdown or not at all. Using the Store after Close returns an error rather than panicking. It is safe to call more than once.
Callers that discard the error are not being sloppy: every record a caller wrote is already durable when its mutation returned, so a Close failure can only mean the final WAL checkpoint did not run. Turning that into a non-zero exit would report a successful mutation as a failure, which is the worse outcome for a CLI whose caller is usually an agent.
func (*Store) CloseFlow ¶ added in v0.10.1
func (s *Store) CloseFlow(update ClosureUpdate) (FlowRecord, error)
CloseFlow marks a Flow deliberately closed with a required reason. Phases are left exactly as they are, so the record still explains where work stopped; terminality is enforced by the launch guards, not by rewriting phase rows.
Unlike SetAutoMode and SetHeadless, a redundant call is an error rather than a no-op: a second close discards a reason the user typed, which is worth reporting.
func (*Store) Create ¶
func (s *Store) Create(record FlowRecord) (FlowRecord, error)
Create writes a new flow record with the default Flow phase graph. New records always start with auto mode enabled; callers that need manual mode should create the Flow, then opt out with SetAutoMode(false).
func (*Store) CreatePreparation ¶ added in v0.10.2
func (s *Store) CreatePreparation(record FlowRecord, opts CreateOptions) (FlowRecord, PreparationFinalizer, error)
CreatePreparation creates an ordinary receipt-less Flow and returns the sole capability that may stamp its preparation receipt.
func (*Store) CreateWithOptions ¶
func (s *Store) CreateWithOptions(record FlowRecord, opts CreateOptions) (FlowRecord, error)
CreateWithOptions writes a new flow record, optionally seeding empty phase lists from a preset instead of the default graph.
func (*Store) Delete ¶
Delete removes only the requested Flow row. It takes the launch/close reservation so a delete and same-ID recreate cannot replace a Flow while an admitted launch still owns that identity.
func (*Store) DemoteReconciledPhase ¶ added in v0.10.7
func (s *Store) DemoteReconciledPhase(update ReconciliationDemotionUpdate) (FlowRecord, error)
DemoteReconciledPhase persists a launch-control reconciliation marker with the phase transition. Agent-facing phase commands do not expose this method.
func (*Store) EnableEpicProgressionForPreparedFlow ¶ added in v0.10.2
func (s *Store) EnableEpicProgressionForPreparedFlow(update PreparedEpicProgressionUpdate) (EpicProgression, FlowRecord, error)
EnableEpicProgressionForPreparedFlow revalidates the exact child Flow and enables progression in one SQLite writer transaction.
func (*Store) HaltEpicProgression ¶ added in v0.10.5
func (s *Store) HaltEpicProgression(update EpicProgressionHaltUpdate) (EpicProgression, error)
HaltEpicProgression atomically moves authoritative active progression to halted. Only active state may halt; an already-halted record retains its first halt tuple and both timestamps, so the original cause always wins.
func (*Store) List ¶
func (s *Store) List(filter FlowFilter) ([]FlowRecord, error)
List returns records matching filter, sorted by UpdatedAt descending.
func (*Store) MarkManualMerge ¶
func (s *Store) MarkManualMerge(update ManualMergeUpdate) (FlowRecord, error)
MarkManualMerge completes the merge phase and records verified GitHub merge metadata for a PR that was merged outside approach.
The linked-plan sync runs AFTER the merge commits, giving four cases:
- sync succeeded: the committed record, nil.
- sync failed on an already-merged repeat: the record and nil. The error is DISCARDED rather than allowed to demote durable merge state.
- sync failed otherwise: the compensated record beside the error, or the current state when the compensation guard rejects — deliberately unlike SetPhase, which returns a zero record. A guard-rejected record is still merged and still reaches the TUI beside a merge-failure message; that is the truth of the durable state.
- sync failed and the compensation failed too: a zero record beside one error carrying both, matching SetPhase.
See syncLinkedPlanPhase for the ordering and its accepted windows.
func (*Store) MarkPhaseLaunchEnded ¶
func (s *Store) MarkPhaseLaunchEnded(update PhaseLaunchEndUpdate) (FlowRecord, error)
MarkPhaseLaunchEnded mirrors launch finalization into Flow-attached session metadata so recovery labels and reset eligibility do not depend on a later provider hook.
func (*Store) OpenDiagnostics ¶ added in v0.10.5
func (s *Store) OpenDiagnostics() OpenDiagnostics
OpenDiagnostics reports what this open observed about the root and the database. It is populated for every role and is the surface that makes a RoleReader's skipped checks — the root's 0700 assertion and the WAL assertion — observable rather than simply absent.
func (*Store) PrepareUntrackedOwnerTransport ¶ added in v0.10.8
func (s *Store) PrepareUntrackedOwnerTransport(update UntrackedOwnerActivation) (FlowRecord, error)
PrepareUntrackedOwnerTransport records an exact transport identity while the launcher reservation still owns admission. Once the caller confirms spawn, LauncherHandoffComplete transfers reconciliation to that exact transport.
func (*Store) Read ¶
func (s *Store) Read(flowID string) (FlowRecord, error)
Read returns one flow record by ID.
func (*Store) ReadEpicProgression ¶ added in v0.10.2
func (s *Store) ReadEpicProgression(key EpicProgressionKey) (EpicProgression, bool, error)
ReadEpicProgression distinguishes a missing row from a malformed or unreadable row. Missing means normal disabled to callers that project state.
func (*Store) ReconcileEpicProgressionSuccessor ¶ added in v0.10.2
func (s *Store) ReconcileEpicProgressionSuccessor(update EpicProgressionSuccessorUpdate) (EpicProgressionSuccessorResult, error)
ReconcileEpicProgressionSuccessor authoritatively classifies a prepared sequential successor without enabling or otherwise mutating progression. Inactive progression wins before any Flow condition is inspected.
func (*Store) RecoverReconciledPhase ¶ added in v0.10.7
func (s *Store) RecoverReconciledPhase(update PhaseRecoveryUpdate) (FlowRecord, error)
RecoverReconciledPhase atomically removes the exact stale launch from a reconciliation-demoted phase and derives that phase back to ready.
func (*Store) ReleaseUntrackedOwner ¶ added in v0.10.8
func (s *Store) ReleaseUntrackedOwner(update UntrackedOwnerRelease) (FlowRecord, error)
func (*Store) ReopenFlow ¶ added in v0.10.1
func (s *Store) ReopenFlow(flowID string) (FlowRecord, error)
ReopenFlow clears the closure from a closed Flow, restoring the launchability it had before the close.
func (*Store) ReplaceUntrackedOwner ¶ added in v0.10.8
func (s *Store) ReplaceUntrackedOwner(update UntrackedOwnerReplacement) (FlowRecord, error)
ReplaceUntrackedOwner reclaims a proven-dead active owner and installs a new reservation in the same writer transaction.
func (*Store) ReserveAgentLaunch ¶ added in v0.10.1
func (s *Store) ReserveAgentLaunch(flowID string) (FlowRecord, func(), error)
ReserveAgentLaunch orders an agent spawn with CloseFlow. The caller must hold the returned reservation until the terminal or external launcher has either started or failed, so a close and a launch have one authoritative cross-process ordering.
func (*Store) ReserveEpicProgressionSuccessor ¶ added in v0.10.2
ReserveEpicProgressionSuccessor holds the Flow's launch/close lock while sequential progression classifies it. Unlike launch reservations, this reservation intentionally permits missing and closed Flows so the caller can apply progression-first inactive precedence to every Flow condition.
func (*Store) ReserveRepairLaunch ¶ added in v0.10.1
func (s *Store) ReserveRepairLaunch(flowID string) (FlowRecord, func(), error)
ReserveRepairLaunch holds the Flow's cross-process repair-launch lock and returns the current record. Callers must invoke the returned release function after the terminal has either started or failed to start. CloseFlow takes the same lock, so exactly one ordering wins: a completed close makes this call reject, while an admitted repair starts before a concurrent close proceeds. The OS releases the advisory lock automatically if the process exits.
func (*Store) ResetAwaitingSessionPhase ¶
func (s *Store) ResetAwaitingSessionPhase(update PhaseResetUpdate) (FlowRecord, error)
ResetAwaitingSessionPhase removes an orphaned latest launch attempt from a running phase and lets approach derive it back to ready. This is intentionally not part of the agent-facing phase transition table.
func (*Store) ResetRecoverableRunningPhase ¶
func (s *Store) ResetRecoverableRunningPhase(update PhaseResetUpdate) (FlowRecord, error)
ResetRecoverableRunningPhase removes the latest stale launch attempt from a running phase and lets approach derive it back to ready. This is intentionally not part of the agent-facing phase transition table.
func (*Store) RestartPhase ¶
func (s *Store) RestartPhase(update PhaseRestartUpdate) (FlowRecord, error)
RestartPhase atomically restarts a blocked or needs-attention phase as running.
func (*Store) Revalidate ¶ added in v0.10.6
Revalidate forces the exact answer the throttled check only approximates. A caller about to do something it cannot undo — and `db restore` itself — should ask rather than wait out the window.
func (*Store) Root ¶ added in v0.10.6
Root is the canonical state root this store opened, after symlink resolution. Callers that place files beside the database — the owners lease especially — must use this rather than the path they passed in, or a symlinked spelling would put their file in a directory nothing else looks at.
func (*Store) SetAutoMerge ¶ added in v0.10.8
func (s *Store) SetAutoMerge(update AutoMergeUpdate) (FlowRecord, error)
SetAutoMerge sets or clears one Flow's automatic merge-phase override.
func (*Store) SetAutoMode ¶
func (s *Store) SetAutoMode(update AutoModeUpdate) (FlowRecord, error)
SetAutoMode enables or disables TUI-owned automatic phase launching for one Flow.
func (*Store) SetEpicProgression ¶ added in v0.10.2
func (s *Store) SetEpicProgression(update EpicProgressionUpdate) (EpicProgression, error)
SetEpicProgression atomically applies one active, normal-off, or done target. Redundant writes preserve both timestamps and perform no row update.
func (*Store) SetHeadless ¶ added in v0.10.0
func (s *Store) SetHeadless(update HeadlessUpdate) (FlowRecord, error)
SetHeadless enables or disables headless manual launches for one Flow.
func (*Store) SetIssue ¶
func (s *Store) SetIssue(update IssueUpdate) (FlowRecord, error)
SetIssue validates and persists the issue metadata reported by an agent.
func (*Store) SetMerge ¶
func (s *Store) SetMerge(update MergeUpdate) (FlowRecord, error)
SetMerge validates and persists the merge metadata reported by an agent.
func (*Store) SetPR ¶
func (s *Store) SetPR(update PRUpdate) (FlowRecord, error)
SetPR validates and persists the pull request metadata reported by an agent.
func (*Store) SetPhase ¶
func (s *Store) SetPhase(update PhaseUpdate) (FlowRecord, error)
SetPhase validates and persists one phase update on an existing flow.
The linked-plan sync runs AFTER the phase change commits, so the return contract has four cases:
- sync succeeded: the committed record, nil.
- sync failed on a repeat of an already-completed phase: the committed record and nil, because the durable state is correct and the plan write is idempotent. The sync error is DISCARDED, so this recovery path reports success even when it failed again; the signal is the linked plan's own phase status, exactly as on MarkManualMerge's retry.
- sync failed otherwise: a ZERO record beside the sync error, whether the needs_attention compensation was persisted by the second update or its guard declined to write. MarkManualMerge deliberately differs and returns its compensated record; both halves are pinned by tests.
- sync failed and the compensation failed too: a zero record beside one error carrying both, with %w on the sync error.
See syncLinkedPlanPhase for the ordering and its accepted windows.
func (*Store) SetPhaseAgentSettings ¶ added in v0.10.1
func (s *Store) SetPhaseAgentSettings(update PhaseAgentSettingsUpdate) (FlowRecord, error)
SetPhaseAgentSettings atomically replaces only one phase's agent settings. It intentionally bypasses Flow normalization, readiness derivation, linked plan synchronization, and unrelated phase validation: settings affect only a future launch and legacy records must remain otherwise byte-for-byte stable.
func (*Store) SetPlanLink ¶
func (s *Store) SetPlanLink(update PlanLinkUpdate) (FlowRecord, error)
SetPlanLink validates and persists the saved plan artifact linked to a Flow.
func (*Store) SetStartMetadata ¶
func (s *Store) SetStartMetadata(update StartMetadataUpdate) (FlowRecord, error)
SetStartMetadata persists branch/worktree/plan metadata discovered while starting a Flow. Empty fields leave existing values unchanged.
type StoreOptions ¶
type StoreOptions struct {
Root string
Now func() time.Time
// LockTimeout bounds bootstrap lease and SQLite writer acquisition waits.
LockTimeout time.Duration
Presets []Preset
// AllowDevLiveMigration acknowledges that a development build may advance
// the schema of the database a *released* build owns. Without it that one
// case is refused; every other root, and every release build, is unaffected.
// Surfaced as --allow-dev-live-migration and
// APPROACH_ALLOW_DEV_LIVE_MIGRATION=1.
AllowDevLiveMigration bool
// Role names what this open is allowed to do. The zero value, RoleMigrator,
// is today's behaviour; every non-test caller names one explicitly and a
// call-site test enforces that.
Role Role
// RootExplicit reports that Root was named by a flag or an environment
// variable rather than falling back to config or the built-in default. It
// decides only whether a RoleReader creates a missing root or reports the
// typo. The zero value keeps today's create-if-absent behaviour.
RootExplicit bool
// BackupDir is where a migration writes its pre-migration copy. Empty means
// <root>/backups/. Surfaced as `approach db migrate --backup-dir`.
BackupDir string
// OwnerNonce is the caller's own internal/dblease holder, excluded from the
// owners scan a migration runs. A long-lived migrator (TUI startup) takes
// its lease FIRST and keeps it across the migration; without this it would
// refuse to migrate because of itself.
OwnerNonce string
}
StoreOptions configures a Store.
type UntrackedOwner ¶ added in v0.10.8
type UntrackedOwner struct {
LaunchID string `json:"launch_id"`
Role UntrackedOwnerRole `json:"role"`
State UntrackedOwnerState `json:"state"`
Transport UntrackedOwnerTransport `json:"transport,omitzero"`
LauncherPID int `json:"launcher_pid,omitempty"`
LauncherToken string `json:"launcher_token,omitempty"`
ReservedAt time.Time `json:"reserved_at"`
ActivatedAt time.Time `json:"activated_at,omitempty"`
EndedAt time.Time `json:"ended_at,omitempty"`
}
UntrackedOwner is the durable phase-untracked worktree owner. Ended owners remain as fenced lifecycle history until the next claim replaces them.
type UntrackedOwnerActivation ¶ added in v0.10.8
type UntrackedOwnerActivation struct {
FlowID string
LaunchID string
Transport UntrackedOwnerTransport
// LauncherHandoffComplete clears the temporary launcher fence after the
// exact transport has started, so later reconciliation probes that identity.
LauncherHandoffComplete bool
}
type UntrackedOwnerClaim ¶ added in v0.10.8
type UntrackedOwnerClaim struct {
FlowID string
Owner UntrackedOwner
}
type UntrackedOwnerRelease ¶ added in v0.10.8
type UntrackedOwnerReplacement ¶ added in v0.10.8
type UntrackedOwnerReplacement struct {
FlowID string
ExpectedLaunchID string
Owner UntrackedOwner
}
type UntrackedOwnerRole ¶ added in v0.10.8
type UntrackedOwnerRole string
const ( UntrackedOwnerWorktreeAgent UntrackedOwnerRole = "worktree_agent" UntrackedOwnerAutofix UntrackedOwnerRole = "autofix" UntrackedOwnerRepair UntrackedOwnerRole = "repair" )
type UntrackedOwnerState ¶ added in v0.10.8
type UntrackedOwnerState string
const ( UntrackedOwnerReserved UntrackedOwnerState = "reserved" UntrackedOwnerLive UntrackedOwnerState = "live" UntrackedOwnerEnded UntrackedOwnerState = "ended" )
type UntrackedOwnerTransport ¶ added in v0.10.8
type UntrackedOwnerTransport struct {
Kind UntrackedTransportKind `json:"kind"`
Socket string `json:"socket,omitempty"`
Session string `json:"session,omitempty"`
Window string `json:"window,omitempty"`
PID int `json:"pid,omitempty"`
ProcessToken string `json:"process_token,omitempty"`
}
UntrackedOwnerTransport identifies the exact transport whose liveness proves whether an owner may be reclaimed. Only the fields used by Kind are set.
type UntrackedTransportKind ¶ added in v0.10.8
type UntrackedTransportKind string
const ( UntrackedTransportLauncher UntrackedTransportKind = "launcher_process" UntrackedTransportRepoTmux UntrackedTransportKind = "repo_tmux" UntrackedTransportEmbeddedTmux UntrackedTransportKind = "embedded_tmux" UntrackedTransportDirect UntrackedTransportKind = "direct_embedded" )
Source Files
¶
- actionable.go
- backend.go
- backend_sqlite.go
- backup.go
- bead_slot.go
- epic_progression.go
- generation_guard.go
- graph.go
- inspect.go
- legacy_source.go
- manifest.go
- owners_lease.go
- plan_sync.go
- preparation.go
- preset.go
- restore.go
- role.go
- session_select.go
- sidecar.go
- sqlite_bootstrap.go
- storage_codec.go
- store.go
- transitions.go
- untracked_owner.go