Documentation
¶
Overview ¶
cross_daemon.go is the HTTP forwarder the SOR-1184 routing resolver's `forward` decision dispatches through. When resolveCreateDestination resolves a prospective issue to a peer daemon, CrossDaemonClient proxies the create to that peer's POST /v1/issues surface, carrying the source daemon's bearer + the X-Sorcerer-Forwarded-From / X-Sorcerer-Route-Audit-ID headers the peer's dual-auth path (SOR-1214) and inbound idempotence (SOR-1216) consume.
Package daemon hosts the long-running sorcererd process: the event loop, external pollers, the scheduler-pass debouncer, the session supervisor, and the wiring that turns Linear/GitHub/agent events into Issue and Session SM transitions.
The daemon has NO unified tick. Instead:
- Internal events (session results, request arrivals, operator commands, signals) fire reactions immediately via channels.
- External state is polled by independent goroutines, each at its own natural cadence (e.g. PR poller every 30s while in-flight).
- Per-session timers (max-age, idle activity) are individual time.AfterFunc, not a global sweep.
- Scheduler passes run debounced — many triggering events within ~100ms collapse to a single pass.
See docs/architecture.md for the full design rationale.
flow_liveness.go hosts the FlowLivenessMonitor (SOR-2630): an independent supervisor goroutine that watches EVENT-FLOW liveness — whether the scheduler-pass producer (the Debouncer) is servicing triggers — and self-heals when it isn't.
This is a distinct monitored property from the stuck-bash watchdog (internal/watchdog), which detects a LIVE daemon whose Bash tool calls are stuck. The flow-liveness monitor detects a LIVE daemon whose event PRODUCER has silently died: the main loop is healthy in its select but starved, every issue frozen mid-state, no sweep running because every sweep rides the same dead loop. That outage class previously had zero autonomous detection — the daemon idled indefinitely, bounded only by operator attention.
Signal: the Debouncer exposes LastTriggerAt() (most recent trigger) and LastPassAt() (most recent COMPLETED pass). A pending trigger that has gone unserviced longer than the threshold (LastTriggerAt after LastPassAt, and now-LastTriggerAt > threshold) means the producer is wedged or dead — a trigger landed but no pass serviced it. An idle daemon (no trigger pending → LastTriggerAt <= LastPassAt) never trips the monitor, so the event-driven scheduler design is preserved without a periodic self-ping.
Self-heal: on first detection the monitor logs loudly, emits a durable scheduler_flow_stall_detected audit event, and re-triggers the producer (a cheap attempt to wake a wedged-but-alive goroutine). If the stall persists into the next check interval the re-trigger did not restore flow — the producer is truly gone — so the monitor calls exitFn(1) (os.Exit(1) in production) and process supervision restarts the daemon. Fail loud; never idle silently.
route.go defines the pure, side-effect-free routing resolver every cross-daemon routing primitive consumes (forward mechanism, recognizer guard, operator verbs, body classifier, triager-assist). Given a request body, the repos it names, and a topology snapshot, it returns a RouteDecision describing whether to handle the request locally, forward it to a peer daemon, or reject it outright.
The resolver does NO I/O: it consults Topology.OwnerOf / IsStale / LastRefresh / Self only. Audit-event emission, transport, and config reads all happen at the call-site in the next-phase wire-up.
Structural precedent: internal/triager/router.go (a single-file dispatch helper paired with a co-located table-driven test). The triager router is a different domain — confidence-banded action routing — but the shape (one resolver function, one Decision struct, exhaustive table-driven tests) is the same.
triager_dispatcher.go is the event-driven triager dispatcher (SOR-258). It closes the polling-delay autonomy gap where any issue transitioning to blocked_user (or planning issue to plan_blocked) would wait up to a full triager polling interval before the triager looked at it. The dispatcher hooks the supervisor's transition-write path: a transition INTO the blocked set notifies the dispatcher, the dispatcher debounces over a short window to coalesce storms (boot recovery, batch transitions), then runs ONE triager Tick on behalf of the cohort.
The polled triager poller stays in place as a backstop and is rewired to route its Tick through this dispatcher's RunPolled so a polled tick and an event-driven dispatch can never run in parallel.
triager_executors.go is the production implementation of triager.Executor — the daemon-side surface the router calls into to apply, propose, or reject one triager action. The executor's per-action methods are responsible for:
- Re-verifying any cited evidence_commit_sha against gh via the CommitVerifier helper (defense-in-depth: the LLM already cited it; we MUST re-verify before applying because the daemon's tool- policy gate denies the executor's gh access at action time).
- Idempotency: a second call with the same args is a no-op. The check happens on the main goroutine inside the SM-mutation handler so it observes the post-mutation snapshot.
- Writing the triager_decisions audit row BEFORE the SM mutation so a crash mid-apply leaves the audit trail consistent (per AC).
- Submitting the typed EventTriagerActionApply onto the daemon's main loop (the single-writer invariant on d.state) and blocking on the reply.
- Appending the right lifecycle event (triager_action_applied / triager_action_proposed / triager_action_rejected) to the events table.
The Executor's actual SM mutations re-use the existing daemon surfaces — applyIssueTransition / applyPlanTransition (Force=true) for force_converge_to_terminal; RemoveDependency + in-memory edge drop for unblock_via_dep_drop; rejectOrphanProposal for cascade_abandon_orphan_proposal. No new SM helpers are introduced; triager actions are an alternate trigger for paths the supervisor already exposes.
triager_gh_verify.go holds the gh-verification helper shared by the triager Executor's evidence_commit_sha-bearing actions (unblock_via_dep_drop, force_converge_to_terminal). The helper signature is `(repo string, ok bool, err error)`: it iterates over the project's repos in deterministic input order calling CommitVerifier(repo, sha) for each, returning the first repo where the SHA resolves. A 404 in every repo returns ErrSHANotFound; any non-404 error (rate limit, auth failure) aborts and surfaces back to the caller.
Each per-repo call gets a 10s context timeout (AC mandate). The daemon-side production CommitVerifier shells out to `gh api repos/<owner>/<repo>/commits/<sha>`; tests inject a fake.
Index ¶
- Constants
- Variables
- func ActionsWaitTimeoutAnchor(buffered time.Time, latestRunStartedAts []time.Time) time.Time
- func ActionsWaitTimeoutFires(buffered time.Time, latestRunStartedAts []time.Time, now time.Time, ...) bool
- func CapabilitySignatureFromClass(class string) string
- func ClassifyGateFailureEnvironment(stdout, stderr []byte, exitCode int) (transient bool, signatures []string)
- func ComposeProgressLine(state string, metrics *LiveSessionMetrics, baseline *issuestore.StageBaseline, ...) apiclient.ProgressLine
- func DefaultDefectDomain(projectLanguage string) string
- func DetectReposConflict(body string, declaredRepos []string, topology *Topology) (bool, string, []string)
- func EvaluateActivationProbe(ctx context.Context, probe *dsl.ActivationProbe, pctx ProbeContext) (string, *role.ProbeEvidence, error)
- func EventTriggersScheduler(k EventKind) bool
- func FormatAbsentNativeHelperMessage(lang string, absent []role.PropTestHelperObligation) string
- func HermeticGenerationBarForProject(cfg *config.Config, implementerSpec []byte) string
- func IntegrationDispatchGateHeld(throttleHeld, authDeadHeld bool) bool
- func IsPlanAddChildrenStateRejection(err error) bool
- func IsPristineMainGateFailure(stdout []byte) bool
- func IsTracematrixVerifyCheck(f FailedPlanPRCheck) bool
- func IssueTransitionTriggersScheduler(from, to sm.IssueState) bool
- func NewTriagerExecutor(d *Daemon) triager.Executor
- func ParseAcceptanceCriteria(body string) []string
- func ParseGitDiffNameOnly(raw string) []string
- func PlanExternalMergeCandidateState(s sm.PlanState) bool
- func ReclaimGateScratch(ctx context.Context, projectRoot string) (reclaimedBytes int64, err error)
- func RegisterRequirementExecutor(mode string, exec RequirementExecutor)
- func RegisteredExecutorModes() []string
- func RequiredBenchmarkTargets(spec *dsl.Spec, verifies []string, symbolPattern string) []role.BenchmarkTargetObligation
- func RequiredPropTestHelpers(spec *dsl.Spec, verifies []string) []role.PropTestHelperObligation
- func RequiredPropTestHelpersByRequirementLanguage(trace spectrace.RequirementTrace, fallbackLang string, spec *dsl.Spec, ...) []role.PropTestHelperObligation
- func RequiredPropTestHelpersForLanguage(lang string, spec *dsl.Spec, verifies []string) []role.PropTestHelperObligation
- func ResolveInputsClosure(ctx context.Context, readSet []string, cmd string, workDir string) (relevance.InputsClosure, error)
- func ResolveRequirementBackend(entry spectrace.TraceEntry, projectLang string) (proptestbackend.Interpreter, bool)
- type AbortRoleSessionsResult
- type ActivationListResult
- type ActivationPollObservation
- type AssemblyFixerDispatcher
- type AssemblyFixerEventPayload
- type BlockedUserPROpenResultEvent
- type CIRunChecker
- type CPBChildSquashResult
- type CPBIntegrationReviewEntry
- type CPBMergeResolverResult
- type CPBSquashFailureKind
- type Cadence
- type CallCounts
- type CapabilityGapParkPayload
- type CapabilityGapReleaseProbePlanResult
- type CapabilityGapReleaseProbeResultPayload
- type ChildActualDiffResolver
- type ChildSquasher
- type CommentMutationResult
- type CommitVerifier
- type Config
- type ConversationRecycler
- type CrashRecoveryPRDiscoveryResultEvent
- type CreateMutationResult
- type CrossDaemonClient
- func (c *CrossDaemonClient) ForwardCreate(ctx context.Context, targetURL string, body []byte, auditID int64) (destKey string, destDaemon string, err error)
- func (c *CrossDaemonClient) ForwardMove(ctx context.Context, targetURL string, body []byte, auditID int64, ...) (destKey string, destDaemon string, err error)
- type Daemon
- func (d *Daemon) AbortActiveRoleSessions(ctx context.Context, role, reason string) (int, error)
- func (d *Daemon) AddPoller(p *Poller)
- func (d *Daemon) AmendBodySizingWarning(newDescription string, forceLargeScope bool) string
- func (d *Daemon) AppendEscalation(esc store.Escalation)
- func (d *Daemon) ApplyOperatorAbortRoleSessions(ctx context.Context, role, reason string) (int, error)
- func (d *Daemon) ApplyOperatorActivationOverride(ctx context.Context, key, rid, reason string) error
- func (d *Daemon) ApplyOperatorAddBlocker(ctx context.Context, blockerKey, blockedKey string) error
- func (d *Daemon) ApplyOperatorAddPlanChildren(ctx context.Context, in httpapi.PlanAddChildrenInput) ([]string, []string, error)
- func (d *Daemon) ApplyOperatorAdoptPR(ctx context.Context, in httpapi.IssueAdoptPRInput) (string, error)
- func (d *Daemon) ApplyOperatorAmend(ctx context.Context, key, newDescription, reason string) error
- func (d *Daemon) ApplyOperatorCancel(ctx context.Context, key, reason string, cascadePlanAbandon bool) error
- func (d *Daemon) ApplyOperatorComment(ctx context.Context, key, body string) (int64, int64, error)
- func (d *Daemon) ApplyOperatorCreateIssue(ctx context.Context, in httpapi.IssueCreateInput) (string, string, bool, error)
- func (d *Daemon) ApplyOperatorListActivating(ctx context.Context) ([]httpapi.ActivationPlanView, error)
- func (d *Daemon) ApplyOperatorRefactorPlan(ctx context.Context, in httpapi.PlanRefactorInput) (httpapi.PlanRefactorResult, error)
- func (d *Daemon) ApplyOperatorReferBack(ctx context.Context, key, reason string, concerns []role.ReviewConcern) error
- func (d *Daemon) ApplyOperatorRekey(ctx context.Context, oldKey string) (string, error)
- func (d *Daemon) ApplyOperatorRemoveBlocker(ctx context.Context, blockerKey, blockedKey string) error
- func (d *Daemon) ApplyOperatorReplaceIssue(ctx context.Context, in httpapi.IssueCreateInput, oldKey string) (string, error)
- func (d *Daemon) ApplyOperatorRetitle(ctx context.Context, key, newTitle, reason string) error
- func (d *Daemon) ApplyOperatorRouteMove(ctx context.Context, sourceKey, targetDaemon, reason string) (string, string, error)
- func (d *Daemon) ApplyOperatorSetPriority(ctx context.Context, key string, newPriority int, reason string) error
- func (d *Daemon) ApplyOperatorSpecAmend(ctx context.Context, key, narrowChange, reason, scope string) error
- func (d *Daemon) ApplyOperatorSpecApprove(ctx context.Context, key, reason string) error
- func (d *Daemon) ApplyOperatorSpecPatch(ctx context.Context, key string, findingID int64, patches json.RawMessage) error
- func (d *Daemon) ApplyOperatorSpecSuppressFinding(ctx context.Context, key string, findingID int64, reason string) error
- func (d *Daemon) ApplyOperatorSpecVerify(ctx context.Context, key string) error
- func (d *Daemon) ApplyOperatorStewardAnswerQuestion(ctx context.Context, id int64, chosenLabel string) error
- func (d *Daemon) ApplyOperatorStewardLedgerCreate(ctx context.Context, row issuestore.StewardLedgerRow) (issuestore.StewardLedgerRow, error)
- func (d *Daemon) ApplyOperatorStewardLedgerExpire(ctx context.Context, id int64) error
- func (d *Daemon) ApplyOperatorStewardLedgerUpdate(ctx context.Context, row issuestore.StewardLedgerRow) error
- func (d *Daemon) ApplyOperatorTransition(ctx context.Context, key, toState, reason string) error
- func (d *Daemon) ApplyProjectApprovedChildren(ctx context.Context, proposalID int64) error
- func (d *Daemon) ApplyRecoverSweep(ctx context.Context, kinds []string) (httpapi.RecoverResponse, error)
- func (d *Daemon) ApplyStewardBlockIntent(ctx context.Context, issueKey, diagnosis, reason string) error
- func (d *Daemon) BackfillOrphanAdoptionMetadata(ctx context.Context, fetch OrphanAdoptionPRFetcher)
- func (d *Daemon) BootRecover(ctx context.Context)
- func (d *Daemon) ClassifyRoute(_ context.Context, body string, repos []string) (RouteDecision, error)
- func (d *Daemon) CountIssuesWithOpenPRs(ctx context.Context) int
- func (d *Daemon) CreateAutonomousIssue(ctx context.Context, in httpapi.IssueCreateInput) (string, error)
- func (d *Daemon) DryRunCreateIssue(ctx context.Context, in httpapi.IssueCreateInput, ...) (*httpapi.IssueDryRunResult, error)
- func (d *Daemon) EmitPollerRestarted(name, cause string)
- func (d *Daemon) EmitPredictionCheck(child *sm.Issue, diffFiles []string, fileContent map[string][]byte, ...)
- func (d *Daemon) EventChannel() chan<- Event
- func (d *Daemon) IssueStore() issuestore.IssueStore
- func (d *Daemon) LookupTransitionLegality(key string) (state string, legal []string, found bool)
- func (d *Daemon) PRSnapshotsAll(ctx context.Context) (map[string][]PRSnapshot, error)
- func (d *Daemon) PRSnapshotsForIssue(ctx context.Context, key string) ([]PRSnapshot, error)
- func (d *Daemon) ProgressByIssue(ctx context.Context) (map[string]*apiclient.ProgressLine, error)
- func (d *Daemon) RecomputeStageBaselines(ctx context.Context, now time.Time) error
- func (d *Daemon) RequireOperatorApproval() bool
- func (d *Daemon) Run(ctx context.Context) error
- func (d *Daemon) RunSchedulerPass(ctx context.Context) error
- func (d *Daemon) SetEventBroadcast(fn func(issuestore.Event)) error
- func (d *Daemon) SetEventHook(fn func(Event))
- func (d *Daemon) SetPlanActivatingPoller(p *Poller)
- func (d *Daemon) SetPlanPRMergePendingCIPoller(p *Poller)
- func (d *Daemon) SetTriagerNotifyBlocked(fn func(issueKey string))
- func (d *Daemon) State() *store.State
- func (d *Daemon) StatusSnapshot() (map[string]any, error)
- func (d *Daemon) Submit(ev Event)
- func (d *Daemon) SubmitNonblocking(ev Event) bool
- func (d *Daemon) SubmitOperatorCommand(ctx context.Context, command string) error
- func (d *Daemon) SweepCapabilityGapRelease(ctx context.Context, now time.Time) int
- func (d *Daemon) SweepDedupePending(ctx context.Context) (resolved, stillPending, foundDup int)
- func (d *Daemon) SweepEphemeralWorktreePrune(ctx context.Context) int
- func (d *Daemon) SweepPerIssueWorktreeGC()
- func (d *Daemon) SweepPlanActivating(ctx context.Context, now time.Time) int
- func (d *Daemon) SweepPlanAssembleStaleRerere(ctx context.Context, now time.Time) int
- func (d *Daemon) SweepPlanMainMerge(ctx context.Context, now time.Time) int
- func (d *Daemon) SweepPlanPRMergePendingCI(ctx context.Context, now time.Time) int
- func (d *Daemon) Topology() *Topology
- func (d *Daemon) TriggerPoller(name string) bool
- type DaemonInfo
- type Debouncer
- func (d *Debouncer) AdvanceQuietForTest()
- func (d *Debouncer) IsRunning() bool
- func (d *Debouncer) LastPassAt() time.Time
- func (d *Debouncer) LastTriggerAt() time.Time
- func (d *Debouncer) PassCount() uint64
- func (d *Debouncer) RestartCount() uint64
- func (d *Debouncer) SetPanicSink(fn func(cause string))
- func (d *Debouncer) SetRestartBackoff(backoff time.Duration)
- func (d *Debouncer) Start(ctx context.Context)
- func (d *Debouncer) Stop()
- func (d *Debouncer) Trigger()
- func (d *Debouncer) TriggerCount() uint64
- type DedupeDispatcher
- type DedupeSnapshotIssue
- type DedupeSnapshotPR
- type DedupeSnapshotResult
- type DiffStatTotals
- type DiscoveredPR
- type DiscoveryRoleConfig
- type DispatchOutcome
- type DispatchSituation
- type DuplicateEntry
- type DuplicateError
- type ErrRefactorPlanAborted
- type Event
- func NewActivationPollObservedEvent(planKey string, allSatisfied bool, failedRIDs []string, ...) Event
- func NewAssemblyFixerResultEvent(planKey, sessID, triggeringChildKey, resolverDiagnosis string, ...) Event
- func NewCapabilityGapReleaseProbeResultEvent(results []CapabilityGapReleaseProbePlanResult) Event
- func NewCapabilityGapReleaseSweepEvent() Event
- func NewGitHubPRsPolledEvent(snapshots ...PRSnapshot) Event
- func NewImplementerResultEvent(sessionID string, result role.ImplementerResult, sessionError error) Event
- func NewMainMergeRelevanceResultEvent(trigger string, relevant, deferred []string) Event
- func NewMergeResultEvent(sessionID string, results []gh.MergeSetResult, sessionError error) Event
- func NewMergeResultEventWithTail(sessionID string, tail *LandingGateTail, class GateFailureClass) Event
- func NewOperatorCommandEvent(cmd string, reply chan<- error) Event
- func NewOperatorIssueMutationEvent(m Mutation) Event
- func NewOrphanPRsDiscoveredEvent(refs []OrphanPRRef) Event
- func NewPerChildPROpenResultEvent(sessionID, summary, requirementTraceJSON, verifiedSpecVersion string, ...) Event
- func NewPerIssueWorktreeGCSweepEvent() Event
- func NewPlanAssembleStaleRerereSweepEvent() Event
- func NewPlanMainMergeSweepEvent() Event
- func NewPlanMainMergeTipProbeResultEvent(perRepoTips, probeErrors map[string]string) Event
- func NewPlanPROpenResultEvent(sessionID string, results []PlanPROpenResult, sessionError error) Event
- func NewPlanPRPendingCIObservedEvent(planKey string, class pendingCIClassification, reason string, now time.Time, ...) Event
- func NewPlanReviewerResultEvent(sessionID string, verdict role.PlanReviewerVerdict, sessionError error) Event
- func NewPlannerResultEvent(sessionID, originIssue string, result role.PlannerResult, errs []string, ...) Event
- func NewRecoverySweepEvent(kinds []string, reply chan<- httpapi.RecoverResponse) Event
- func NewRequestEvent(path string) Event
- func NewReviewerResultEvent(sessionID string, verdict role.ReviewerVerdict, sessionError error) Event
- func NewScheduleEvalEvent() Event
- func NewSessionCrashedEvent(sessionID, lastMessage string) Event
- func NewSessionInitFailedEvent(f SessionInitFailureEvent) Event
- func NewSessionLifecycleEvent(sessionID, phase, stateDir string) Event
- func NewSessionSpawnFailedEvent(f SpawnFailureEvent) Event
- func NewSessionTimeoutEvent(sessionID string) Event
- func NewShutdownEvent() Event
- func NewSpecDrafterResultEvent(sessionID, taskKind string, result role.SpecDrafterResult, sessionError error) Event
- func NewSpecReviewerVerdictEvent(sessionID string, verdict role.SpecReviewerVerdict, sessionError error) Event
- func NewStewardEvent(subKind string) Event
- func NewStewardReactiveWakeEvent() Event
- type EventKind
- type FailedPlanPRCheck
- type FlowLivenessMonitor
- type ForceConvergeForTestPayload
- type ForwardError
- type ForwardRefLintResult
- type ForwardRequiredError
- type GateFailureClass
- type ImplementerCycleDiffStater
- type ImplementerDispatcher
- type ImplementerVerifyChainResult
- type IssuePRClosedRecovery
- type IssuePRDraftConverted
- type IssueTransitionEffects
- type LLMVerifierRunner
- type LandingGateTail
- type LiveSessionMetrics
- type LivenessSessionWriteback
- type Logger
- type LoopHarness
- func (h *LoopHarness) AssemblyFixerDispatcher() AssemblyFixerDispatcher
- func (h *LoopHarness) CapturedAssemblyFixerTasks() []role.AssemblyFixerTask
- func (h *LoopHarness) CapturedImplementerTasks() []role.ImplementerTask
- func (h *LoopHarness) CapturedMergeResolverTasks() []role.MergeResolverTask
- func (h *LoopHarness) CapturedReviewerTasks() []role.ReviewerTask
- func (h *LoopHarness) CapturedStewardTasks() []role.StewardTask
- func (h *LoopHarness) ConversationRunner(inner role.ConversationRunner) role.ConversationRunner
- func (h *LoopHarness) Counts() CallCounts
- func (h *LoopHarness) Faulter() faultinjection.Faulter
- func (h *LoopHarness) ImplementerDispatcher() ImplementerDispatcher
- func (h *LoopHarness) Issuestore(inner issuestore.IssueStore) issuestore.IssueStore
- func (h *LoopHarness) MergeResolverDispatcher() MergeResolverDispatcher
- func (h *LoopHarness) PlanMainMerger() PlanMainMerger
- func (h *LoopHarness) Reset()
- func (h *LoopHarness) ReviewerDispatcher() ReviewerDispatcher
- func (h *LoopHarness) StageAssemblyFixerEnvelopeRejection(parseErr error, coercions []string) *LoopHarness
- func (h *LoopHarness) StageMainMergeGateFailure(stderr string, exitCode int) *LoopHarness
- func (h *LoopHarness) StageReviewerVerdict(v role.ReviewerVerdict) *LoopHarness
- func (h *LoopHarness) StageSpecDraft() *LoopHarness
- func (h *LoopHarness) StageSpecReviewVerdict(v role.SpecReviewerVerdict) *LoopHarness
- func (h *LoopHarness) StageSpecRevise() *LoopHarness
- func (h *LoopHarness) StageSpecVerifyFindings() *LoopHarness
- func (h *LoopHarness) StageStewardResult(r role.StewardResult) *LoopHarness
- func (h *LoopHarness) StagedAssemblyFixerRejectionCount() int
- func (h *LoopHarness) StagedMainMergeGateFailureCount() int
- func (h *LoopHarness) StagedReviewerVerdictCount() int
- func (h *LoopHarness) StagedSpecDraftCount() int
- func (h *LoopHarness) StagedSpecReviewVerdictCount() int
- func (h *LoopHarness) StagedSpecReviseCount() int
- func (h *LoopHarness) StagedSpecVerifyFindingsCount() int
- func (h *LoopHarness) StewardDispatcher() StewardDispatcher
- func (h *LoopHarness) StewardTaskCount() int
- func (h *LoopHarness) WithFaulter(f faultinjection.Faulter) *LoopHarness
- func (h *LoopHarness) WrapOpenPlanPR(...) ...
- func (h *LoopHarness) WrapViewPR(inner func(ctx context.Context, prURL string) (*gh.PR, error)) func(ctx context.Context, prURL string) (*gh.PR, error)
- type MainMergeRelevanceResultEvent
- type MergeResolverDispatcher
- type MergeRunner
- type Mutation
- type MutationKind
- type OrphanAdoptionPRFetcher
- type OrphanPRRef
- type Outcome
- type PRResolver
- type PRResolverResult
- type PRSnapshot
- type PRSnapshotsAllResult
- type PRSnapshotsForIssueResult
- type PendingTransition
- type PickResultEvent
- type PlanAddChildrenMutationResult
- type PlanBranchCreateRepoResult
- type PlanBranchCreateResult
- type PlanBranchRecreateRepoResult
- type PlanExternalMergeRecovery
- type PlanMainMergeFailureKind
- type PlanMainMergeRepoResult
- type PlanMainMergeResultEvent
- type PlanMainMergeTipProbeResultEvent
- type PlanMainMerger
- type PlanPROpenResult
- type PlanPRPendingCIObservation
- type PlanRefactorMutationResult
- type PlanReviewerDispatcher
- type PlanTransitionEffects
- type PlannerDispatcher
- type PlannerInputs
- type Poller
- func (p *Poller) ApplyProjectState(state sm.ProjectState)
- func (p *Poller) Cadence() Cadence
- func (p *Poller) IsActive() bool
- func (p *Poller) LastError() error
- func (p *Poller) Name() string
- func (p *Poller) RunCount() uint64
- func (p *Poller) SetActive(active bool)
- func (p *Poller) SetCadence(c Cadence)
- func (p *Poller) SetPanicSink(fn func(name, cause string))
- func (p *Poller) SetRestartBackoff(backoff time.Duration)
- func (p *Poller) Start(ctx context.Context)
- func (p *Poller) Stats() PollerStats
- func (p *Poller) Stop()
- func (p *Poller) Trigger()
- type PollerConfig
- type PollerStats
- type PrePushGateVerifier
- type PredictionCheckAnchorClass
- type PredictionCheckFileClass
- type PredictionCheckPerAnchor
- type PredictionCheckPerFile
- type PredictionCheckResult
- type ProbeContext
- type PropTestStubVerifier
- type RecoverEvent
- type RequirementExecutor
- type ReviewerDispatcher
- type RouteAction
- type RouteDecision
- type RoutePrepareMoveResult
- type SchedulerPass
- type SessionInitFailureEvent
- type SpawnFailureEvent
- type SpecDrafterDispatcher
- type SpecReviewerDispatcher
- type SpecVerificationResultEvent
- type SpecVerifier
- type StalledCheckRecoveryKind
- type StewardActionExecutor
- type StewardBlockIntentPayload
- type StewardDispatcher
- type StewardHandlerFunc
- type StewardLedgerMutationResult
- type StewardMutationSurface
- type StewardResult
- type StewardSweepFunc
- type StewardTask
- type SubscriptionSnapshot
- type TerminationCause
- type Topology
- func (t *Topology) AuthTokenHashOf(daemonName string) string
- func (t *Topology) BlockingFirstRefresh(ctx context.Context) error
- func (t *Topology) DaemonURLByName(daemonName string) string
- func (t *Topology) IsStale() bool
- func (t *Topology) LastRefresh() time.Time
- func (t *Topology) OwnerOf(repo string) string
- func (t *Topology) Snapshot() TopologySnapshot
- func (t *Topology) Start(ctx context.Context)
- type TopologySnapshot
- type TriagerActionApplyPayload
- type TriagerActionApplyResult
- type TriagerActionKind
- type TriagerDispatcherLogger
- type TriagerEventDispatcher
- type TriagerEventDispatcherInputs
- type TriagerPauseFn
- type TriagerTickFn
- type VerdictCacheHandle
- type Verifier
- type VerifierEnv
- type VerifierExecutorDispatch
- type VerifierResult
- type WedgeInvestigatorDispatcher
- type WorkflowDispatcher
Constants ¶
const ( EventKindCrossDaemonIssueForwarded = "cross_daemon_issue_forwarded" EventKindCrossDaemonForwardQueued = "cross_daemon_forward_queued" EventKindCrossDaemonForwardDelivered = "cross_daemon_forward_delivered" EventKindCrossDaemonForwardGaveUp = "cross_daemon_forward_gave_up" )
Cross-daemon forward audit-event kinds (SOR-1215).
The four kind constants below are the single source of truth for the audit-event strings emitted along the cross-daemon issue-forwarding path that the SOR-1185 routing plan ships in downstream issues:
EventKindCrossDaemonIssueForwarded: the forwarder delivered a route_resolved-derived issue to a peer daemon's HTTP surface in one shot (no retry needed). Subject = source-issue key; message carries target daemon + target URL.
EventKindCrossDaemonForwardQueued: the initial delivery attempt failed transiently and the forwarder appended a row to pending_cross_daemon_forwards for the sweep to retry. Subject = source-issue key; message carries the queued-row id + first error.
EventKindCrossDaemonForwardDelivered: a queued retry succeeded. Subject = source-issue key; message carries the queued-row id + attempt count.
EventKindCrossDaemonForwardGaveUp: the retry budget was exhausted and the forwarder flipped the queued row to status= 'gave_up'. Subject = source-issue key; message carries the queued-row id + last error.
Definitions only — emission sites land in the downstream issues that wire the forwarder and the retry sweep.
const ( PlanPRFixDispatchedKind = "plan_pr_fix_dispatched" PlanPRFixSucceededKind = "plan_pr_fix_succeeded" PlanPRFixFailedKind = "plan_pr_fix_failed" PlanPRFixExhaustedKind = "plan_pr_fix_exhausted" PlanPRFixRecoveredKind = "plan_pr_fix_recovered" // PlanPRFixDispatchFailedKind: the autonomous plan-PR-fix dispatch // (dispatchPlanPRFix) could not prepare a fix worktree or open a fix // session, so the plan would otherwise sit in plan_pr_fix_dispatched // with no live session and no scheduled retry. The plan is routed // plan_pr_fix_dispatched → plan_blocked for operator / recognizer // re-arm — closing the silent-wedge class. PlanPRFixDispatchFailedKind = "plan_pr_fix_dispatch_failed" )
SOR-1329 audit-event kinds for the autonomous plan-PR refer-back resolver. Each is an events.kind string written via d.appendEvent on the planning issue's key as it walks the fix cycle. One-line meanings:
- PlanPRFixDispatchedKind: a final-reviewer refer-back under budget dispatched an autonomous fix implementer; the plan moved plan_awaiting_pr_merge → plan_pr_fix_dispatched and PlanPRFixCycle was incremented.
- PlanPRFixSucceededKind: the fix implementer reported success; the plan walked back to plan_awaiting_pr_merge and the final reviewer was re-dispatched against the updated plan-branch tip.
- PlanPRFixFailedKind: the fix implementer reported failure (or an escalate-class status); the plan routed to plan_blocked.
- PlanPRFixExhaustedKind: the refer-back budget (max_refer_back_cycles) was exhausted; the plan routed to plan_blocked for operator action.
- PlanPRFixRecoveredKind: boot recovery found a plan stuck in plan_pr_fix_dispatched with no live session and walked it back to plan_awaiting_pr_merge for a free retry (counter left untouched).
const ( ImplementerVerifyChainStartedKind = "implementer_verify_chain_started" ImplementerVerifyChainSucceededKind = "implementer_verify_chain_succeeded" ImplementerVerifyChainFailedKind = "implementer_verify_chain_failed" VerifierStartedKind = "verifier_started" VerifierSucceededKind = "verifier_succeeded" VerifierFailedKind = "verifier_failed" // VerifierChainDispatchSkippedInFlightKind is emitted when a verifier- // chain dispatch is refused because a chain is already in flight for // the same issue (spawnImplementerVerifierChain's per-issue guard). // Closes the concurrent-chain race where a second dispatch interleaves // with the first chain's result-application and reads an empty // iss.PendingVerifierConcerns, firing a feedback cycle with an empty // REVIEW_FEEDBACK envelope. VerifierChainDispatchSkippedInFlightKind = "verifier_chain_dispatch_skipped_in_flight" )
Audit-event kinds for the daemon-side implementer post-submit verifier chain. Each is an events.kind string written via d.appendEvent on the implementation issue's key as the chain runs against the implementer's pushed branch. The chain-level kinds envelope the per-verifier kinds: a chain run starts with one ImplementerVerifyChainStartedKind row, emits one VerifierStartedKind plus exactly one of VerifierSucceededKind / VerifierFailedKind per verifier the runner reaches, and closes with one of ImplementerVerifyChainSucceededKind / ImplementerVerifyChainFailedKind. On chain failure, the failing verifier's `verifier_failed` message includes the first 500 bytes of each captured stream (stdout and/or stderr — empty streams are omitted) so an operator can grep the events table without pulling the per-issue refer-back comment. Verifiers that surface offender diagnostics via stdout land in the audit row alongside the existing stderr projection.
const ( RecoverySweepKindBlockedUserPROpen = "blocked_user_pr_open" RecoverySweepKindPlanAwaitingChildren = "plan_awaiting_children" RecoverySweepKindProposalOrphanSession = "proposal_orphan_session" RecoverySweepKindApprovedProposalUnmaterialized = "approved_proposal_unmaterialized" RecoverySweepKindOrphanSessionID = "orphan_session_id" RecoverySweepKindGhostImplementing = "ghost_implementing" // RecoverySweepKindRawCloneDrift (SOR-1162) re-runs the boot-time // raw-clone-pristine sweep on the recovery tick so mid-run // pollution (an agent's bash session that cd'd into the project // root and ran a mutator) is caught at the next sweep cadence // instead of waiting for daemon restart. Restorative work routes // through internal/runtime.RawCloneSweep; the daemon adapts the // per-sweep outcome into one synthetic RecoverTransition when any // restorative action fired. RecoverySweepKindRawCloneDrift = "raw_clone_drift" // RecoverySweepKindStalePendingReviewerVerdict (SOR-1420) enforces // the SOR-1097 PendingReviewerVerdict-only-on-in_review invariant // as defense-in-depth on the recovery tick. The runtime path clears // the buffer in applyTransitionEffects whenever an issue transitions // out of in_review, and replayPendingReviewerVerdict clears it // lazily on the next github-prs poll for any drift case. The sweep // is the catch-all: a non-in_review issue whose pending_reviewer_ // verdict column survived rehydrate (pre-fix DB, ForceConverge path // that bypassed applyTransitionEffects, direct DB write by // sorcererd doctor) gets the buffer cleared on the next sweep tick // rather than waiting for the issue's PRs to be re-polled. RecoverySweepKindStalePendingReviewerVerdict = "stale_pending_reviewer_verdict" // RecoverySweepKindStaleTeardownMarker (SOR-1420) enforces the // SOR-1230 IsTearingDown-only-on-plan_abandoned invariant. The // runtime path sets the marker atomically with the * → plan_abandoned // transition and clears it in completePlanTeardown once every child // session terminates. A pre-fix DB or a faulty path that left the // marker set on a plan_completed / plan_waiting / etc. planning // issue would gate every subsequent dispatch for that plan and its // children behind planTeardownPausesDispatch indefinitely; this // sweep clears the marker so the scheduler resumes work. RecoverySweepKindStaleTeardownMarker = "stale_teardown_marker" // RecoverySweepKindOrphanBranchNoPR is the autonomous escape from // the verifier-chain success-arm-drop class: when the // post-submit verifier chain succeeded but the chain result handler's // state guard correctly dropped the success-arm continuation because // the issue is no longer in implementer-arm state (operator // transition, same-state-cycle detection, recognizer wedge-loop // routing during the chain window), the implementer's branch is // already pushed to origin but no PR is opened. The issue lands in // in_review / feedback / rebasing with iss.Branch set + iss.Repos // populated + iss.PRURLs == nil. The reviewer dispatch gate refuses // (it requires len(iss.PRURLs) > 0) and there is no autonomous path // that opens the missing PR — the operator has to manually open the // PR + wait up to 5 minutes for orphan-PR adoption. This sweep finds // the (state has branch + missing PR) shape and re-dispatches // dispatchPerChildPROpen (the same path the verifier-chain success // arm uses); applyPerChildPROpenResults lands the per-repo opener // results and populates iss.PRURLs. RecoverySweepKindOrphanBranchNoPR = "orphan_branch_no_pr" // RecoverySweepKindStuckPlanAutoFixing is the autonomous escape from // the plan_auto_fixing wedge class: two production paths strand a plan // in plan_auto_fixing with no autonomous exit — an assembly_fixer // envelope rejected at validation (the dispatch counts as failed but no // assembly_fixer_exhausted event fires), and boot recovery interrupting // an in-flight fixer cycle (the PlanStateAutoFixing arm in supervisor.go // parks the plan and re-dispatches only if a fresh gate failure happens // to recur). When the proximate assembly gate has cleared organically // (a child squashed onto the plan branch, or a main→plan-branch merge // ran) AND no assembly_fixer session is in flight, the plan should // re-enter the scheduler rather than wait for operator intervention. // This sweep transitions plan_auto_fixing → plan_awaiting_children so // the scheduler re-evaluates. RecoverySweepKindStuckPlanAutoFixing = "stuck_plan_auto_fixing" // RecoverySweepKindInvariantsCheck is the periodic in-daemon sweep // that walks every runtime invariant registered in // internal/invariants/ against a fresh daemon-state snapshot and // emits one invariant_violation_detected event per returned // Violation. Cadence-gated by cfg.InvariantsCheckTickInterval — the // handler is dispatched every recover-sweep firing, but only // performs the actual check on every Nth call. See // docs/structural-coverage/phase-1-invariant-manifest.md § 4.7. RecoverySweepKindInvariantsCheck = "invariants_check" // RecoverySweepKindNonterminalRehydrate re-hydrates any non-terminal // issuestore row absent from d.state.Issues back into memory — the // periodic-reconcile counterpart of the boot-only rehydrateIssuesFromStore. // A non-terminal issuestore row missing from d.state is the // stranded-live-orphan divergence the db-nonterminal-issue-present-in-memory // invariant flags; this sweep self-heals it on the next tick so the issue // becomes reachable by the scheduler / operator API without a daemon // restart. See internal/daemon/rehydrate_missing.go. RecoverySweepKindNonterminalRehydrate = "nonterminal_rehydrate" // RecoverySweepKindTerminalPlanOrphanChildren (SOR-2087) converges any // non-terminal materialized child of a TERMINAL plan to the // parent-appropriate terminal state — abandoned for plan_abandoned // parents, merged for plan_completed parents. It covers BOTH terminal // plan states and ANY non-terminal child state, and runs both at boot // AND on the periodic recover-sweep tick so an orphan created mid-run // self-heals without a restart. See sweepTerminalPlanOrphanChildren. RecoverySweepKindTerminalPlanOrphanChildren = "terminal_plan_orphan_children" // RecoverySweepKindBlockedPlanPendingProposal (SOR-2531) re-arms any // planning issue standing in plan_blocked whose newest durable proposal is // still pending — the work product the plan_reviewer never consumed. The // live R5 guard in applyPlanningIssuePlannerResult reconciles NEW // session-end occurrences; this sweep self-heals the already-wedged class // (no live session fires a session-end event, so only a polling sweep // reaches it). It Force-transitions plan_blocked → plan_review (no legal SM // edge) and consumes no planner re-plan budget. See // sweepBlockedPlanPendingProposal. RecoverySweepKindBlockedPlanPendingProposal = "blocked_plan_pending_proposal" // RecoverySweepKindPlanBranchCreate (SOR-2699) re-drives plan-branch // creation for any membership-CIPB plan in plan_awaiting_children whose // PlanCommitSHAs is incomplete — the sweepPlanBranchCreate boot/periodic // call site that was previously absent entirely (the sweep had no caller // anywhere). Catches both the partial-failure retry (one repo failed on // the last create dispatch) and the strand recovery (an empty-branch_model // plan whose dispatchPlanBranchCreate was never fired after a crash // interrupted the approve→awaiting_children entry orchestration). The // sweep gates on membership + plan state, never on branch_model, so an // unstamped strand is no longer invisible to recovery — it auto-recovers // without the daemon-stopped DB write the operator otherwise needs. RecoverySweepKindPlanBranchCreate = "plan_branch_create" // RecoverySweepKindStaleVerifierChain (SOR-3150, SPEC-SOR-2927-v3 R4) is // the active-reclaim half of the verifier-chain stale-window bound. The // post-submit verifier chain runs off-main after an implementer // IMPLEMENT_OK; a chain whose goroutine dies / panics / hangs without // posting its result leaks the in-flight marker, which (within budget) // legitimately suppresses feedback re-dispatch and exempts the issue from // ghost-implementing reaping. A dead goroutine posts nothing, so no // in-process defer/recover can bound it — only a time-bounded sweep can. // This sweep clears the marker + its start instant for every key the // verifierChainStale classifier reports past the configured max-age, emits // a stale_verifier_chain_marker_cleared audit event, and either re-drives // the issue (the cleared marker lets the next dispatchFollowOns pass // re-dispatch) or escalates via escalateSubject when the issue's // sustained-failure backstop is exhausted. The read-site age-gating in the // same child is the by-construction complement: it makes a stale chain stop // suppressing / exempting the instant it crosses max-age, independent of // this sweep's timing. A within-budget chain is left fully intact. RecoverySweepKindStaleVerifierChain = "stale_verifier_chain" // RecoverySweepKindSessionRequiredStrand (SOR-3371, SPEC-SOR-3366-v1) is the // steady-state strand recovery sweep for the FULL sm.IsSessionRequiredState // set (discovering / implementing / merging / review_discovering / // review_judging). A strand is an issue in one of those states with an empty // SessionID and no durable hold (DiscoveringHoldSince, an in-flight CIPB // per-child squash, or an in-flight non-stale verifier chain). The sweep rolls // a within-budget strand back to a re-dispatchable state (the same boot-recovery // session_recovered target mapping) and increments the durable per-issue // recovery-attempt budget; a budget-exhausted strand escalates through the // single escalation chokepoint instead. Supersedes the boot-only, // discovering-only sweepDiscoveringNoSession for the session-required set; runs // both at boot (via recoverOrphanSessions) and on the periodic recovery tick. // Ordered BEFORE RecoverySweepKindGhostImplementing in allRecoverySweepKinds so // the bounded (budget-tracking) disposition wins over the ghost reap's unbounded // roll-back for the implementing + empty-SessionID overlap — each strand gets // exactly one bounded disposition per pass. RecoverySweepKindSessionRequiredStrand = "session_required_strand" // RecoverySweepEventKind is the audit-trail event row emitted on // every recovery-sweep firing (periodic poller and HTTP handler). // Stable name kept in one place so docs/state-machines.md and the // poller event constructor agree on the wire shape. RecoverySweepEventKind = "recovery_sweep_ran" )
Stable token names for the recovery-sweep kinds exposed via /v1/recover and the periodic sweep poller. The closed list is part of the wire contract: adding a new kind requires a code change so unknown kinds are rejected at the boundary.
const ( RouteActionLocal = "local" RouteActionForward = "forward" RouteActionReject = "reject" )
Route Action values returned in RouteDecision.Action.
const ( EventSubmitResultValidationFailed = "submit_result_validation_failed" EventSubmitResultValidationRetrySucceeded = "submit_result_validation_retry_succeeded" EventSubmitResultValidationExhausted = "submit_result_validation_exhausted" EventSubmitResultValidationSynthesizedEscalation = "submit_result_validation_synthesized_escalation" )
Audit-event kinds for the submit_result outcomes (SOR-1378 + SOR-1410). The recognizer's default LowSignalEventKinds set in internal/config does NOT include these kinds — they are recurring-failure signals the recognizer's tick is meant to see. Operator dashboards filter on the literal kind strings.
EventSubmitResultValidationExhausted is preserved for pre-SOR-1410 audit-trail compatibility (historical rows in the events table) but no longer fires from the dispatch retry loop — the synthesis path emits EventSubmitResultValidationSynthesizedEscalation instead.
const CPBSquashMatrixVerifyDefectKind = "cpb_squash_matrix_verify_defect"
CPBSquashMatrixVerifyDefectKind is the audit-event kind emitted when a per-child plan-branch squash episode aborts because the in-episode tracematrix.Verify failed against the staged detached-HEAD tree. Like PlanPRCITracematrixVerifyDefectKind it attributes the daemon generator/seam (the matrix is daemon-generated and out of every implementer's footprint), so the pushing child is NEVER routed to blocked_user — the squash is re-dispatched. (SOR-3132 / SPEC-SOR-3123-v2 R7.)
const DedupeTokenCap = 30000
DedupeTokenCap is the coarse byte-length÷4 estimate above which the dedupe gate drops the oldest entries from the inflight digest until the payload fits. Matches the AC's "30k tokens" cap. The estimate is deliberately coarse — the goal is to avoid pathological cases on projects with hundreds of in-flight rows, not to hit a precise token count. A separate `dedupe_payload_truncated` event records how many entries were dropped.
const DefaultExecutorDispatchTimeout = DefaultPrePushGateTimeout
DefaultExecutorDispatchTimeout is the phase-scoped wall-clock budget the mode-dispatch verifier anchors fresh at context.Background() for the executor subprocesses it spawns. It mirrors PrePushGateVerifier's own decoupled budget (DefaultPrePushGateTimeout): the implementer session context that reaches runVerifierChainForRepo is shared across the whole chain, and the pre-push gate that runs before this dispatch legitimately consumes ~15 minutes of it. Anchoring the dispatch's per-subprocess deadline from context.Background() instead of that near-exhausted parent ctx is what keeps `go vet`/`go build` from being killed before they print a byte — the duration_ms=32 / bare-colon empty-stderr incident class (SOR-2626).
The budget is the pre-push-gate floor, not a fixed 5 minutes. The executor- dispatch probe (the prop_test_stub compile/run plus the structural / type / benchmark executors) is a SUBSET of the work the pre-push gate already runs, so it can never legitimately need a tighter budget than the gate itself; a fixed Go-calibrated 5m (ample for a sub-10s go-tool pass) is what killed a SOUND slow-compile managed-project implementation — a Rust/cargo prop_test probe whose 55m pre_push_gate is green but whose compile straddles 5m — long before the gate would (archers SOR-1509). Pinning the default to DefaultPrePushGateTimeout by NAME (not a duplicated 55m literal) keeps the adequate-default magnitude in exactly one place and makes "the probe is never bounded tighter than the gate" true by construction. The constructor default and the Run zero-value fallback both consume this constant by name.
const DefaultPrePushGateTimeout = 55 * time.Minute
DefaultPrePushGateTimeout caps how long the pre-push-gate verifier will wait for the gate subprocess to exit before killing it. The race-enabled sweep that `scripts/pre-push-gates.sh` runs at its default `RACE_COUNT=1` / `RACE_TIMEOUT=15m` setting straddles ten minutes of wall-clock on hot or loaded hosts (the race phase alone can occupy most of that window, with the surrounding phase-A unit tests, golangci-lint, vite build, and docs gates adding several minutes more); a ten-minute cap killed subprocesses mid-sweep and surfaced as opaque `verifier_failed pre_push_gate` events with no actionable diagnostic. Fifty-five minutes sits strictly above the gate's `go test -race -timeout=40m` harness cap (matched from ci.yml) plus the surrounding phase-A unit tests, golangci-lint, vite build, and docs gates, so the daemon's blunt SIGKILL fires only for a genuine hang — the harness's own 40m timeout, with a goroutine dump, fires first. The standard sweep runs ~16m unloaded but stretches well past the former 20m under concurrent per-plan squash + main-merge gates contending for cores on a throttled pool. Operators who want the faster non-race verifier path can still set `PRE_PUSH_NO_RACE=1` on the daemon environment (see `scripts/pre-push-gates.sh:505-510`) as the implementer-time fast path; this constant preserves that choice rather than imposing it. The constructor default and the `Run` zero-value fallback both consume this constant by name; no parallel literal lives in this file.
const DefaultTriagerEventDebounce = 5 * time.Second
DefaultTriagerEventDebounce is the wall-clock window the dispatcher waits after the first blocked-set transition before running a Tick. Multiple transitions arriving within this window are coalesced into a single Tick whose payload includes every blocked issue (the triager re-assembles from the issuestore on its own, so the dispatcher only has to delay long enough for the storm to land). Default tuned to the AC's 5–10s suggestion; production wiring can override via NewTriagerEventDispatcher's input struct.
const DeployGatedRetryCap = 3
DeployGatedRetryCap is the maximum number of deploy-gated retries the release sweep grants a parked plan before it circuit-breaks to plan_blocked (SPEC-SOR-2923-v1 R6). The block's deploy_gated_retries counter is seeded 0 at park time and consumed one unit per re-release to integration; once it reaches this cap a deployed-but-still-failing plan is human-blocked via escalateSubject(ReasonCapabilityGapUnresolved) rather than re-parked forever. A compile-time constant (no config surface): the name is the operator grep hook.
const EventKindDispatchConditionChanged = "dispatch_condition_changed"
EventKindDispatchConditionChanged is the audit kind that RELEASES a refractory hold: a condition-change event for the issue strictly after the most recent precondition failure means the failing precondition may now be satisfiable, so re-dispatch is allowed before the backoff window elapses.
const EventKindDispatchPreconditionFailed = "dispatch_precondition_failed"
EventKindDispatchPreconditionFailed is the audit kind a dispatch call site emits when its deterministic precondition fails — the signal the refractory predicate reads back over the events table. By convention the message carries a "family=<family>" token so the predicate can scope a failure to the role-family it belongs to (a soft, message-encoded match, not a typed column). Sibling children thread the emission onto the call sites; this seam only consumes it. A message that omits the token simply never matches, which is safe-failing: the refractory hold is skipped and the dispatch proceeds (over-dispatch, never a wrongful suppression).
const EventRoleSessionAbortedByPause = "role_session_aborted_by_pause"
EventRoleSessionAbortedByPause is the audit-event kind written when the operator-pause path (SOR-1364) terminates an in-flight session. Emitted exactly once per aborted session via the TerminationCause.AuditKind plumbing inside terminateSession. Distinct from the generic `session_aborted` kind (operator cancel / triager mark_blocked_user / operator transition out of an in-flight state) so audit consumers can grep operator-pause-driven aborts as their own class without parsing the message body.
The constant lives in the daemon package (next to the handler) rather than in cmdadapter because the event is emitted by terminateSession on the main goroutine — the constant follows the writer, not the dispatch entry point (the cmdadapter's role-pause writer owns `role_health_pause_set_by_operator` for the same reason).
const EventTriagerDispatchedOnEvent = "triager_dispatched_on_event"
EventTriagerDispatchedOnEvent is the events-table kind written by the event-driven dispatcher every time it starts a Tick on a coalesced cohort of blocked transitions. Parallel to the triager_dispatched event the triager itself writes inside Tick; this row records the per-event dispatch metadata (coalesced issue keys, debounced_count, and the wall-clock latency from the first event to the actual dispatch) that proves the autonomy gap is closed.
const InvariantViolationDetectedEventKind = "invariant_violation_detected"
InvariantViolationDetectedEventKind is the stable audit-event kind one invariant_violation_detected row is recorded under per Violation the invariants_check sweep observes. Subject is the first element of Violation.Subjects (or empty when the slice is empty); message is `<InvariantName>: <Detail>`; the typed-field migration lands in the Phase 2 follow-on and is out of scope here.
const InvariantsCheckRanEventKind = "invariants_check_ran"
InvariantsCheckRanEventKind is the per-sweep heartbeat row emitted at the end of every actual check run (NOT every tick — only the Nth ticks the handler does work). The message records the per-sweep duration as the operator-visible cadence-tuning telemetry promised in docs/structural-coverage/phase-1-invariant-manifest.md § 7 R-2.
const MainMergeInfraRequeueThreshold = 5
MainMergeInfraRequeueThreshold bounds the number of CONSECUTIVE infrastructure-transient merge_resolver requeues for one plan's periodic main-merge. The resolverInfraRequeued arm re-dispatches while the per-plan MainMergeInfraRequeueConsecutive counter is below this threshold; once the post-increment value reaches it the arm stops requeuing and routes the plan to recovery (dispatchPlanMainMergeFailureToAssemblyFixer, then escalateSubject). Distinct from MainMergeMaxConsecutiveFailures (the M=3 genuine-conflict circuit-breaker) so a permanent conflict that keeps SIGKILLing / deadline-canceling the resolver subprocess can no longer loop forever with the escalation counters frozen at 0 (SPEC-SOR-2574 R1/R2/R4). Modeled on MainMergeMaxConsecutiveFailures — fixed daemon-side, not user-tunable; set higher than M=3 because an infra-transient kill is more often genuinely transient (host noise) than a genuine conflict and deserves more self-heal attempts before operator inspection.
const MainMergeMaxConsecutiveFailures = 3
MainMergeMaxConsecutiveFailures is the CIPB Phase C plan-blocked circuit-breaker threshold. After M=3 consecutive sweep cycles where at least one repo's main-merge attempt failed (after the full resolver + assembly_fixer chain exhausted), the supervisor force- routes the plan plan_awaiting_children → plan_blocked with the plan_main_merge_blocked escalation. Not user-tunable — the threshold is fixed daemon-side so the operator-escalation rate stays predictable across managed projects.
const MergeTimeConflictMaxAttempts = 3
MergeTimeConflictMaxAttempts bounds the merge-time BEHIND/DIRTY autonomous-resolution budget. After this many FAILED merge-time main-merge attempts (resolver exhausted / gate failure), the plan routes to plan_blocked rather than re-attempting forever. Mirrors MainMergeMaxConsecutiveFailures (M=3): fixed daemon-side, not user-tunable, so the operator-escalation rate stays predictable.
const PlanAssembleStaleRerereSweepActor = "daemon:plan_assemble_stale_rerere_sweep"
PlanAssembleStaleRerereSweepActor is the actor string stamped on transitions the sweep applies. Distinct from the reactive cascade's "daemon" actor so the audit trail makes clear the sweep was the writer.
const PlanPRCIFailureVerdictSynthesizedKind = "plan_pr_ci_failure_verdict_synthesized"
PlanPRCIFailureVerdictSynthesizedKind is the SOR-1366 audit-event kind emitted on the planning issue's key after the plan_pr_merge_pending_ci poller's failed-check arm synthesizes a refer-back ReviewerVerdict and the autonomous fix loop accepts it (i.e. plan transitioned to plan_pr_fix_dispatched). The event message carries the failing check names and the dispatch session ID so operators can grep the synthesis trail end-to-end.
const PlanPRCITracematrixVerifyDefectKind = "plan_pr_ci_tracematrix_verify_defect"
PlanPRCITracematrixVerifyDefectKind is the SOR-2975 audit-event kind emitted on the planning issue's key when the canonical tracematrix-verify CI check (the dedicated `tracematrix-verify` job in ci.yml) is a failing required check on a plan PR. The failure is classified as a daemon generator/seam defect — the matrix is daemon-generated and out of every implementer's footprint — and routed to the daemon-fix signal rather than synthesizing a plan-PR refer-back verdict or spawning a corrective child against the pushing issue. (SPEC-SOR-2970-v1 R4.)
const QuarantineThreshold = 3
QuarantineThreshold is the number of consecutive crashed+timed_out sessions on the same role's conversation that triggers quarantine. Per state-machines.md §3 ("3 consecutive crashed sessions ⇒ quarantine; 3 consecutive timed_out sessions ⇒ quarantine; mixed counts combined; threshold 5"). We use 3 for the simple case and don't distinguish kinds — strictly stricter than the spec.
const RejectionReasonLocalDaemonNotBSorcerer = "local_daemon_not_b_sorcerer"
RejectionReasonLocalDaemonNotBSorcerer is the rejection-reason tag the routing-rejected event carries when the local daemon's push allowlist does not include sorcerer's own repo. Companion to the rejection-reason tags in internal/triager/router.go; kept on the executor side because the router itself has no notion of "this daemon's project."
const SameStateCycleDetectedKind = "same_state_cycle_detected"
SameStateCycleDetectedKind names the audit event the SOR-1356 same-state-cycle detector writes when an open issue's (from, to) state pair has cycled past the configured threshold within the configured window. Subject is the offending issue's key (or its chain-parent's key when the cycle is across siblings sharing a parent); Message carries the cycle count, the repeated state pair, the dominant predispatch_lint_finding / plan_amend_auto_aborted signature observed during the cycle, and the time-window bounds so the operator can triage without re-reading hundreds of lint findings.
const SorcererSelfIssueRoutingRejectedKind = "triager_action_rejected_routing"
SorcererSelfIssueRoutingRejectedKind is the events.kind written when file_sorcerer_self_issue is rejected because the local daemon does not manage sorcerer's own repo (SOR-1086). The message field carries `action=file_sorcerer_self_issue reason=local_daemon_not_b_sorcerer` per the issue AC so an operator scanning the events table sees both the action name and the rejection reason in one row.
const ( // TopologyBootRefreshTimeout caps the synchronous first-refresh the // boot sequence runs before opening the HTTP listener (SOR-1464). On // timeout the daemon logs a warning and serves anyway; route checks // then fall through to the IsStale-empty fallback (= local) until the // periodic refresh populates the cache. TopologyBootRefreshTimeout = 30 * time.Second )
const TracematrixVerifyCheckName = "tracematrix-verify"
TracematrixVerifyCheckName is the canonical CI CHECK name for the tracematrix verify job — the `tracematrix-verify` job in .github/workflows/ci.yml. That verifier runs as its OWN job (not a step of the docs job) precisely so its failure surfaces under this distinct, selectively-matchable check name: gh's statusCheckRollup names an Actions check after the JOB's display name, never the per-step name. Matching the old "tracematrix verify" STEP name therefore never fired in production — the failure surfaced under the shared docs job's check name (the SOR-2948/#1216 instance, where the failing check was recorded as the docs job name). This constant MUST equal the `tracematrix-verify` job's `name:`. A plan-PR CI failure whose CheckName matches this string (case-insensitively) is classified as a generator/seam defect. (SPEC-SOR-2970-v1 R4.)
const TriagerActionSkippedIdempotentKind = "triager_action_skipped_idempotent"
TriagerActionSkippedIdempotentKind is the events.kind value the triager-action dispatch path writes when an action's daemon-side dispatch is a no-op AND the proposed outcome is already realized on the live issue row (SOR-1311). Distinct from `triager_action_applied applied=false`: this kind names the idempotent re-fire shape so operator dashboards stop conflating an already-realized state with a fresh apply attempt.
Two production trigger sites:
- `mark_blocked_user` against an issue already in `blocked_user` / `plan_blocked` whose `BlockedReason` matches the proposed `user_message`.
- `escalate_plan_defect` against an issue already in `blocked_user` / `plan_blocked` whose `TriagerBlockedReason` matches the proposed `AssembledNeeds` (the assembled operator-escalation body — the per-action "defect signature").
Emit-once per `(issue_key, action_kind)` per state-edge: the daemon's in-memory `triagerIdempotentEmitted` tracker records the `iss.TriagerBlockedAt` marker timestamp observed at the most recent skip-emit for each pair; a fresh skip dedupes iff the live marker equals the recorded one. Operator transitions that clear the marker (via `clearTriagerBlockedMarker`) zero `TriagerBlockedAt`, and the next re-block stamps a fresh timestamp — naturally rearming the emit on the next idempotent re-fire without hooking every transition path with a `delete()`. Conceptually parallel to SOR-1287's per-state-edge dedupe for `dispatch_paused_plan_branch_conflict`, though the rearm signal there is an explicit `delete()` on the transition out; this tracker keys on a durable issue field instead. Operators see one row per state-edge in the events table instead of one row per triager tick.
const TriagerBridgeDedupedKind = "triager_bridge_deduped"
TriagerBridgeDedupedKind is the events.kind written when escalate_to_recognizer suppresses a fresh triager_recognizer_bridge emission because the new evidence_event_ids set has a Jaccard similarity ≥ recognizerBridgeJaccardThreshold against an existing bridge event within the dedupe window. The message field carries the existing decision_id, the dropped LLM signature, and the Jaccard score so an operator scanning the events table can grep the dropped escalation back to its surviving canonical entry.
const TriagerBridgeStaleEvidenceSkippedKind = "triager_bridge_stale_evidence_skipped"
TriagerBridgeStaleEvidenceSkippedKind is the events.kind written when escalate_to_recognizer suppresses emission because the newest evidence event referenced by evidence_event_ids is older than recognizerBridgeEvidenceRecency. The message field carries the latest evidence age in seconds so an operator can see how stale the pattern was at suppression time.
Variables ¶
var ErrFootprintClaimConflict = errors.New("daemon: transition into write state refused — issue footprint overlaps an in-flight peer; re-drive after the peer leaves the in-flight cohort, or Force=true to override")
ErrFootprintClaimConflict is returned by applyIssueTransition when an unforced transition targets a write state (sm.IsImplWriteState — implementing / feedback / rebasing / merging) but the issue's predicted footprint anchor-conflicts a peer already resting in the IN-FLIGHT cohort (sm.IsInFlightImplState — the write states plus the review states in_review / review_discovering / review_judging, whose diff is frozen-but-unmerged). It is the sibling of ErrSessionRequiredForTransition: both are pre-mutation refusals at the single in-band transition chokepoint, and both share Force=true as the documented bypass for recovery / operator-override paths. The refusal makes footprint mutual-exclusion hold BY CONSTRUCTION across every write-state entry edge — because the check runs on the single-writer main loop against live d.state.Issues, the first of two issues crossing in the same pass is admitted and is visible to the second's check, so they serialize without any after-the-fact edge materialization. Callers use errors.Is to detect the refusal.
var ErrProductFootprintAltitude = errors.New("daemon: transition into write state refused — product-domain issue footprint touches no product-source path; tag the issue managed_project (R3) or Force=true (R4) to bypass")
ErrProductFootprintAltitude is returned by applyIssueTransition when an UNFORCED transition into a write state targets an issue whose effective defect-domain is product but whose to-be-seated predicted footprint touches no product-engine-source path (footprint.IsProductEngineSource) — a CI-only or managed-project self-config footprint is not a product fix. It is the altitude sibling of ErrFootprintClaimConflict: both are pre-mutation refusals at the single in-band write-state admission chokepoint, and both share Force=true as the documented bypass for recovery / operator-override paths. The refusal is the SOR-3069 keystone's teeth — it keeps a product-domain wedge from being re-decomposed into a b/sorcerer-CI band-aid that touches only self-hosting scaffolding (spec SPEC-SOR-3069-v4 R1). Tag the issue managed_project (R3) or Force=true (R4) to bypass. Callers use errors.Is.
var ErrSHANotFound = errors.New("triager: evidence_commit_sha not found in any configured repo")
ErrSHANotFound is returned by verifyCommitSHAInRepos when no repo in the project's configured list reported the SHA as reachable. The triager Executor maps this to the rejection reason `evidence_commit_sha_not_found` on the triager_action_rejected event.
var ErrSessionRequiredForTransition = errors.New("daemon: transition into session-required state requires AttachSessionID, a carried-over session, or Force=true")
ErrSessionRequiredForTransition is returned by applyIssueTransition when an unforced transition targets a session-required state (sm.IsSessionRequiredState) but neither attaches a session (IssueTransitionEffects.AttachSessionID) nor carries one over on the issue (iss.SessionID already populated). It is the mirror image of the terminateSession atomicity invariant: a transition INTO a between- sessions state clears iss.SessionID; a transition INTO a session- required state must attach one. Callers use errors.Is to detect the refusal; Force=true is the documented bypass for recovery sweeps / operator paths that intentionally park session-less.
ErrTopologyUnavailable is the typed sentinel ClassifyRoute returns when d.topology is nil — the daemon was started without a UI endpoint, so cross-daemon routing is disabled entirely and the classifier has no peers to consult. The httpapi handler maps this to 503 Service Unavailable; callers that need a different mapping can check for it via errors.Is.
Functions ¶
func ActionsWaitTimeoutAnchor ¶
maybeRouteActionsWaitTimeout fires on the ciPending arm of applyReviewerVerdict when the wait-budget age exceeds Config.ActionsWaitTimeout. Routes the issue from in_review to blocked_user with escalation reason `actions_wait_timeout` so the recovery sweep's re-arm path (cooldownLapseBlockedByTerminalCIFailure peer) can pick up the issue once Actions' final state for its head SHA is observable. Returns true when the timeout fired; the caller MUST NOT also buffer the verdict (the issue is no longer in_review).
The age is measured from the wait-budget ANCHOR — the LATER of the buffered-verdict time and the most-recent CI run's start across the issue's PR snapshots (SOR-3200, ActionsWaitTimeoutAnchor). The buffer time is the start of the current in_review buffering episode: the first `reviewer_verdict_buffered` event emitted while the issue has held its current PendingReviewerVerdict. bufferReviewerVerdict no-ops on identical re-buffer (so a replay against still-pending CI does NOT emit a fresh event), making the most recent event in the table the start of the current episode. A zero events-table (boot recovery, never-buffered) yields a zero buffer time and the timeout cannot fire — preserves the original buffer/replay shape for issues whose verdict hasn't actually been buffered yet. With no feedback-cycle restart the latest run started near the buffer time so the anchor stays the buffer time (current behavior); after a restart the latest run's start is recent so the new run gets a fresh budget.
ActionsWaitTimeoutAnchor returns the bounded-wait budget anchor: the LATER of the buffered-verdict time and the most-recent CI run's start across the issue's PR snapshots. A zero LatestRunStartedAt (no started run observed) contributes nothing, so with no feedback-cycle restart the anchor stays the buffer time and the original buffer-age behavior holds (SOR-3200). Pure so the prop_test helper and maybeRouteActions- WaitTimeout share one definition.
func ActionsWaitTimeoutFires ¶
func ActionsWaitTimeoutFires(buffered time.Time, latestRunStartedAts []time.Time, now time.Time, timeout time.Duration) bool
ActionsWaitTimeoutFires reports whether the bounded-wait timeout fires for a buffered merge verdict: true iff a positive budget is configured, the verdict has actually been buffered (non-zero buffer time), and the elapsed time since the anchor — max(buffered, latest-run starts) — has reached the budget. Anchoring on the LATER of the two means the latest CI run is judged against its own start, so elapsed time spent in a prior implementer-feedback cycle does not count (R1); the backstop is preserved because once the latest run itself ages past the budget the anchor's age reaches it and the timeout fires (R2). Pure; the SOR-3165 prop_test helpers anchor on it directly.
func CapabilitySignatureFromClass ¶
CapabilitySignatureFromClass derives the cross-plan dedup key (the capability_signature, SPEC-SOR-2923-v1 R9, SOR-3083) for a gate-failure class. It normalises the class string to lowercase-trimmed so the SAME logical capability gap maps to ONE signature regardless of agent-authored capitalisation or surrounding whitespace — the equivalence the sameSig model requires so N plans hitting the same gap share one gap issue. Deterministic and side-effect-free: same input → same signature.
func ClassifyGateFailureEnvironment ¶
func ClassifyGateFailureEnvironment(stdout, stderr []byte, exitCode int) (transient bool, signatures []string)
ClassifyGateFailureEnvironment returns transient=true when the captured gate output or exit code matches an environmental-failure signature (disk-full, I/O error, OOM kill, process kill). The signatures slice carries the matched substrings (or `signal:<N>` when classification fired on the exit code alone) so the audit-event message names what fired. Two arms cover the gate PROCESS ITSELF being signal-killed (an OOM/CommandContext SIGKILL of the gate wrapper, which Go's (*os.ProcessState).ExitCode() reports as -1): the `signal:-1` exit-code arm and the `killed-before-success-marker` output-shape arm (gate-progress evidence, neither outcome marker).
Pure free function: no daemon state, no I/O, deterministic on its inputs. Called from each CIPB gate-failure apply handler before routing to assembly_fixer.
func ComposeProgressLine ¶
func ComposeProgressLine( state string, metrics *LiveSessionMetrics, baseline *issuestore.StageBaseline, elapsedSeconds float64, planFractionN, planFractionM int, ) apiclient.ProgressLine
ComposeProgressLine deterministically composes the single per-issue progress line the operator UI renders. It is pure: it reads nothing itself — every input is handed in — performs no LLM call and no I/O, and is therefore fully table-testable.
Inputs:
- state: the issue's current state name (the wire string, e.g. "implementing"); an unmapped state degrades gracefully rather than panicking.
- metrics: the live session metering DTO, or nil when there is no live session entry for the issue (the "no live metering" sentinel that, with an in-flight state and elapsed past the stall threshold, surfaces the loud stalled health line).
- baseline: the cached stage baseline, or nil when absent. A non-weak baseline drives the "vs-typical" band; a weak or absent baseline falls back to plan-fraction (when M > 0) or phase-step.
- elapsedSeconds: time-in-current-stage.
- planFractionN, planFractionM: materialized children done / total; 0/0 means "not a plan-child context" and disables the plan-fraction band.
func DefaultDefectDomain ¶
DefaultDefectDomain is the single daemon-side resolver of the product-vs- managed_project default a freshly-minted issue carries when no create path stamped an explicit domain (SPEC-SOR-3317-v1 R4/R5). It returns managed_project on a foreign-project daemon and product on the sorcerer-self daemon — the ONE place the default decision is made, consumed by every new-issue mint seam: the Store.SaveIssue normalize path (via Store.SetDefaultDefectDomain, wired from this resolver in New) and the three direct-SaveIssueTx daemon mints (operator create, operator replace, the local planning-issue mint).
Foreignness is decided SOLELY through the foundational predicate config.ProjectIsForeign (SPEC-SOR-3317-v1 R1) so the whole chain — the altitude gate and this classifier — shares one foreignness source of truth and adds no second language comparison. projectLanguage is the daemon's already- resolved project language (daemon.Config.ProjectLanguage, set from config.ProjectLanguage at boot); the empty string resolves NOT foreign (-> product), matching ProjectLanguage's ""->"go" resolution, so an unwired test daemon keeps the product/self path (R5's self-default).
A managed-project SIGNAL is unaffected: this resolver only supplies the DEFAULT for an empty domain — an explicit managed_project tag on a create path (e.g. the recognizer's trigger-derived classification) is preserved on either daemon.
func DetectReposConflict ¶
func DetectReposConflict(body string, declaredRepos []string, topology *Topology) (bool, string, []string)
DetectReposConflict scans body for staticDaemonPaths prefix matches grouped by owning daemon (resolved through topology.OwnerOf on each path-map key), then classifies the result against the declared repos[0]'s owning daemon. The detector is the SOR-1218 Phase 3 safety net that catches a misrouted create where the operator's (or the recognizer's) `repos` field disagrees with the body content.
Return semantics:
- conflict=true: exactly one owning daemon X has ≥3 distinct prefix matches AND X is not the daemon owning declaredRepos[0]. suggestedDaemon is X; evidence is the matched prefix slice (sorted for deterministic output).
- conflict=false, suggestedDaemon=="", evidence non-nil: matches span two or more distinct daemons — the body is ambiguous. The caller should treat this as a reject. The evidence slice carries the union of all matched prefixes (sorted) so the rationale can name the offending paths.
- conflict=false, suggestedDaemon=="", evidence nil: zero matches OR a single-daemon match cluster that didn't clear the ≥3 threshold (insufficient signal — defer to the declared repos field).
A nil topology, an empty declaredRepos, an empty staticDaemonPaths, or a declaredRepos[0] with no owner in topology all degrade to the "no override" outcome — the detector is a safety net, not a hard gate, and silent passthrough is the right behavior when the inputs aren't sufficient to make a confident call.
func EvaluateActivationProbe ¶
func EvaluateActivationProbe(ctx context.Context, probe *dsl.ActivationProbe, pctx ProbeContext) (string, *role.ProbeEvidence, error)
EvaluateActivationProbe mechanically evaluates one activation probe against pctx and returns its terminal status (satisfied / failed), pending status (not-yet-decidable), and the typed evidence backing the verdict. It is the deterministic-first evaluator the post-merge plan_activating phase fires per activation requirement; an llm_judged probe layers an LLM verdict over the deterministic evidence.
Per kind:
- repo_artifact: os.Stat(Path) under the worktree — present → satisfied, absent → failed.
- command: run Command (space-split argv) in the worktree under the pre-push-gate exec discipline (own process group + WaitDelay so a kill reaches the whole tree) with the phase-scoped DefaultPrePushGateTimeout budget — zero exit → satisfied, non-zero → failed.
- ci_run: the daemon-dispatched run of Workflow (bound by DispatchRef + DispatchedAt, not the plan-branch SHA) via the CIChecker — success → satisfied, a non-success terminal conclusion → failed, no completed dispatched run yet (or a nil checker) → pending.
- metric: parse the committed MetricName file as a float and compare against the float Threshold — value ≥ threshold → satisfied, below → failed.
- any unknown kind, or a nil probe → pending (fail-open: an unrecognized future probe kind must not block the phase).
When EvalMode is "llm_judged" and an LLMRunner is wired, a non-pending deterministic result is re-judged by the oracle against the probe's Rubric (the deterministic evidence summary is folded into the judged statement); a nil runner keeps the deterministic result.
SOR-2436 boundary: probe evidence is EXECUTION state sourced from repo / CI AFTER merge (an artifact a merged build produced, a CI run on the merged SHA), recorded into the issue-keyed review_verdicts store. It is NOT the code-hash-keyed verdict_cache (pre-merge, code-change-derived oracle verdicts) — that store and this one answer different questions and never share a row.
func EventTriggersScheduler ¶
EventTriggersScheduler reports whether the given event kind should kick a scheduler-pass re-eval. Used by the main loop's dispatch — most events do, but a few (e.g. EventOperatorCommand for read-only queries, or EventScheduleEval which IS the pass) don't need to.
func FormatAbsentNativeHelperMessage ¶
func FormatAbsentNativeHelperMessage(lang string, absent []role.PropTestHelperObligation) string
FormatAbsentNativeHelperMessage builds the actionable authoring refer-back the native section emits when a template-backed (non-Go) language's per-requirement predicate helper files are absent from the worktree (SPEC-SOR-3094-v2 R3). It names each absent obligation's language-native helper path and predicate symbol to author — the file the daemon-generated stub calls — mirroring the Go-section absent-package authoring refer-back. It deliberately carries NO raw runner module-resolution text: the helper is created by AUTHORING the file, not by a runner/dependency fetch, so a `go get` / `npm install`-style remediation would misdirect. It is the single source both Run (the production emit) and the SPEC-SOR-3094-v2 R3 gen property read, so the named set cannot drift from what the gate emits.
func HermeticGenerationBarForProject ¶
HermeticGenerationBarForProject returns the hermetic-generation bar text the managed project's implementer is held to — the implementer role spec's "Hermetic generation bar" paragraph (test time/clocks/randomness/external-I/O injected not ambient; synchronization deterministic — never sleep-and-hope).
The bar is a PRODUCT SURFACE: the daemon resolves it FOR a managed project, so it must stay project-agnostic (spec SPEC-SOR-3238 R3 / R18). Two properties make it so:
- It is resolved through the common project-declared language path (config.DeclaredLanguages) — the SAME path any foreign project uses — so b/sorcerer's own Go self-host is NOT a special case (R18). DeclaredLanguages (not the project-wide ProjectLanguage selector the product-self-boundary analyzer flags) keeps this product-path read clean while still sourcing the language from the project's own config.
- The bar text itself is language-agnostic (R3: the product surface assumes no managed-project language): it is the implementer role spec's hermetic section, identical for every declared language with no per-language branch. The declared languages are resolved to make the common-path dependency explicit and to keep this a project-aware product surface; the bar does not branch on them.
implementerSpec is the raw implementer role-spec body (typically loaded via agentcli.LoadSpecForRole("", "implementer")). Threading it in as a parameter — rather than loading it here — keeps this resolver dependency-light and lets a caller amortize a single spec load across several role-spec reads in one dispatch cycle.
func IntegrationDispatchGateHeld ¶
IntegrationDispatchGateHeld reports whether the integration-dispatch gate suppresses (holds) a role-session dispatch: held iff the OAuth pool has no selectable token, i.e. it is throttle-held OR auth-dead-held. The two bool arguments are the first two return values of d.cfg.PoolDispatchState() (throttleHeld, authDeadHeld).
This extracts the predicate the scheduler-pass gate already applies inline at pass.go:81-94 (RunSchedulerPass suspends the pass when throttleHeld || authDeadHeld) into one reusable function, so the three CIPB post-completion integration seams (the assembly_fixer, the periodic main-merge merge-resolver, and the per-child squash merge-resolver — wired by a later SOR-3197 child) can consult the SAME held decision before spawning.
Exported so the SPEC-SOR-3197-v1 prop-test helpers can anchor on the production decision function directly.
func IsPlanAddChildrenStateRejection ¶
IsPlanAddChildrenStateRejection reports whether err is the typed httpapi.ErrBadRequest wrap the gate emits when the plan is in a state the primitive does not accept. Used by the HTTP handler to map the rejection to 422 (semantically-distinct from 400 for other ErrBadRequest cases, but folded onto the same wrapper for the handler's existing error-mapping path).
func IsPristineMainGateFailure ¶
IsPristineMainGateFailure reports whether the captured pre-push gate stdout contains the empty-diff change-detection marker the gate script emits when the plan-branch diff against its merge-base is empty. Such a failure ran against a plan-branch tree byte-identical to the default branch (no child squashed yet), so it is main-side, NOT cohort- attributable.
Pure free function: no daemon state, no I/O, deterministic on its input. Called from the PlanMainMergeFailureGate arm AFTER ClassifyGateFailureEnvironment (the env signature is the more specific match) and BEFORE the assembly_fixer dispatch.
func IsTracematrixVerifyCheck ¶
func IsTracematrixVerifyCheck(f FailedPlanPRCheck) bool
IsTracematrixVerifyCheck reports whether a failed plan-PR check is the canonical tracematrix verify check. The match is mechanical and self-contained — a case-insensitive equality on the check name — and is the SOLE gate for the generator-defect route; every non-tracematrix failing check keeps its existing refer-back behavior. strings.EqualFold guards against gh surfacing a differently-cased "Tracematrix-Verify". (SPEC-SOR-2970-v1 R4.)
func IssueTransitionTriggersScheduler ¶
func IssueTransitionTriggersScheduler(from, to sm.IssueState) bool
DispatchTable maps Issue state changes to scheduler-relevant signals. Used by the reconciler to decide whether a Linear-driven SM transition (e.g. operator marked an issue Done) should kick a scheduler pass.
The set is "non-terminal-ish" — anything that frees up a slot or unblocks a dep should re-eval. merged unblocks dependents; abandoned unblocks dependents; the rest are mid-pipeline.
func NewTriagerExecutor ¶
NewTriagerExecutor wires the production Executor against the daemon handle. The daemon's PlannerInputs.Repos drives the gh-verification repo list; CommitVerifier is the per-(repo, sha) probe.
The returned value implements triager.Executor. Tests can wire a different implementation against the router by satisfying the interface directly — the production impl is just one of many.
func ParseAcceptanceCriteria ¶
ParseAcceptanceCriteria is the exported peer of parseAcceptanceCriteria for callers outside the daemon package (`sorcererd doctor` maintenance verbs, etc.). The two share the same implementation; the wrapper exists so the parser remains the single source of truth for the accepted bullet vocabulary.
func ParseGitDiffNameOnly ¶
ParseGitDiffNameOnly extracts one file path per line from `git diff --name-only` output. Empty lines and lines starting with `#` (rare git annotations) are dropped. Returned in input order so the prediction_check classifier walks a stable list. Exported so future plumbing (e.g. when the supervisor wires the EmitPredictionCheck call to its production git path) can reuse the parser; today's hook is the awaiting_integration transition.
func PlanExternalMergeCandidateState ¶
PlanExternalMergeCandidateState reports whether the given Plan SM state is one of the post-integrating non-terminal states that the SOR-1375 external-merge auto-recovery walks. Centralized so the runtime poller, boot sweep, and tests all agree on the candidate cohort. Exported so the runtime poller can filter candidates without reimplementing the closed set.
func ReclaimGateScratch ¶
ReclaimGateScratch runs a narrow disk-reclamation pass between env- class retry attempts: (a) `git worktree prune` on every bare clone under <projectRoot>/.sorcerer/repos/; (b) deletes plan-assemble artifact files matching <projectRoot>/.sorcerer/plan-assemble/*/*.log older than gateScratchArtifactMaxAge.
Deliberately narrow scope: does NOT touch /tmp, ~/.cache/go-build, any path outside <projectRoot>/.sorcerer/, or any *.db / *.db-wal / *.db-shm file. Those classes can be live-read by other goroutines running gate dispatches against other plans / repos and mid-run delete would corrupt their state. Worktree prune is admin-only metadata (never touches active worktree dirs); artifact files are write-once audit artifacts (never read after their dispatch cycle ends).
Returns an error only when projectRoot is empty or unreadable; a missing <projectRoot>/.sorcerer/repos returns a nil error (the helper degrades gracefully on test daemons / a fresh project). Per- clone prune failures and per-file remove failures are logged via the daemon logger (when wired) and skipped — a single failed reclaim step does NOT fail the helper.
func RegisterRequirementExecutor ¶
func RegisterRequirementExecutor(mode string, exec RequirementExecutor)
RegisterRequirementExecutor binds mode to exec in the package-level registry. It panics on a non-executable mode (manual / empty has no executor by construction — R2: manual is not-live) and on a double-registration of the same mode (two executors claiming one mode is a wiring bug that must surface at program start, not silently let one win).
func RegisteredExecutorModes ¶
func RegisteredExecutorModes() []string
RegisteredExecutorModes returns the sorted set of verification_mode strings the dispatch registry routes to a RequirementExecutor (prop_test / structural / type / benchmark / llm_test, each registered by its file's init()). It is the executor-registry analog of invariants.All: a closed-world enumerator the mode-general non-vacuity meta-check sweeps so it covers EVERY registered executor rather than a hardcoded list — a future executor that self-registers is automatically enrolled, and the meta-check's per-mode failing-state constructor map is required to key exactly on this set. Sorting makes the enumeration deterministic across runs (the map's iteration order is not).
func RequiredBenchmarkTargets ¶
func RequiredBenchmarkTargets(spec *dsl.Spec, verifies []string, symbolPattern string) []role.BenchmarkTargetObligation
RequiredBenchmarkTargets computes the required-benchmark-targets set (spec SPEC-SOR-3095-v1 R4) for a spec-driven implementation issue: the exact set of per-requirement benchmark obligations the issue owes, derived once from (spec, verifies, symbolPattern) so the two downstream sites that read this one value — the implementing-dispatch instruction and the predicted-footprint augmentation — agree by construction. It is the direct benchmark analog of RequiredPropTestHelpers in internal/daemon/proptest_required_helpers.go.
For each requirement in spec whose VerificationMode is benchmark AND whose ID appears in verifies, it appends one obligation carrying the R-ID, its per-R-ID benchmark file path (benchmarkFilePath), its benchmark symbol (benchmarkSymbol over the profile-sourced symbolPattern), and its bound text (the requirement's EARS Statement). symbolPattern is the language profile's benchmark HelperSymbolPattern (e.g. "Benchmark{R}" for Go) threaded by the caller so no language assumption is hard-coded here.
It returns nil for a nil spec, an empty verifies list, or no matching requirements (no cited R-ID is benchmark mode). The result is a deterministic pure function of (spec, verifies, symbolPattern): the requirements are walked in declared order and only cited benchmark-mode R-IDs are kept, so two calls on the same inputs return an identical slice.
func RequiredPropTestHelpers ¶
func RequiredPropTestHelpers(spec *dsl.Spec, verifies []string) []role.PropTestHelperObligation
RequiredPropTestHelpers computes the required-helpers set (spec SPEC-SOR-2946-v1 R4) for a spec-driven implementation issue: the exact set of per-requirement prop_test helper obligations the issue owes, derived once from (spec, verifies) so every downstream site that reads this one value — the dispatch instruction (R1), the predicted-footprint augmentation (R2), and the absent-package authoring refer-back (R3) — agrees by construction.
For each requirement in spec whose VerificationMode is prop_test AND whose ID appears in verifies, it appends one obligation carrying the R-ID, its helper path (stubname.HelperFilePath) and its predicate symbol (stubname.PredicateSymbol). The path/symbol scheme is single-sourced through internal/spec/codegen/stubname so the naming cannot drift; the per-requirement HelperFilePath sharding gives R6 injectivity (distinct requirements → distinct helper files) by construction.
It returns nil for a nil spec, an empty verifies list, or no matching requirements (no cited R-ID is prop_test mode). The result is a deterministic pure function of (spec, verifies): the requirements are walked in declared order and only cited prop_test-mode R-IDs are kept, so two calls on the same inputs return an identical slice.
func RequiredPropTestHelpersByRequirementLanguage ¶
func RequiredPropTestHelpersByRequirementLanguage(trace spectrace.RequirementTrace, fallbackLang string, spec *dsl.Spec, verifies []string) []role.PropTestHelperObligation
RequiredPropTestHelpersByRequirementLanguage is the per-REQUIREMENT-language variant of RequiredPropTestHelpersForLanguage (spec SPEC-SOR-3056-v5 R10). It computes the same per-requirement prop_test helper obligation set, but routes EACH requirement's helper path/symbol through that requirement's OWN language — resolved from its trace targets (ResolveRequirementBackend over trace[rid]) — rather than the single project-wide fallbackLang. So in a {go, typescript} project a requirement traced to a web/ .ts file is told to author its helper in TypeScript (the typescript profile's path + symbol), while a Go-traced requirement keeps the Go path — the per-file generalization the union-stub generator (generatePropTestStubUnion) already performs at the seam.
fallbackLang is the dispatch-time project primary language (config.ProjectLanguage, read off d.cfg.ProjectLanguage at the call sites); it is used ONLY for an UNTRACED requirement (no resolvable trace target), so on the INITIAL dispatch — where iss.RequirementTrace is nil/empty — every requirement falls back to fallbackLang and the result is BYTE-IDENTICAL to RequiredPropTestHelpersForLanguage(fallbackLang, spec, verifies). A re-dispatch carrying a persisted trace routes each requirement per its own language.
The requirement filter (prop_test mode ∧ cited in verifies), the declared-order walk, and the nil-spec / empty-verifies short-circuits are identical to RequiredPropTestHelpersForLanguage; only the per-requirement language SOURCE differs. The path/symbol scheme stays single-sourced through langprofile.PropTestHelperPathAndSymbol so the per-requirement HelperFilePath sharding (R6 injectivity) holds language-natively by construction.
func RequiredPropTestHelpersForLanguage ¶
func RequiredPropTestHelpersForLanguage(lang string, spec *dsl.Spec, verifies []string) []role.PropTestHelperObligation
RequiredPropTestHelpersForLanguage is the language-native variant of RequiredPropTestHelpers (spec SPEC-SOR-3094-v2 R2). It computes the same per-requirement prop_test helper obligation set but sources each obligation's helper path and predicate symbol from lang's LanguageProfile (langprofile.PropTestHelperPathAndSymbol) instead of the Go-fixed stubname scheme, so a template-backed (non-Go) spec-driven issue is told to author its helper in its OWN language — the correct file extension and predicate symbol — rather than a .go file with a Go symbol (the vacuity this issue removes).
lang is the dispatch-time project language (config.ProjectLanguage, read off d.cfg.ProjectLanguage at the call sites — the dispatch instruction in supervisor_dispatch.go and augmentDiscoveryFootprintPropTest below). An empty or unregistered lang resolves to the Go profile via langprofile.ProfileForLanguage, so the result is BYTE-IDENTICAL to RequiredPropTestHelpers for a Go (or unset-language) project — Go's go_codegen reference path stays unchanged (R5).
The requirement filter (prop_test mode ∧ cited in verifies), the declared-order walk, and the nil-spec / empty-verifies short-circuits are identical to RequiredPropTestHelpers; only the path/symbol SOURCE differs. The path/symbol scheme stays single-sourced through langprofile.PropTestHelperPathAndSymbol so the per-requirement HelperFilePath sharding (R6 injectivity: distinct requirements → distinct helper files) holds language-natively by construction.
func ResolveInputsClosure ¶
func ResolveInputsClosure(ctx context.Context, readSet []string, cmd string, workDir string) (relevance.InputsClosure, error)
ResolveInputsClosure resolves a plan's build-input closure through the three tiers defined by SPEC-SOR-2773, in precedence order:
- Tier 1 (observed): readSet non-empty → relevance.ObservedClosure. The gate's recorded file-read set is the most precise signal.
- Tier 2 (declared): cmd non-empty → run `sh -c cmd` in workDir, parse newline-delimited stdout → relevance.DeclaredClosure.
- Tier 3 (unavailable): neither signal → relevance.UnavailableClosure, under which relevance.Relevant classifies every advance relevant — the R5 conservative fallback (sound but unoptimized).
It is a package-level function, not a daemon method, so it is called cleanly from off-main goroutines. It MUST be invoked off the main goroutine when cmd is non-empty: tier 2 runs a subprocess, and the no-main-loop-blocking-call invariant forbids a blocking subprocess on the single-writer main loop. A caller captures config.ProjectRelevanceInputsCommand into a local on the main goroutine, then spawns the goroutine that calls this function. On a tier-2 command error the function returns the unavailable closure paired with the error, so a caller that ignores the error still degrades to the sound conservative fallback.
func ResolveRequirementBackend ¶
func ResolveRequirementBackend(entry spectrace.TraceEntry, projectLang string) (proptestbackend.Interpreter, bool)
ResolveRequirementBackend resolves a single requirement's property-test interpreter from its traced targets, realizing SPEC-SOR-3054-v1 R6 (propTestBackendLang(r) = requirementLang(r)): it scans the requirement's implementing then verifying targets and, for the first whose file extension maps to a registered language (langprofile.ForFileExtension), returns that language's backend (proptestbackend.ForLanguage) — so the backend follows the REQUIREMENT's own language, never a single project-wide language. Only when no traced target resolves to a registered language does it fall back to the project-wide projectLang (an un-traced requirement / legacy row), with the zero value treated as "go" per the daemon.Config.ProjectLanguage zero-value convention. An unregistered fallback language returns (zero, false), so the caller no-ops rather than running a foreign runner.
It is exported so the SPEC-SOR-3054-v1 R6 gen-package property anchors on the real per-requirement resolution rather than a re-implementation.
Types ¶
type AbortRoleSessionsResult ¶
AbortRoleSessionsResult is the payload sent back on Mutation.AbortRoleSessionsReply (SOR-1364). Count is the number of non-terminal sessions the handler walked to aborted (zero when no in-flight session of the paused role existed). Err carries any typed error from the dispatch; in practice the abort handler is pure local-state mutation and Err is reserved for future failure modes (e.g. issuestore write failures on the per-session terminate_session AuditKind write — those surface today via the daemon's event-write log path and do not abort the dispatch).
type ActivationListResult ¶
type ActivationListResult struct {
Candidates []activationCandidate
}
ActivationListResult is the main-loop snapshot the MutationActivationList handler replies with: the plans currently in plan_activating (key + bound spec). The off-main bridge (ApplyOperatorListActivating) reads each plan's spec + verdict store to assemble the per-probe status views, so the slow issuestore reads stay off the single-writer goroutine.
type ActivationPollObservation ¶
type ActivationPollObservation struct {
AllSatisfied bool
FailedRIDs []string
EvidenceSummary string
Reason string
Now time.Time
}
ActivationPollObservation is the EventActivationPollObserved payload (SOR-2503). AllSatisfied is true when every owned activation probe has a terminal-satisfied verdict (or the plan has no activation criteria — vacuously all-satisfied), and the main-loop handler drives plan_activating → plan_completed. FailedRIDs names the requirement R-IDs whose probe terminally failed (or the can't-evaluate causes: absent spec / permanent dispatch denial); when non-empty the plan routes to plan_blocked. EvidenceSummary is the per-R-ID evidence digest the closing completion event carries (spec R7). Reason is the verbose diagnostic the audit/escalation message renders. Now is the poll instant.
type AssemblyFixerDispatcher ¶
type AssemblyFixerDispatcher func(ctx context.Context, task role.AssemblyFixerTask) (role.AssemblyFixerResult, []string, error)
AssemblyFixerDispatcher runs one autonomous assembly-fixer task and returns the typed result plus any liberal-parser coercions the dispatcher applied. SOR-1443 Fix 4. Invoked when a plan transitions to plan_auto_fixing (in the retired plan_branch one-shot-assembler model, either from its pre-integration dry-run gate on a cumulative- gate failure or from its assembly stage on a defensive real-push failure — that model's `plan_integrating`/`plan_dry_running` states are retired-model names, unrelated to the live CIPB integration-window plan_integrating state); the dispatcher renders the per-task message via role.FormatAssemblyFixerTask, runs the `assembly_fixer` conversation, and parses the typed AssemblyFixerResult via role.ParseAssemblyFixerResult. A typed Status=failed result is a successful dispatch — the caller decides whether to retry (the daemon's outer-retry counter budget-guards M=2 dispatches per plan before synthesizing an escalate_capability_gap); only transport / conversation failures should return a non-nil error.
Coercions carries any liberal-parser descriptors the dispatcher emitted (one entry per `field=X from_shape=Y to_shape=Z` coercion). The supervisor's closure emits one role_input_coerced audit event per entry, mirroring the merge_resolver path's per-coercion emit.
type AssemblyFixerEventPayload ¶
type AssemblyFixerEventPayload struct {
Result role.AssemblyFixerResult
Coercions []string
}
AssemblyFixerEventPayload is the typed EventAssemblyFixerResult payload. Result carries the parsed assembly_fixer envelope (status + actions); Coercions carries the per-coercion descriptors the dispatcher emitted (one entry per `field=X from_shape=Y to_shape=Z` coercion). applyAssemblyFixerResult emits one role_input_coerced audit event per Coercions entry before applying the actions.
type BlockedUserPROpenResultEvent ¶
BlockedUserPROpenResultEvent is the EventBlockedUserPROpenResult payload. The off-main worker (spawnOrSkipBlockedUserPROpen) snapshots the blocked_user candidates on the main loop, runs the PRSetDiscoverer probes on a session goroutine, and submits this struct for applyBlockedUserPROpenResult to apply on the main loop. The worker writes neither d.state nor the issuestore — it only Submit()s this result, which carries only plain values (no live issuestore handle, no *Daemon reference) so the off-main/on-main boundary stays clean.
Results holds one entry per probed blocked_user issue, in deterministic (issue-key) order; At is the on-main-captured sweep timestamp the handler threads through evaluateBlockedUserUnblock so the async path stamps its audit/transition rows with the same `now` the inline sweep would have.
type CIRunChecker ¶
type CIRunChecker interface {
DispatchedWorkflowRunStatus(ctx context.Context, repoSlug, workflowName, ref string, dispatchedAt time.Time) (conclusion string, runID int64, headSHA string, err error)
}
CIRunChecker is the narrow CI-run-status accessor the ci_run activation probe uses (SOR-2502 / spec R10; SPEC-SOR-3247). *github.Client satisfies it via DispatchedWorkflowRunStatus, so the evaluator depends on this one method rather than importing the full github client surface — the same narrow-handle pattern LLMVerifierRunner / VerdictCacheHandle use on the verifier chain. A nil checker makes a ci_run probe evaluate to pending (the daemon defers until CI access is wired) rather than fail.
The method binds a dispatch_workflow probe to the run the daemon DISPATCHED — keyed on (workflow, ref/branch, the workflow_dispatch event, created at/after the dispatch instant) — not on an exact head-SHA equality against the plan-branch key commit (SPEC-SOR-3247: the dispatched run runs on ref=main, so its head is the post-merge tip and never equals the recorded plan-branch SHA).
type CPBChildSquashResult ¶
type CPBChildSquashResult struct {
ChildKey string
PlanKey string
PlanBranch string
RepoSHAs map[string]string
FailureKind CPBSquashFailureKind
FailureReason string
FailureRepo string
ConflictingPaths []string
GateStdout []byte
GateStderr []byte
// GateExitCode is the failing gate subprocess's exit code, threaded
// from *github.AssembleGateFailure.ExitCode (SOR-2400 closed the
// "capture gap" where applyCPBChildSquashResult passed a hardcoded 0
// to ClassifyGateFailureEnvironment). Populated only on a Gate
// failure; -1 marks a signal-terminated gate process so the env-class
// classifier reaches the signal:-1 arm.
GateExitCode int
// GateDeadlineKill is true when the gate was killed by its own
// phase-scoped deadline (cpbPrePushGateTimeout) rather than by an
// external signal (SOR-2400). Maps from *github.AssembleGateDeadlineKill;
// drives the CPBSquashFailureGateDeadline routing in
// applyCPBChildSquashResult.
GateDeadlineKill bool
// RerereAutoResolved is true when at least one repo's per-child squash
// committed via the rerere-fully-resolved branch (the `git merge
// --squash` exited non-zero, zero unmerged paths remained, and a
// rerere-staged tree was committed) — the signal threaded up from
// ChildSquasher / SquashChildIntoPlanBranch. applyCPBChildSquashResult
// emits a distinct cpb_squash_rerere_autoresolved audit event on the
// success path when this is set, so a recurring auto-resolved-conflict
// rate stays observable rather than indistinguishable from a clean
// squash (SOR-2390). False on a clean exit-zero squash, a
// resolver-resolved squash, and the net-no-op success.
RerereAutoResolved bool
// CircuitBreakerEngaged / CircuitBreakerRejectedPayload are transient
// signaling fields the main loop sets DURING applyCPBChildSquashResult
// (not by the off-main squash producer): when
// dispatchCPBSquashFailureToAssemblyFixer trips the consecutive-
// validator-rejection circuit-breaker it sets CircuitBreakerEngaged and
// records the last rejected assembly_fixer payload excerpt here so the
// caller's refer-back-to-feedback comment can surface what the fixer
// was trying to say. CircuitBreakerRejectedPayload is populated only
// when CircuitBreakerEngaged is true (and a payload was recorded).
CircuitBreakerEngaged bool
CircuitBreakerRejectedPayload string
// FullResolverVerdict carries the FULL untruncated merge-resolver
// give-up reason threaded out of buildCPBMergeResolverClosure (the best
// available: the resolver's verbatim FailureReason prose on a genuine
// give-up, or the trimmed infra error on the infra-transient arm).
// applyCPBChildSquashResult persists it via captureAndPersistResolverDiagnosis
// so the durable diagnostic row holds the verbatim verdict the 500-byte
// cpb_squash_resolver_failed event truncates. Populated only on a
// CPBSquashFailureConflict result; zero-valued elsewhere.
FullResolverVerdict string
}
CPBChildSquashResult is the EventCPBChildSquashResult payload — the typed per-child-per-repo squash outcome. RepoSHAs maps repo slug to the plan-branch HEAD SHA AFTER the squash landed (one entry per repo on full success). On failure FailureKind / FailureReason / FailureRepo classify the failure for applyCPBChildSquashResult's routing; ConflictingPaths populates only on Conflict failures and GateStdout / GateStderr populate only on Gate failures.
type CPBIntegrationReviewEntry ¶
type CPBIntegrationReviewEntry struct {
// ChildKey is the just-squashed child whose squash created this review
// point — the integration surface being reviewed.
ChildKey string `json:"child_key"`
// Reason is why the surface triggered a review: cpbReviewReasonOverlap (its
// footprint conflicts with a prior-squashed sibling's) or
// cpbReviewReasonCadence (the footprint-independent backstop).
Reason string `json:"reason,omitempty"`
// Status is the review lifecycle: cpbReviewStatusPending at dispatch,
// cpbReviewStatusClean / cpbReviewStatusFinding once the verdict returns.
Status string `json:"status"`
// SessionID is the interim review session this surface dispatched. The
// returning verdict is matched back to this entry by session id, so the
// outcome lands on exactly the surface it reviewed.
SessionID string `json:"session_id,omitempty"`
// FindingDetail carries the first substantive concern's detail when the
// review recorded a finding (cpbReviewStatusFinding); empty otherwise.
FindingDetail string `json:"finding_detail,omitempty"`
}
CPBIntegrationReviewEntry is one element of a planning issue's CPBIntegrationReviews tracking list: a cross-child integration surface or cadence review point and its review status. Persisted JSON-encoded in the issuestore `cpb_integration_reviews` column (the sm.Issue.CPBIntegrationReviews string). The struct lives in the daemon package (not sm) because the daemon owns the encode/decode; sm treats the field as opaque.
type CPBMergeResolverResult ¶
type CPBMergeResolverResult struct {
ChildKey string
RepoSlug string
Teardown func()
Success bool
Reason string
// HoldNoProvider (SPEC-SOR-3197-v1 R4/R11) is set when the resolver's
// dispatcher returned a no_provider error mid-retry (PoolExhaustedError /
// spawn_picker_unavailable — the OAuth pool exhausted before a session could
// spawn). It signals a HOLD rather than a genuine resolver give-up: when set,
// applyMergeResolverResult emits the reused pause row and re-dispatches the
// squash (which the proactive gate holds again while the pool is unavailable)
// instead of escalating the child through escalateSubject. Zero-valued (false)
// on every other result, so existing CPBMergeResolverResult literals default
// to the unchanged give-up path.
HoldNoProvider bool
}
CPBMergeResolverResult is the EventCPBMergeResolverResult payload — the outcome of one off-budget merge_resolver dispatch the daemon spawned on a per-child squash conflict (SOR-2739). ChildKey / RepoSlug identify the child and repo whose squash conflicted; Success is true when the resolver landed a clean resolution (its commit auto-recorded the rerere resolution in the shared rr-cache), false when it gave up. Reason carries the resolver's give-up detail on failure. Teardown is the carved-worktree cleanup closure the main loop calls after it records the durable resolution marker (success); nil on the failure path, where the goroutine tears down before posting.
type CPBSquashFailureKind ¶
type CPBSquashFailureKind int
CPBSquashFailureKind classifies a per-child squash failure for applyCPBChildSquashResult's routing decision (CIPB Phase B).
const ( // CPBSquashFailureNone is the success sentinel (FailureReason == ""). CPBSquashFailureNone CPBSquashFailureKind = iota // CPBSquashFailureConflict — merge_resolver exhausted attempting to // resolve a `git merge --squash` conflict. Routes the child to // feedback with the conflict as a synthetic refer-back so the next // implementer cycle can resolve it. CPBSquashFailureConflict // CPBSquashFailureGate — the plan-branch push GATE (`scripts/ // pre-push-gates.sh` or the pre-push hook) failed AFTER the squash // landed cleanly but BEFORE the push. Code-class: routes the failure // through the assembly_fixer recovery chain (Phase A+B audit Fix 4). // Maps from *github.AssembleGateFailure (SOR-1461). CPBSquashFailureGate // CPBSquashFailureAuth — the post-gate `git push` was rejected with // an HTTP 401/403 (expired/insufficient token). Maps from // *github.AssemblePushAuthFailure (SOR-1461). Routes to a // token-refresh + capped re-dispatch, NEVER to assembly_fixer (a // fixer agent cannot mint a token). On retry exhaustion → blocked_user. CPBSquashFailureAuth // CPBSquashFailureTransient — the post-gate `git push` failed with a // recognized transient error (network blip, or a lost // --force-with-lease race with a concurrent sibling squash). Maps // from *github.AssemblePushTransientFailure (SOR-1461). Routes to a // backoff re-dispatch, NEVER to assembly_fixer. On retry exhaustion // → blocked_user. CPBSquashFailureTransient // CPBSquashFailureGeneric — plumbing failure (bare clone missing, // fetch error, etc.) OR a generic post-gate-pass push failure that is // neither auth nor a recognized transient (*github.AssemblePushFailure, // SOR-1461). Routes the child to blocked_user with an operator // escalation since this isn't fixable by the implementer. CPBSquashFailureGeneric // CPBSquashFailureGateDeadline — the plan-branch push GATE subprocess // was killed by its OWN phase-scoped deadline (cpbPrePushGateTimeout) // rather than by an external OOM/host signal (SOR-2400). Maps from // *github.AssembleGateDeadlineKill. DISTINCT from CPBSquashFailureGate // (a genuine non-zero gate exit, assembly_fixer territory) and from the // env-transient class: a self-inflicted timeout is neither a cumulative- // state code defect nor a clean-runner-clearable host flake, so it // routes the child to blocked_user with the gate_deadline_kill_detected // audit event, never an env-transient retry. CPBSquashFailureGateDeadline // CPBSquashFailureMatrixVerify — the in-episode tracematrix.Verify failed // against the staged detached-HEAD tree (a daemon generator/seam defect, // DELIBERATELY NOT a gate failure). Maps from // *github.PlanBranchMatrixVerifyFailure (internal/github/assembly_errors.go), // classified in runChildSquashOffMain BEFORE the generic default arm so a // matrix-verify discrepancy is never misrouted to CPBSquashFailureGeneric. // Routes through applyCPBChildSquashResult to a re-dispatch (the child stays // in merging), NEVER to blocked_user / cpb_squash_failed and NEVER to the // assembly_fixer chain — a matrix-verify discrepancy is not a code defect the // implementer or the fixer can address. (SOR-3132 / SPEC-SOR-3123-v2 R7.) CPBSquashFailureMatrixVerify )
type Cadence ¶
type Cadence struct {
Active time.Duration // when relevant work is in flight
Idle time.Duration // when nothing is in flight (or paused entirely if Idle == 0)
}
Cadence is the configurable polling interval for one poller.
type CallCounts ¶
CallCounts is the per-closure invocation tally — useful for tests asserting "the daemon retried N times" without iterating the captured-tasks slice.
type CapabilityGapParkPayload ¶
type CapabilityGapParkPayload struct {
PlanKey string
GapIssueKey string
CapabilitySignature string
}
CapabilityGapParkPayload is the typed EventCapabilityGapPark payload (SPEC-SOR-2923-v1 R2, SOR-3083). PlanKey is the plan to park; GapIssueKey is the capability-gap fix issue the plan blocks on; CapabilitySignature is the dedup key the durable block records. All three are required — parkPlanOnCapabilityGap errors on an empty PlanKey / GapIssueKey / CapabilitySignature.
type CapabilityGapReleaseProbePlanResult ¶
type CapabilityGapReleaseProbePlanResult struct {
PlanKey string
GapIssueKey string
IsDeployed bool
}
CapabilityGapReleaseProbePlanResult is the per-plan git-ancestry probe result: whether the plan's gap fix is DEPLOYED in the running daemon build (the keystone deployed != merged check). PlanKey / GapIssueKey identify the (plan, gap) block; IsDeployed is the probe verdict the result handler routes on.
type CapabilityGapReleaseProbeResultPayload ¶
type CapabilityGapReleaseProbeResultPayload struct {
Results []CapabilityGapReleaseProbePlanResult
}
CapabilityGapReleaseProbeResultPayload is the typed EventCapabilityGapReleaseProbeResult payload (SOR-3084). The off-main probe spawned by sweepCapabilityGapRelease fills Results with one entry per held block it evaluated; the main-loop handler applyCapabilityGapReleaseProbeResult drives the release / re-park / circuit-break disposition per entry.
type ChildActualDiffResolver ¶
type ChildActualDiffResolver interface {
// ComputeChildActualDiff returns the unioned per-repo diff: the
// sorted set of paths the child's branch touched plus the per-path
// post-diff content (for anchor resolution). The implementer's
// repo set is `child.Repos`; the child branch is `child.Branch`
// (falls back to gh.ChildWorkingBranchName when empty).
ComputeChildActualDiff(ctx context.Context, child *sm.Issue, planBranch string) (files []string, contents map[string][]byte, err error)
}
ChildActualDiffResolver computes the actual implementer diff for a child whose reviewer just returned `merge`. CIPB Phase A+B audit Fix 1: the two EmitPredictionCheck hook sites in supervisor.go pass the real diff (and the post-diff file content for anchor analysis) instead of the nil/nil placeholders the Phase A/B PRs landed — without real data every event classifies every predicted file as over_predicted and emits zero anchor data, poisoning Phase D's learning loop before it starts.
The resolver runs per repo against the child's bare clone (sorcerer's canonical git store) using `git diff --name-only <base-ref>..<child- branch-tip>` for the file list and `git show <child-branch-tip>:<path>` for the post-diff content of each touched file. The per-repo lists are merged: a file touched in any repo lands in the unified file set. The anchor map is keyed by path; collisions across repos pick the LAST-seen content (rare in practice — children rarely touch the same path in multiple repos).
The base-ref is the upstream/main equivalent for legacy `plan_branch` children (the carve-point was origin/main) and the plan branch's SHA at the moment the child was carved for `continuous_plan_branch` children (the carve-point was the live plan branch). For the cpb case we re-fetch the plan branch fresh and use its tip as the base — any sibling squash that landed after the child carved is reflected as an under_predicted file, which is the correct telemetry (the child's diff DID end up touching that path, because the sibling already landed it on the plan branch and the cumulative diff includes it). For the legacy case we use origin's default branch.
On error reading any per-repo diff: the helper does NOT short-circuit the whole emission — it records the per-repo failure as a prediction_check_diff_skipped audit event and continues with the repos that succeeded. The caller (EmitPredictionCheck) degrades gracefully on a fully-empty diff (anchor analysis short-circuits; classifier emits all over_predicted).
func NewChildActualDiffResolver ¶
func NewChildActualDiffResolver(client *gh.Client, projectRoot, branchPrefix string) ChildActualDiffResolver
NewChildActualDiffResolver constructs the production-wiring ChildActualDiffResolver from a github.Client, the daemon's project root, and the daemon's branch prefix. cmd/sorcererd/start.go wires it into daemon.Config.ChildActualDiffResolver.
type ChildSquasher ¶
type ChildSquasher func(ctx context.Context, repoSlug, planBranch string, child gh.ChildBranchToAssemble) (string, bool, error)
ChildSquasher is the CIPB Phase B per-child squash hook (signature on Config.ChildSquasher). It runs ONE child's squash against the live plan branch in ONE repo: an ephemeral worktree off the bare clone checked out to the plan branch tip, `git merge --squash <child-branch>`, commit, run the per-push gate, and push the new plan-branch tip. On a genuine conflict it returns *github.AssembleConflict immediately — the resolver is NOT run inline (SOR-2739); the daemon dispatches the merge_resolver off its own budget and re-dispatches a finalize. Returns the new plan-branch HEAD SHA after the squash landed, plus a rerereAutoResolved bool that is true ONLY when the squash committed via the rerere-fully-resolved branch (non-zero git exit, zero unmerged paths, a rerere-staged tree committed). The daemon threads it onto CPBChildSquashResult.RerereAutoResolved so applyCPBChildSquashResult emits a distinct cpb_squash_rerere_autoresolved audit event for exactly that case (SOR-2390); every other path returns false.
On a squash conflict the function returns a *github.AssembleConflict carrying the conflicting paths (no inline resolver — SOR-2739). On a per-push gate failure, it returns a *github.AssemblePushFailure carrying the captured gate output. Any other error is unrecoverable plumbing (bare clone missing, fetch failure, ctx cancel, etc.).
Production wiring uses (*github.Client).SquashChildIntoPlanBranch; nil is a documented degraded mode — the daemon routes a cpb child's squash to blocked_user since it can't run git.
type CommentMutationResult ¶
CommentMutationResult is the payload sent back on Mutation.CommentReply. ID and TS are zero on error; otherwise ID is the SQLite-assigned row id and TS is the unix-second timestamp the daemon stamped on the row.
type CommitVerifier ¶
CommitVerifier is the per-(repo, sha) verification surface the triager Executor depends on. Returns (true, nil) on a 200; (false, nil) on a 404 (caller tries the next repo); (false, err) on any other status (rate limit, auth, transport) — the AC mandates that non-404 errors abort the verification, not silently fall through.
The CommitVerifier is wired by start.go via ghClient.VerifyCommit; the indirection here keeps internal/daemon free of an internal/github import (the dependency direction matches the rest of the daemon's Config fields).
type Config ¶
type Config struct {
// ProjectRoot is the directory containing .sorcerer/. The daemon
// reads/writes state.json under it. Required.
ProjectRoot string
// BuildCommit is the running daemon binary's embedded build commit SHA
// (cmd/sorcererd buildProvenance().Commit — the SAME value threaded into
// recognizer.Config.BuildCommit). The capability-gap park chokepoint
// (parkPlanOnCapabilityGap) stamps it onto each capability_gap_block record
// as parked_at_daemon_build, so the operator can tell a deploy-lag park from
// a live one. Empty when the build is unstamped (an unwired test daemon).
BuildCommit string
// BranchPrefix is prepended to the lowered issue key when the
// supervisor mints an implementer branch. Already normalized by
// config.applyDefaults to either "" or end-with-slash. Empty =
// bare issue key.
BranchPrefix string
// ProjectLanguage is the managed project's config-sourced primary
// language (config.ProjectLanguage(cfg)). It is the single signal the
// product path consults to learn a project's language, keeping
// language-specific behavior config-sourced rather than assuming Go (the
// product-self boundary; spec R5). The zero value ("") is treated as Go
// by every consumer so unwired test daemons keep the Go path.
ProjectLanguage string
// ProjectIsForeign reports whether the managed project is foreign (not the
// sorcerer self-hosting project) — populated from config.ProjectIsForeign(cfg)
// at daemon start (SPEC-SOR-3317-v1 R2). When true the product-footprint-altitude
// gate is inert: the refusal that fires for a product-domain issue whose
// footprint touches no product-engine-source path never fires on a foreign
// daemon, so a foreign project is no longer subjected to sorcerer-self
// self-hosting altitude semantics. The zero value (false) is self: it preserves
// the gate's self-daemon refusal exactly for every unwired test daemon and
// legacy config (the product-self boundary).
ProjectIsForeign bool
// DeclaredLanguages is the managed project's config-sourced DECLARED
// language SET (config.DeclaredLanguages(cfg)) — the multi-language analog
// of ProjectLanguage. The traceability-matrix generator consults it to
// classify each trace target per-file against the declared set and drop a
// FOREIGN target (a file owned by no declared language) from the regenerated
// matrix (SOR-3096), so a {rust} project's matrix never references .go
// targets tracematrix-verify cannot resolve. The zero value (nil/empty)
// means "no declared-set filtering" — every target passes as native,
// preserving today's behavior for unwired test daemons and legacy configs.
DeclaredLanguages []string
// PropTestRunnerFor resolves the project's config-DECLARED prop_test runner
// for a language (config.PerLanguageConfig.PropTestRunner), threaded from
// cmd/sorcererd/start.go. generatePropTestStubUnion feeds the result through
// langprofile.ResolvePropTestRunner so a language with runner variants
// (TypeScript) emits the selected runner's stub idiom (SPEC-SOR-3093-v1 R4).
// It is nil-safe: a nil field (every unwired test daemon / legacy config)
// means "no declared runner", so selection falls to detection/default and the
// emitted stub stays byte-identical to today's default-runner output. An empty
// returned string for a given language means the same.
PropTestRunnerFor func(lang string) string
// PrePushGate is the managed project's config-sourced pre-push gate
// command (config.ProjectPrePushGate(cfg)), mirroring the ProjectLanguage
// plumbing. cmd/sorcererd/start.go populates it and threads the SAME value
// into the github.Client (WithPreConfiguredGate) and the pre-push verifier
// (NewPrePushGateVerifier), so the daemon-resolved managed gate is hermetic
// and language-native. Empty means "use the language-native auto-detected
// default" — never a self-asset assumption (the product-self boundary).
PrePushGate string
// RelevanceInputsCommand is the project-declared tier-2 build-input
// closure command (config.ProjectRelevanceInputsCommand(cfg)). When
// non-empty, the relevance gate runs it OFF the main goroutine and parses
// its newline-delimited stdout into the declared inputs closure; empty
// means no declared command, so the tier-3 conservative fallback applies
// (every main-advance is relevant). Wired from config in cmd/sorcererd;
// the zero value keeps the conservative fallback so unwired test daemons
// preserve today's all-advances-relevant behavior.
RelevanceInputsCommand string
// DeclaredArtifactPaths is the project's flattened set of declared
// generated-artifact output paths (the GeneratedFiles of every
// config.DeclaredGeneratedArtifacts entry, flattened — see
// cmd/sorcererd/start.go declaredArtifactPaths). It is threaded into the
// footprint-admission chokepoint (seatPredictedFootprint →
// footprint.FilterDeclaredArtifactPaths), so a footprint declaring a
// daemon-generated artifact path is stripped before seating regardless of
// source. The matching httpapi.Config.PlanDeclaredArtifactPaths threads the
// same value into the plan validator's proposal-time guard. Empty/nil keeps
// the matrix-only behavior, so an unconfigured project (and unwired test
// daemons) preserve the prior guard.
DeclaredArtifactPaths []string
// ArtifactVerifyConfigured is true when at least one config-declared generated
// artifact carries a non-empty VerifyArgv — i.e. the github.Client was wired
// with a non-nil artifact verifier (runtime.ArtifactVerifierFn(cfg) != nil).
// The daemon forwards it into invariants.SnapshotInput so the
// generated-artifact-regenerated-at-seam drift invariant fires only for projects
// that actually declare a verify command; an unconfigured project (and unwired
// test daemons) leave it false, so the invariant stays silent.
ArtifactVerifyConfigured bool
// MaxConcurrentImplementers is the concurrency cap passed to
// schedule.Pick. Defaults to 2.
MaxConcurrentImplementers int
// MaxActivePlans is the CIPB admission cap on the number of plans in the
// active integration window (plan_awaiting_children or plan_integrating) at
// once. A continuous_plan_branch plan approved while the window is full
// parks in admission_pending until a slot frees, rather than creating its
// plan branch immediately. Defaults to 3, independent of (and not derived
// from) MaxConcurrentImplementers.
MaxActivePlans int
// EventBufferSize is the events channel capacity. Defaults to 64.
EventBufferSize int
// SchedulerDebounce is how long the debouncer waits for the burst to
// settle. Defaults to 100ms.
SchedulerDebounce time.Duration
// FlowStallThreshold is how long the scheduler-pass producer (the
// Debouncer) may leave a trigger unserviced before the flow-liveness
// monitor (SOR-2630) judges event flow stalled and self-heals. A
// ZERO value DISABLES the monitor entirely — the deliberate default
// for test daemons (no monitor goroutine, no os.Exit risk). Production
// wires the config-sourced default (~5 min) via start.go; New does
// NOT default it, so the monitor is strictly opt-in.
FlowStallThreshold time.Duration
// FlowStallCheckInterval is the flow-liveness monitor's tick cadence.
// Ignored when FlowStallThreshold is zero. Production default ~1 min
// (config-sourced); a zero value with a non-zero threshold disables
// the monitor (the monitor's Run requires both > 0).
FlowStallCheckInterval time.Duration
// FlowStallExitFn is the flow-liveness monitor's process-exit hook,
// injected so tests capture the exit instead of killing the test
// process. nil (production) defaults to os.Exit. Mirrors the
// SignalNotify/SignalStop test-seam pattern.
FlowStallExitFn func(int)
// SchedulerPass is invoked after each debounce window. If nil, the
// daemon uses its own RunSchedulerPass implementation.
SchedulerPass SchedulerPass
// Pollers are external-state poll goroutines. The daemon adopts
// them — Start/Stop is the daemon's responsibility.
Pollers []*Poller
// ShutdownGrace bounds how long Run waits for in-flight handlers
// during shutdown. Defaults to 30s.
ShutdownGrace time.Duration
// Logger is optional; nil disables daemon-level logs (events and
// escalations are persisted to the issuestore regardless).
Logger Logger
// SignalNotify / SignalStop inject the os/signal wiring used by
// installSignalHandler. nil (production) falls through to the real
// signal.Notify / signal.Stop. Tests inject these to drive the
// shutdown-on-signal path with a synthetic signal value instead of
// sending a real SIGTERM to the shared `go test` process: a real
// self-SIGTERM races the handler install, and any window where the
// handler is not yet registered reverts SIGTERM to its default
// disposition, terminating the whole test binary.
SignalNotify func(c chan<- os.Signal, sig ...os.Signal)
SignalStop func(c chan<- os.Signal)
// ImplementerDispatcher is invoked from the scheduler pass for each
// ready leaf. nil disables implementer dispatch (Phase 2 daemon).
ImplementerDispatcher ImplementerDispatcher
// ReviewerDispatcher is invoked when an issue lands in in_review with
// a complete PR set.
ReviewerDispatcher ReviewerDispatcher
// ImplementerDiscovery / ReviewerDiscovery carry the per-project
// discovery-phase tuning knobs (config.Roles.*) the supervisor reads at
// dispatch time to set the discovery dispatch's model/effort override,
// and at telemetry time to attribute the completion + deviation events
// and gate the deviation threshold. Zero value preserves today's
// behavior (frontmatter-default model/effort, 30% threshold).
ImplementerDiscovery DiscoveryRoleConfig
ReviewerDiscovery DiscoveryRoleConfig
// ImplementerRuntime is the per-project implementer dispatch runtime
// (config runtime.implementer): "container" routes the implementer
// claude subprocess through the ContainerRunner, "native" or empty
// keeps it on the host. dispatchImplementerSession stamps it onto the
// dispatched task's RuntimeSelector for both the discovery and
// implementation phases. Empty preserves today's native dispatch.
ImplementerRuntime string
// MergeRunner is invoked when an issue transitions to merging.
MergeRunner MergeRunner
// MergeOptions are passed to MergeRunner. Strategy defaults to "squash".
MergeOptions gh.MergeOptions
// PlanLandingGateRunner runs the FRESH, uncached landing gate for one
// (repo, plan-branch) pair before the plan PR's MergeSet lands the tree
// on main (spec SPEC-SOR-3035-v2 R7/R8/R9/R13). It is the authoritative
// last verification before main — never served from the gate-verdict
// cache — so a tree that was only ever a cache HIT at an intermediate
// plan-branch seam still gets a full, fresh gate run here. Wired to
// (*github.Client).RunPlanLandingGate in cmd/sorcererd/start.go; nil
// disables the landing gate (test mode / back-compat), in which case
// dispatchPlanPRMergeSet proceeds straight to MergeRunner as before.
PlanLandingGateRunner func(ctx context.Context, repoSlug, planBranch, planKey string) error
// PlanPROpener opens the plan PR for one repo of a `branch_model:
// plan_branch` planning issue (SOR-1139): a single PR per repo whose
// head is the long-lived plan branch and whose base is the repo's
// default branch. dispatchPlanPR calls it once per repo after every
// plan_branch child's work has landed on the plan branch. Returns the
// gh-emitted PR URL. Production wiring uses (*github.Client).OpenPlanPR;
// nil routes a plan_branch plan's PR-open to plan_blocked (the daemon
// cannot open the plan PR).
PlanPROpener func(ctx context.Context, repoSlug, planBranch, title, body string) (string, error)
// PerChildPROpener opens the per-child PR for one repo of a
// `branch_model: per_child` implementation issue. Daemon-side peer
// of PlanPROpener: opening the PR moved from the implementer's
// `gh pr create` into the daemon so the verifier chain gates the
// open — a red chain leaves no stale PR behind on GitHub.
// applyImplementerResult's per_child success arm calls it once per
// repo in iss.Repos AFTER the verifier chain reports green, before
// the implementing → in_review transition.
//
// baseBranch is empty for the common case (gh defaults to the
// repo's default branch); set when the per_child issue stacks on a
// sibling's working branch. Production wiring uses
// (*github.Client).OpenPerChildPR; nil routes a per_child success
// to blocked_user via the per_child_pr_open_failed escalation
// (parallel to plan_pr_open_failed).
PerChildPROpener func(ctx context.Context, repoSlug, headBranch, baseBranch, title, body string) (string, error)
// Verifiers is the ordered chain of deterministic gates the daemon
// runs against the implementer's pushed branch on every success-
// path emit BEFORE the per-branch-model in_review transition. Each
// Verifier executes against an ephemeral worktree carved off the
// bare clone pinned to the freshly-fetched tip of the implementer's
// branch; the chain short-circuits on the first failing verifier
// and the supervisor routes the issue back through the existing
// refer-back machinery as synthetic ReviewConcern rows so the next
// feedback-N dispatch carries the captured output. A nil / empty
// slice skips the gate entirely (the test-mode + back-compat
// shape); production wiring assigns
// `[]Verifier{NewPrePushGateVerifier(cfg.CleanupGoBuildCacheMaxBytes(), sink)}`
// in cmd/sorcererd/start.go. Forward-looking enforcement of
// `do-not-do-X` rules plugs in as additional Verifier instances on
// this slice — the LLM cannot bypass a gate it cannot see.
Verifiers []Verifier
// LLMVerifierRunner is the oracle-runner handle threaded onto every
// VerifierEnv (env.LLMRunner) so the llm_test executor can emit a
// structured pass/fail verdict for a requirement's traced code. nil
// (the default + back-compat shape) disables llm_test invocations until
// that oracle executor ships; the deterministic executors ignore it.
// Production wiring assigns the clauderunner-backed implementation in
// cmd/sorcererd/start.go.
LLMVerifierRunner LLMVerifierRunner
// GateRuntimeFactory mints a fresh gateruntime.GateRuntime per gate
// subprocess dispatch — the configured runtime chokepoint every daemon
// verifier-chain gate spawn routes through (spec R1). Threaded onto every
// VerifierEnv (env.GateRuntimeFactory). nil (the default + back-compat
// shape) leaves each spawn site on a fresh &gateruntime.NativeGateRunner{}
// fallback, reproducing the prior direct host spawn. Production wiring
// assigns the native factory in cmd/sorcererd/start.go.
GateRuntimeFactory func() gateruntime.GateRuntime
// GateRuntimeLabel is the configured gate-dispatch runtime (config
// runtime.gate: "native" | "container") the verify-chain gate-result
// audit event carries so the warm-container-vs-native latency-parity
// comparison can be made from the events table (AC: every gate-result
// event carries runtime=native|container + duration). It always agrees
// with GateRuntimeFactory in production (start.go derives both from
// cfg.Runtime.Gate); empty normalizes to "native" at the emit site.
GateRuntimeLabel string
// GateGovernorStat returns the daemon-global gate governor's live running
// and configured-capacity counts (SPEC-SOR-3122-v2). New wires it into
// store.State.GateGovernorStat so the gate-concurrency-within-capacity
// runtime invariant (R12) observes the at-most-K bound live. nil (the
// test/back-compat shape) leaves State.GateGovernorStat nil — the
// "no governor wired" sentinel the invariant skips on.
GateGovernorStat func() (running, capacity int)
// GateHarnessTimeout is the daemon-authoritative effective harness timeout
// (the parsed form of config.ProjectGateHarnessTimeout), populated at daemon
// start and forwarded to the invariants sweep as
// SnapshotInput.GateHarnessTimeout so the gate-harness-timeout-below-budget
// runtime invariant (SPEC-SOR-3074-v1 R5) orders it below the gate-subprocess
// SIGKILL budget (DefaultPrePushGateTimeout). Zero means "no config override
// set; the gate script's own fallback applies" — the invariant skips on zero.
GateHarnessTimeout time.Duration
// GateQueuedGates returns the number of gate dispatches currently blocked
// waiting for a governor slot. verifierChainStale reads it (nil-safe) to
// suppress stale-reaping a verifier chain that is merely queued behind
// running gates rather than genuinely idle (R4 — a queued gate is alive,
// not stalled). nil (the test/back-compat shape) disables the guard, so
// staleness reverts to the pure age check.
GateQueuedGates func() int
// WorkflowDispatcher dispatches a named GitHub Actions workflow on a ref —
// the dispatch_workflow primitive the SOR-2503 activation driver fires for a
// dispatch_workflow-triggered activation criterion (spec R11). Production
// wiring assigns *github.Client (it satisfies WorkflowDispatcher via
// DispatchWorkflow). nil disables only the dispatch_workflow firing path (a
// such-triggered probe defers to pending rather than failing).
WorkflowDispatcher WorkflowDispatcher
// ActivationCIChecker is the narrow CI-run-status accessor the activation
// driver threads into each ci_run probe's ProbeContext (the SOR-2502
// CIRunChecker). Production wiring assigns *github.Client (it satisfies it
// via DispatchedWorkflowRunStatus). nil makes a ci_run probe evaluate to
// pending (CI access not wired) rather than fail.
ActivationCIChecker CIRunChecker
// ActivationProbeEvaluator is the activation-driver test seam: when wired it
// REPLACES the deterministic EvaluateActivationProbe (SOR-2502) so a test can
// drive firing + polling with fake probe results (no live workflow run / CI
// query / worktree). nil (the production default) runs the real evaluator.
ActivationProbeEvaluator func(ctx context.Context, probe *dsl.ActivationProbe, pctx ProbeContext) (string, *role.ProbeEvidence, error)
// VerifierWorktreeAcquirer carves the ephemeral worktree the
// implementer post-submit verifier chain runs each Verifier
// against. Production wiring composes
// `(*github.Client).AcquireVerifierWorktree`; the returned
// teardown is invoked synchronously after the chain run completes
// (success OR failure) so the bare clone's admin metadata stays
// clean. nil disables the chain entirely (the back-compat shape:
// no carve = no chain) so a test that wants a chain run with
// stub verifiers and a hand-built worktree can pass an in-test
// closure here.
VerifierWorktreeAcquirer func(ctx context.Context, projectRoot, repoSlug, issueKey, branch, planBranch string) (string, func(), error)
// PlanPRReadyForReviewMarker flips a draft plan PR to ready-for-review
// (CIPB Phase A+B audit Fix 5). Called from applyPlanPROpenResults
// the moment the plan transitions to plan_awaiting_pr_merge: every
// per-child squash has landed and the cumulative diff is ready for
// final review. Production wiring uses (*github.Client).MarkPRReadyForReview;
// nil disables the flip (the PR stays as draft and the final
// reviewer can flip it manually; the cycle still progresses because
// CI runs on the draft too).
PlanPRReadyForReviewMarker func(ctx context.Context, prURL string) error
// PlanBranchFinalReviewOptionalAtSingleChild mirrors
// config.PlanBranchFinalReviewOptionalAtSingleChild() (SOR-1139). When
// true AND a plan_branch planning issue materialized exactly one
// child, the daemon skips the mandatory final reviewer cycle on the
// plan PR and merges directly. Defaults to false — the final reviewer
// always runs.
PlanBranchFinalReviewOptionalAtSingleChild bool
// MaxRefererBackCycles caps how many `feedback-N` rounds an issue may
// go through before escalating to blocked_user. Defaults to 3.
MaxRefererBackCycles int
// SessionMaxAges maps role name → wall-clock budget. Sessions that
// exceed their budget are marked timed_out by the per-session timer.
SessionMaxAges map[string]time.Duration
// WorktreeCleanup is called for each (issue, repo) when an issue
// terminates (merged / abandoned). nil disables cleanup; the
// production wiring uses internal/github.RemoveWorktree.
WorktreeCleanup func(projectRoot, issueKey, repoSlug string) error
// PlanBranchWorktreeCleanup is the per-(plan, child, repo) analog
// of WorktreeCleanup for `branch_model: plan_branch` issues
// (SOR-1136). Called for each (plan, child, repo) triple when a
// plan_branch child issue terminates (merged / abandoned). nil
// disables plan-branch worktree cleanup; production wiring uses
// internal/github.RemovePlanChildWorktree.
PlanBranchWorktreeCleanup func(projectRoot, planKey, childKey, repoSlug string) error
// WorktreePrep ensures a worktree exists for (issue, repo) on the
// given branch and returns its absolute path. Called from
// dispatchImplementer before the goroutine runs. Production wiring
// composes EnsureBareClone + AddWorktree from internal/github.
// nil falls back to the canonical WorktreeDir path with no FS
// preparation — useful for tests that don't actually run an agent.
WorktreePrep func(ctx context.Context, issueKey, repoSlug, branchName string) (string, error)
// DeferredChildWorktreePrep is the deferred-integration per-(plan,
// child, repo) worktree prep for branch_model: plan_branch children:
// under deferred integration no long-lived plan branch is minted at
// dispatch, so the child working branch is carved off baseBranch
// instead — origin's default branch (baseBranch == "") when the child
// has no sibling dependency, or a sibling dependency's working branch
// (baseBranch != "") when the child stacks on one. Production wiring
// composes EnsureBareClone + AddDeferredPlanChildWorktree. childBranch
// is the per-child working branch (ChildWorkingBranchName). nil falls
// back to PlanBranchWorktreeDir with no FS preparation — for tests
// that don't run an agent.
DeferredChildWorktreePrep func(ctx context.Context, planKey, childKey, repoSlug, childBranch, baseBranch string) (string, error)
// PlanFixWorktreePrep is the SOR-1329 plan-PR-fix worktree prep.
// Called from dispatchPlanPRFix before the
// implementer goroutine runs, once per repo. Production wiring composes
// EnsureBareClone + EnsurePlanBranch + AddPlanFixWorktree: the worktree
// is checked out on the plan branch DIRECTLY (no per-child branch), so
// the fix implementer commits + pushes the plan branch and the open
// plan PR auto-advances. planBranch is the long-lived plan branch
// (PlanBranchName). nil falls back to PlanFixWorktreeDir with no FS
// preparation — useful for tests that don't actually run an agent.
PlanFixWorktreePrep func(ctx context.Context, planKey, repoSlug, planBranch string) (string, error)
// PlanFixWorktreeCleanup is the per-(plan, repo) analog of
// PlanBranchWorktreeCleanup for the SOR-1329 plan-PR-fix worktrees.
// Called best-effort when a fix dispatch resolves (success or failure)
// and from the terminal plan-branch cleanup hook. nil disables plan-fix
// worktree cleanup; production wiring wraps RemovePlanFixWorktree.
PlanFixWorktreeCleanup func(projectRoot, planKey, repoSlug string) error
// EphemeralWorktreePruner prunes stale, locked plan-branch ephemeral
// worktrees (plan-assemble- / cpb-main-merge- / cpb-squash- / verifier-)
// left on disk when the daemon was killed mid-operation, returning the
// prune count. Called by the periodic ephemeral-worktree-prune sweep off
// the main goroutine (pure filesystem + git I/O against the bare clones;
// touches no d.state). nil disables the sweep — test daemons that don't
// manage real bare clones leave it unset. Production wiring wraps
// gh.Client.PruneStaleEphemeralWorktrees with EphemeralWorktreeMaxAge.
// The pruner emits one ephemeral_worktree_pruned audit event per reaped
// worktree through the github client's EventSink.
EphemeralWorktreePruner func(ctx context.Context) (int, error)
// EphemeralWorktreeLister is the read-only probe behind the
// worktree-no-leaked-locked-ephemeral runtime invariant. For one
// daemon-managed bare clone it returns that clone's locked ephemeral
// worktrees (plan-assemble- / cpb-main-merge- / cpb-squash- / verifier-
// prefixed) with each one's path, lock reason, and age. Consumed by the
// invariants-check sweep — wired into invariants.SnapshotInput's
// WorktreeListProbe so the invariant can flag a leaked, locked ephemeral
// worktree whose owning operation died before it collides with a later
// `git worktree add`. Unlike EphemeralWorktreePruner it never mutates the
// bare clone. Production wiring wraps
// (*github.Client).ListLockedEphemeralWorktrees via
// runtime.EphemeralWorktreeListerFn; nil disables the invariant's check
// (test daemons that don't manage real bare clones leave it unset).
EphemeralWorktreeLister func(ctx context.Context, bareClone string) ([]invariants.LockedEphemeralWorktree, error)
// PerIssueWorktreeLister enumerates every top-level per-issue worktree
// wrapper under <projectRoot>/.sorcerer/worktrees/, returning each one's
// key basename, absolute path, and mtime age (SOR-2817). Consumed by
// sweepPerIssueWorktreeGC — the periodic per-issue worktree GC backstop,
// the self-heal analog of EphemeralWorktreePruner keyed on owning-issue
// terminal state rather than worktree age. The sweep snapshots the
// reapable owner-key set from d.state on-main, then runs this lister +
// the os.RemoveAll reap off-main (the no-main-loop-blocking-call
// contract). nil disables the GC sweep — test daemons that don't manage a
// real project tree leave it unset. Production wiring wraps
// (*github.Client).ListPerIssueWorktrees via runtime.PerIssueWorktreeListerFn.
PerIssueWorktreeLister func(ctx context.Context) ([]gh.PerIssueWorktreeEntry, error)
// PlanPRCommenter posts a plain comment on a plan PR (SOR-1329). The
// autonomous refer-back resolver uses it to record the final
// reviewer's concerns + rationale on each plan PR so the fix
// implementer can read them via `gh pr view`. Best-effort: a nil hook
// or an error is logged and never blocks the fix dispatch (the
// concerns also ride the implementer envelope's PLAN_PR_REFER_BACK
// block). Production wiring wraps gh.Client.CommentOnPR.
PlanPRCommenter func(ctx context.Context, prURL, body string) error
// PlanPRViewer fetches one plan PR's current state for the SOR-1360
// pending-CI poller. Returns the full github.PR shape so the poller
// can read mergeable + mergeable_state + statusCheckRollup at the
// action boundary (rather than caching upstream from the final
// reviewer's stale view). Production wiring uses
// (*github.Client).ViewPR; nil disables the pending-CI poller (plans
// in plan_pr_merge_pending_ci sit there until an operator intervenes).
PlanPRViewer func(ctx context.Context, prURL string) (*gh.PR, error)
// PlanPRBranchDiscoverer looks up the most recent PR (any state —
// OPEN | CLOSED | MERGED) for the canonical plan branch name on
// repoSlug (SOR-1375). The runtime + boot-time plan external-merge
// auto-recovery paths fall back to this when iss.PRURLs is empty —
// the bridge case for a plan PR opened by the operator after a manual
// assembly resolution, where the daemon never recorded the URL.
// Returns (nil, nil) when no PR exists for that branch; the recovery
// then skips this (plan, repo). Production wiring uses
// (*github.Client).FindPRByBranchAnyState; nil disables the fallback
// (PRURLs-populated detection still runs).
PlanPRBranchDiscoverer func(ctx context.Context, repoSlug, branchName string) (*gh.PR, error)
// PlanPROpenDiscoverer looks up the OPEN PR for the canonical plan
// branch name on repoSlug (`gh pr list --head <plan-branch> --state
// open`). The cpb-pr-url-backfill boot sweep uses it to backfill
// iss.PRURLs from an already-open plan PR when a restart landed between
// the PR-open dispatch and its result event — closing the duplicate-
// PR-open class on the recovery side. Returns (nil, nil) when no open
// PR exists for that branch; the sweep then skips this (plan, repo).
// Production wiring uses (*github.Client).FindPRByBranch; nil disables
// the sweep.
PlanPROpenDiscoverer func(ctx context.Context, repoSlug, branchName string) (*gh.PR, error)
// PlanPRMergePendingCITimeout is the wall-clock cap on how long a
// plan may sit in plan_pr_merge_pending_ci before the poller routes
// it to plan_blocked with the ci_timeout escalation. Reads
// config.PlanBranch.PRMergePendingCITimeoutSeconds; defaults to
// 2 hours in applyDefaults. SOR-1360.
PlanPRMergePendingCITimeout time.Duration
// PlanPRMergePendingCIStallBound is the wall-clock bound after which a
// non-terminal required check in plan_pr_merge_pending_ci is treated as
// stalled and the daemon recovers it (rung 1 re-dispatch, rung 2 escalate)
// rather than polling forever — the SOR-2773 never-terminating-check
// strand. Reads config.PlanBranch.PRMergePendingCIStallBoundSeconds;
// defaults to 60 minutes in applyDefaults, strictly below the 90-min
// completed-plan-merge-latency-bounded window so recovery precedes the
// strand. SOR-3152.
PlanPRMergePendingCIStallBound time.Duration
// CompletedPlanProgressWindow is the leader-progress / yield-ceiling
// window for completedPlanHoldsHeadOfLine: a strictly-older completed
// plan holds the head-of-line over a ready follower only while its land
// step is in flight and its last advance is within this window. Reads
// config.PlanBranch.CompletedPlanProgressWindowSeconds; defaults to
// 300 seconds in applyDefaults. A zero value falls back to a 5-minute
// sentinel inside the predicate so an un-wired test daemon still bounds
// the hold. SOR-2685.
CompletedPlanProgressWindow time.Duration
// ActionsRerunner re-requests every failed Actions run on the given
// (repoSlug, headSHA) pair via gh's REST surface. Returns the
// number of runs re-requested. The Actions-only flake-retry path
// uses this exactly once per (check, head-SHA) before falling
// through to the existing CI-failure refer-back routing.
// Production wiring uses (*github.Client).RerunFailedActionsRuns;
// nil disables the flake-retry path (terminal-failure CI routes
// straight to feedback as it did pre-fix). Tests stub in a fake
// that records the call.
ActionsRerunner func(ctx context.Context, repoSlug, headSHA string) (int, error)
// ActionsWaitTimeout is the per-child `verdict_merge` wall-clock
// cap on how long the daemon waits for Actions to report terminal-
// success on the PR's head SHA before routing the issue to
// blocked_user with escalation reason `actions_wait_timeout`. Reads
// config.Daemon.ActionsWaitTimeoutSeconds; defaults to 45 minutes
// in applyDefaults.
ActionsWaitTimeout time.Duration
// RoleWorktreeCleanup is the per-(role, session, repo) analog of
// WorktreeCleanup for the reviewer / planner / plan_reviewer roles.
// Called best-effort when a role session tears down (clean exit,
// dispatcher error, ctx-cancel, panic-unwind) and from the boot-time
// orphan reap. Failures are logged, never fatal. nil disables
// role-worktree cleanup; production wiring wraps
// internal/github.RemoveRoleWorktree.
RoleWorktreeCleanup func(projectRoot, role, sessionID, repoSlug string) error
// RoleWorktreePrep ensures a private detached worktree exists for a
// (role, session, repo) triple and returns its absolute path. The
// reviewer / planner / plan_reviewer roles are read-mostly and never
// push, so there is no caller-supplied branch. nil falls back to no
// FS preparation (prepareRoleWorktree returns an empty path + nil
// error). Production wiring composes EnsureBareClone + a detached
// worktree. Consumed by the three role dispatch sites.
RoleWorktreePrep func(ctx context.Context, role, sessionID, repoSlug string) (string, error)
// PRSetDiscoverer is the GitHub-side hook for PR-set discovery: the
// supervisor calls this to find the implementer's branch's open PR in
// every affected repo. SOR-1148: this is the PRIMARY path on the
// IMPLEMENT_OK success seam — the implementer no longer supplies PR
// URLs, so the daemon discovers them here (one FindPRByBranch lookup
// per repo) before transitioning to in_review. It is ALSO the
// crash-before-marker recovery path (session ended without emitting
// IMPLEMENT_OK). A complete set means the work is durable; the
// supervisor writes iss.PRURLs and transitions the issue to
// in_review. The map's values carry both the PR URL and the head SHA
// gh reported at discovery time — the blocked_user dedupe sweep
// reuses the SHA to detect operator commits that should bypass the
// cooldown. Returns (nil, *recovery.PartialSetError) on partial sets
// — the supervisor must NOT mark the issue in_review on those.
// nil disables recovery; on the success seam a nil hook routes the
// issue to blocked_user (the daemon cannot derive the PR set).
PRSetDiscoverer func(ctx context.Context, branch string, repos []string) (map[string]DiscoveredPR, error)
// DefaultBranchResolver resolves repoSlug's default branch (origin's
// HEAD, e.g. "main") to its short name. The deferred-integration
// reviewer dispatch calls it once per repo on a branch_model:
// plan_branch child that has no sibling dependency, to populate
// ReviewerTask.BaseRef — the ref the child branch was carved off and
// the reviewer diffs against (`git diff <base>..<child-branch>`). A
// child that stacks on a sibling dependency uses that sibling's
// working branch as the base instead and never calls this. Production
// wiring uses (*github.Client).DefaultBranchName; nil makes the
// no-sibling-dep plan_branch reviewer dispatch fail. Unused for
// per_child issues.
DefaultBranchResolver func(ctx context.Context, repoSlug string) (string, error)
// MergeResolverDispatcher is the autonomous-resolver dispatch hook
// the plan-branch assembler's ConflictResolver closure calls when a
// child's squash conflicts (SOR-1373). When non-nil, the daemon
// builds a ConflictResolver closure that translates each
// AssemblyConflictDispatch into a role.MergeResolverTask, dispatches
// the `merge_resolver` agent, and retries up to
// mergeResolverMaxRetries (N=2) attempts per conflicting child
// before falling through to today's `failPlanAssemble` →
// `plan_blocked` cascade. When nil, the daemon passes nil to
// AssemblePlanBranch and today's direct-to-plan_blocked path is
// preserved bit-for-bit. Production wiring uses
// role.NewMergeResolverDispatcher; the retry budget is not
// user-tunable.
MergeResolverDispatcher MergeResolverDispatcher
// AssemblyFixerDispatcher is the autonomous-fix-up dispatch hook
// the daemon's plan_auto_fixing follow-on calls when a plan's
// cumulative dry-run gate (SOR-1443 Fix 1) or real assembly push
// fails. When non-nil, the daemon renders an AssemblyFixerTask
// (carrying the captured pre-push gate artifact path + the parked-
// children cohort) and dispatches the `assembly_fixer` agent; the
// supervisor applies each emitted action (file_implementation_issue,
// refer_back_child, escalate_capability_gap) through existing
// mutation surfaces. The outer-retry budget is M=2 dispatches per
// plan before the supervisor synthesizes an
// escalate_capability_gap on exhaustion so the autonomy gap is
// tracked sorcerer-side regardless. When nil, plan_auto_fixing
// stays as a pure SM state and the operator must re-arm manually
// (`sorcerer issue transition <P-key> plan_auto_fixing` is
// operator-only) — useful for test daemons that don't wire a
// agentcli. Production wiring uses
// role.NewAssemblyFixerDispatcher.
AssemblyFixerDispatcher AssemblyFixerDispatcher
// EnsurePlanBranchFn is the CIPB Phase B plan-branch creator. For a
// continuous_plan_branch planning issue that just landed at
// plan_awaiting_children, dispatchPlanBranchCreate calls it once per
// affected repo: ensure the canonical plan branch (PlanBranchName)
// exists in the bare clone AND on origin, cut from origin's current
// default branch. Production wiring composes EnsureBareClone +
// EnsurePlanBranch from internal/github. Idempotent — a branch that
// already exists is a no-op (the implementation short-circuits both
// the local-ref probe and the ls-remote probe). nil falls back to
// a no-op skip (test daemons that don't actually run git); the
// supervisor's follow-on sweep tolerates the absence by leaving
// PlanCommitSHAs unset for affected repos.
EnsurePlanBranchFn func(ctx context.Context, repoSlug, planBranch string) error
// ResetPlanBranchFn resets an existing plan branch back to current
// main's tip for a re-decomposition, so the new decomposition's
// children build on a clean base with none of the prior (canceled)
// decomposition's diverged commits. Production wiring composes
// (*github.Client).ResetPlanBranchToMain (delete-then-push the captured
// main SHA — never a force-push). nil falls back to a no-op skip (test
// daemons that don't run git); the re-decomposition consumption seam
// tolerates the absence.
ResetPlanBranchFn func(ctx context.Context, repoSlug, planBranch string) error
// ClosePlanPRFn closes the prior plan PR for a re-decomposed planning
// issue. no-op-safe when the PR is already closed, already merged, or
// absent. Production wiring composes (*github.Client).ClosePlanPR. nil
// falls back to a no-op skip (test daemons that don't run gh).
ClosePlanPRFn func(ctx context.Context, repoSlug, prURL string) error
// ChildActualDiffResolver computes the per-child actual diff used by
// emitPredictionCheckWithRealDiff (CIPB Phase A+B audit Fix 1). The
// Phase A/B PRs left the EmitPredictionCheck hook sites passing
// nil/nil — every event classified every predicted file as
// over_predicted and emitted zero anchor data, poisoning Phase D's
// learning loop before it starts. This resolver runs `git diff
// --name-only` + `git show` in the child's bare clone and feeds the
// real file list + per-path content into EmitPredictionCheck.
// Production wiring uses NewChildActualDiffResolver(ghClient,
// projectRoot, branchPrefix); a nil resolver falls through to the
// pre-fix EmitPredictionCheck(nil, nil, now) so tests that don't
// wire git still exercise the emission path.
ChildActualDiffResolver ChildActualDiffResolver
// ImplementerCycleDiffStater computes the best-effort git-diff numstat
// totals (files changed / lines added / lines removed) for an
// implementer cycle's worktree branch, feeding the
// implementer_cycle_summary telemetry. Invoked off-main in the
// dispatch goroutine (never on the single-writer main loop) with the
// dispatch's per-repo worktree paths. Production wiring uses
// NewImplementerCycleDiffStater(ghClient, projectRoot); a nil stater
// leaves the file/line fields zero (the test path, and any cycle whose
// branch never materialized). Best-effort: an error is logged and the
// event records zero totals.
ImplementerCycleDiffStater ImplementerCycleDiffStater
// PlanBranchHeadResolver probes the current HEAD SHA of a plan branch
// in the bare clone (CIPB Phase A+B audit fix-up). Called by
// applyPlanBranchCreateResult AFTER EnsurePlanBranchFn succeeds so
// the per-repo PlanCommitSHAs entry records the actual commit SHA
// (not just the branch name). Production wiring uses
// (*github.Client).PlanBranchHeadSHA; nil falls back to recording the
// branch name as the sentinel (the pre-audit-fix behavior). The brief
// (Fix 7) mandates the actual SHA so the field honors its name.
PlanBranchHeadResolver func(ctx context.Context, repoSlug, planBranch string) (string, error)
// PlanBranchRemoteRefResolver probes the live state of a plan branch
// on origin: `git ls-remote origin refs/heads/<planBranch>` in the
// bare clone. Returns (sha, exists, err): exists==false with a nil
// err means the ref is genuinely absent on origin; a non-nil err is a
// probe failure the caller must skip on. Consumed by the
// invariants_check sweep — wired into invariants.SnapshotInput so the
// plan-branch existence + tip-consistency invariants can detect a
// mid-lifecycle plan whose branch was deleted (or whose recorded tip
// drifted) on origin. Production wiring uses
// (*github.Client).PlanBranchRemoteRef; nil disables the plan-branch
// invariants' check (test daemons that don't run git).
PlanBranchRemoteRefResolver func(ctx context.Context, repoSlug, planBranch string) (string, bool, error)
// ChildSquasher is the CIPB Phase B per-child squash handler. For a
// continuous_plan_branch child whose reviewer just returned `merge`,
// the daemon transitions in_review → merging and spawns the squash
// session goroutine; the goroutine invokes ChildSquasher once per
// repo. The squasher carves an ephemeral worktree off the bare
// clone, checked out to the plan-branch tip, runs `git merge --squash`
// of the child's working branch, runs the per-push gate, pushes the
// new plan-branch tip, and returns the new plan-branch HEAD SHA.
//
// The trailing resolver argument is the autonomous merge-resolver
// callback: when the squash conflicts AND resolver is non-nil, the
// squasher invokes resolver synchronously inside the locked
// ephemeral worktree; the resolver lands a clean resolution commit
// and the squasher pushes. A nil resolver routes the conflict
// directly to *github.AssembleConflict (the daemon then routes the
// child to feedback). Production wiring uses
// (*github.Client).SquashChildIntoPlanBranch; nil routes a cpb
// `merge` verdict's squash to blocked_user (the daemon cannot
// squash).
ChildSquasher ChildSquasher
// ConflictedWorktreeCarver re-materializes a per-child squash conflict in a
// fresh ephemeral worktree carved off the bare clone so the daemon can
// dispatch the merge_resolver on its OWN session budget — decoupled from the
// squash episode (SOR-2739). The squash op (ChildSquasher) returns
// *github.AssembleConflict and tears down its own worktree without running
// any resolver; applyCPBChildSquashResult's conflict arm calls this to
// reproduce the conflict markers for the resolver to edit. The resolver's
// commit inside the carved worktree auto-records the rerere resolution in the
// shared rr-cache, which the re-dispatched finalize's re-squash replays.
// Returns the worktree path, conflicting paths, a teardown closure, and an
// error. Production wiring uses
// runtime.ConflictedWorktreeCarverFn(ghClient, projectRoot); a nil carver
// makes the conflict arm fall through to the legacy assembly_fixer / feedback
// safety net (test daemons that don't run git).
ConflictedWorktreeCarver func(ctx context.Context, repoSlug, planBranch, childBranch string) (string, []string, func(), error)
// PlanMainMerger is the CIPB Phase C main-merge handler. Under the
// event-triggered model (SOR-2187) the sweepPlanMainMerge tick spawns
// the off-main origin/<default> tip-change probe; a probed tip that
// advanced past the recorded one dispatches dispatchPlanMainMerge,
// which calls this once per repo. The merger carves an ephemeral
// worktree off the bare
// clone, runs `git merge --no-ff origin/<default>` against the
// live plan-branch tip, runs the per-push gate against the merge
// commit, and pushes the new plan-branch tip with a REGULAR push
// (NEVER --force, NEVER --force-with-lease — locked by
// TestMergeDefaultIntoPlanBranch_PushIsNeverForce).
//
// The trailing resolver argument is the autonomous merge-resolver
// callback: when the merge conflicts AND resolver is non-nil, the
// merger invokes resolver inside the locked ephemeral worktree;
// the resolver lands a clean resolution commit and the merger
// pushes. A nil resolver routes the conflict directly to
// *github.MainMergeConflict (the daemon then routes the failure
// to the assembly_fixer chain — same Fix 3+4 shape the per-child
// squash uses).
//
// Production wiring uses (*github.Client).MergeDefaultIntoPlanBranch
// via runtime.PlanMainMergerFnFromClient; nil disables the periodic
// merge entirely — the sweep emits a `plan_main_merge_skipped`
// event per candidate and the plan branch may fall behind main
// indefinitely (test daemons that don't run git).
PlanMainMerger PlanMainMerger
// PlanBareCloneRerereResetter clears the bare clone's `rr-cache` for
// the given (repoSlug) by running `git rerere clear` against the
// bare clone. Wired in production via
// runtime.PlanBareCloneRerereResetterFromClient — a closure around
// gh.Client.GitIn + gh.BareCloneDir. The stale-rerere recovery sweep
// invokes it once per repo it walks (under the per-(repoSlug,
// planBranch) plan-branch mutation mutex acquired by the sweep
// itself, via gh.AcquirePlanBranchMutation). Best-effort + non-
// fatal: the sweep logs on error and proceeds to the next repo. A
// nil callback disables the actual rerere clear but the sweep still
// runs (TOCTOU re-check + plan re-arm); useful for test daemons.
PlanBareCloneRerereResetter func(ctx context.Context, repoSlug string) error
// CapabilityGapDeployChecker reports whether potentialAncestor is an
// ancestor of commit in the bare clone for repoSlug — i.e. whether the
// running daemon build (commit) includes a merged commit (potentialAncestor),
// computed via `git merge-base --is-ancestor`. The SOR-3084 deploy-gated
// release sweep uses it as the deployed != merged predicate: a parked plan's
// gap fix is DEPLOYED only when its squash SHA is an ancestor of the daemon's
// OWN build commit. Wired in production via
// runtime.CapabilityGapDeployCheckerFromClient (a closure around
// gh.Client.GitInOutput + gh.BareCloneDir). Nil DISABLES the release sweep
// (test daemons that don't run git, and daemons that simply opt out): the
// sweep is a no-op and parked plans stay parked.
CapabilityGapDeployChecker func(ctx context.Context, repoSlug, potentialAncestor, commit string) (bool, error)
// PlanBranchSHAPusher recreates an externally-deleted plan branch on
// origin by pushing the recorded plan-branch tip SHA from the daemon's
// own bare clone (`git push origin <sha>:refs/heads/<planBranch>`).
// Wired in production via runtime.PlanBranchSHAPusherFn — a closure
// around (*github.Client).RecreatePlanBranchFromSHA. The daemon-side
// recreatePlanBranchIfMissing self-heal owns the per-(repoSlug,
// planBranch) mutex acquire and the withPushRetry wrapping; this
// callback is the bare push and returns the typed push errors the
// shared classifier routes on. A nil callback disables the self-heal
// (recreatePlanBranchIfMissing returns "not needed"); useful for test
// daemons that don't run git.
PlanBranchSHAPusher func(ctx context.Context, repoSlug, sha, planBranch string) error
// RererePreventiveClearCadence is the cadence at which the periodic
// main-merge handler runs a preventive `git rerere clear` against the
// daemon's bare clone (inside the per-(repoSlug, planBranch) mutex,
// before the merge attempt) when the per-(project, repo)
// last_rerere_clear_at is older than it. Reads
// config.RererePreventiveClearCadence(); a non-positive value disables
// the preventive clear (buildPreventiveRerereClearGate returns a nil
// gate), the path a test daemon takes when it leaves this unwired.
RererePreventiveClearCadence time.Duration
// GitHubTokenForceRefresher forces a fresh GitHub App installation
// token regardless of the cached expiry (SOR-1461). Wired in
// production to (*github.TokenRefresher).Refresh. The CIPB plan-branch
// push-auth recovery path calls it before re-dispatching a squash /
// main-merge that failed with an HTTP 401/403, so the retry pushes
// with a freshly-minted token rather than waiting for the Client's
// leeway-based proactive refresh to notice the expiry. nil → the
// auth-retry path skips the explicit refresh and relies on the
// Client's own pre-call refresh (test daemons, or deployments without
// a refresher wired).
GitHubTokenForceRefresher func(ctx context.Context) error
// PlanMainMergeSweepCadence is the CIPB Phase C main-merge sweep
// poll interval. Under the event-triggered model (SOR-2187) the
// main-merge no longer fires on this cadence — it fires from the
// daemon's own merge-to-main completion fan-out (immediate) and from
// the external origin/<default> tip-change probe. This value is the
// poll interval for that tip-change PROBE, i.e. how often the daemon
// checks for an external push to main; it is no longer a re-merge
// interval, so an unchanged tip dispatches nothing. Reads
// config.Daemon.PlanMainMergeSweepSec; defaults to 5 minutes
// (applyDefaults), which remains appropriate for external-push
// detection. Consumed by the poller wiring in cmd/sorcererd/start.go,
// not by the daemon's sweep handler.
PlanMainMergeSweepCadence time.Duration
// PlanBranchInFlightDedupCap is the staleness cap for the three
// persisted plan-branch in-flight dedup markers
// (PlanEarlyDraftPROpenInFlightAt / PlanMainMergeInFlightAt /
// CPBChildSquashInFlightAt). The boot-pass sweep cpb-in-flight-stale-clear
// clears any non-zero marker older than this cap, and the
// in-flight-dedup-bounded runtime invariant fires on any non-zero marker
// older than it. Reads config.PlanBranch.InFlightDedupCapSeconds; a zero
// value falls back to 2 × planMainMergeRepoTimeout at the read site
// (planBranchInFlightDedupCap helper) — the default lives there, next to
// the op-timeout constant, so it tracks it.
PlanBranchInFlightDedupCap time.Duration
// OrphanPRDiscoverer scans GitHub for open bot-authored PRs that no
// in-flight Issue currently claims. Called from a periodic poller;
// the daemon synthesizes Issue records (with OrphanAdopted=true)
// for each result so the reviewer + merge gate can pick them up.
// nil disables orphan-PR adoption.
OrphanPRDiscoverer func(ctx context.Context, claimed map[string]bool) ([]OrphanPRRef, error)
// OrphanBranchEnumerator lists every remote branch name on repoSlug
// ("owner/repo"). The boot-time orphan-branch sweep (SOR-322) calls
// it once per cfg.Repos repo. Production wiring sets this to
// gh.Client.ListRemoteBranches; nil disables the sweep (test daemons
// that don't exercise it).
OrphanBranchEnumerator func(ctx context.Context, repoSlug string) ([]string, error)
// OrphanBranchDeleter deletes branchName on repoSlug's remote.
// Production wiring sets this to gh.Client.DeleteRemoteBranch (a 404
// is success per that primitive's idempotent contract); nil disables
// the boot-time orphan-branch sweep.
OrphanBranchDeleter func(ctx context.Context, repoSlug, branchName string) error
// DeleteOrphanBranches gates the boot-time orphan-branch sweep
// (SOR-322). Reads config.CleanupDeleteOrphanBranches(); when false,
// the sweep does not run at all (no enumeration, no deletion, no
// events). The config knob defaults to true, so the production
// wiring passes true unless the operator pinned
// `cleanup.delete_orphan_branches: false`.
DeleteOrphanBranches bool
// WIPPreserver force-pushes the worktree's uncommitted state to
// wip/<session-id> on GitHub before an issue's terminal-failed
// transition runs cleanup. Returns the wip branch name on success.
// nil disables WIP preservation; failed transitions just clean up
// without saving the diff.
WIPPreserver func(ctx context.Context, sessionID, repoSlug, worktreePath string) (wipBranch string, err error)
// RemoteBranchDeleter best-effort deletes branchName on repoSlug's
// remote. Invoked by the terminal-transition branch-cleanup helper
// (cleanupTerminalBranches) to reap the WIP branch
// preserveWIPIfConfigured force-pushed (on both merged and
// abandoned) and — on abandoned only, when MergeOptions.DeleteBranch
// is set — the implementer's feature branch. Production wiring uses
// (*github.Client).DeleteRemoteBranch (idempotent: a 404 returns
// nil). nil disables terminal-transition branch cleanup.
RemoteBranchDeleter func(ctx context.Context, repoSlug, branchName string) error
// LocalBranchDeleter best-effort deletes branchName from the bare
// clone for repoSlug under projectRoot. SOR-1136: invoked by the
// plan-branch cleanup helper (cleanupPlanBranchPlanArtifacts) when
// a planning issue terminates, the per-child cleanup helper
// (cleanupPlanBranchChildWorktrees) when a plan_branch child reaches
// a terminal state, and the boot-time plan-branch orphan sweep
// (sweepOnePlanBranch) when a stale plan or child working branch
// survives a daemon restart. `git fetch --prune` only touches the
// default branch's refspec, so a plan-branch ref would otherwise
// linger in the bare clone. Production wiring uses
// (*github.Client).DeleteBranch; nil disables local cleanup.
LocalBranchDeleter func(projectRoot, repoSlug, branchName string, force bool) error
// LocalBranchProbe reports whether branchName exists in the bare
// clone for repoSlug under projectRoot. SOR-1136: invoked by the
// boot-time plan-branch rediscovery sweep (rediscoverPlanBranchState)
// to learn which plan / child working branches already exist on
// restart — branch existence lives only in git refs, not the
// issuestore, so the daemon has no in-memory record after a crash.
// Read-only: it never creates a ref. Production wiring uses
// (*github.Client).LocalBranchExists; nil disables the rediscovery
// sweep (test daemons that don't wire the git surface).
LocalBranchProbe func(projectRoot, repoSlug, branchName string) (bool, error)
// PathExistsOnDefaultBranch reports whether path exists on the
// configured default branch of repoSlug's bare clone under the
// project root. The forward-referencing-paths predispatch lint
// consults it once per (repo, path) pair extracted from a per_child
// standalone issue's AC; a returned (false, nil) is the load-bearing
// signal that the path is forward-referencing (only resident on a
// sibling sweep branch of a multi-issue plan, not on the branch the
// per_child implementer would carve). Production wiring resolves the
// default branch through the centralized config-sourced chokepoint
// (config.DefaultBranch) — never the set-once `refs/remotes/origin/HEAD`
// symbolic ref — and shells out to
// `git -C <bareclone> cat-file -e refs/heads/<default>:<path>`,
// treating `git`'s "not a valid object name" exit-1 as (false, nil);
// any other error (bare clone missing, exec failure) is surfaced so
// the lint fails open rather than blocking on a flaky probe. nil
// disables the lint entirely (test daemons that don't wire the git
// surface stay deterministic).
PathExistsOnDefaultBranch func(ctx context.Context, repoSlug, path string) (bool, error)
// RawCloneSweepRunner (SOR-1162) re-runs the boot-time raw-clone-
// pristine sweep against the project root. Called by the periodic
// recovery sweep (`raw_clone_drift` kind) so mid-run pollution —
// an agent's bash session that drifted into the project root and
// ran a mutator, or a daemon code path that accidentally targeted
// the project root — is caught at the next recovery tick instead
// of waiting for daemon restart. Return values: (true, true) when
// the sweep restored HEAD AND stashed a dirty tree; (true, false)
// for HEAD-only restore; (false, true) for stash-only; (false,
// false) for a no-op (already-pristine clone). Production wiring
// composes runtime.RawCloneSweep with the same audit-sink the
// boot path uses. nil disables the sweep — the heartbeat row still
// fires, the per-kind details are empty.
RawCloneSweepRunner func(ctx context.Context) (headRestored bool, workingTreeStashed bool, err error)
// RawCloneDriftAttributeProbe (SOR-1162) runs a read-only drift
// check against the project-root raw clone immediately after a
// role session tears down. When drift is detected (HEAD off
// default OR working tree dirty), the probe emits one
// `raw_clone_drift_attributed` audit event keyed by (role,
// sessionID) so the operator can attribute the pollution to the
// agent that caused it. Restorative work is NOT part of this
// probe — the next recovery-sweep tick (`raw_clone_drift` kind)
// runs the actual restore via RawCloneSweepRunner. nil disables
// the probe; the recovery sweep still catches the drift later
// without per-session attribution.
RawCloneDriftAttributeProbe func(role, sessionID string)
// PlannerDispatcher runs one planner task and returns the parsed
// result. Single-shot per docs/architecture.md.
PlannerDispatcher PlannerDispatcher
// PlanReviewerDispatcher runs one plan-review task between the
// planner's success and the daemon's Linear writes. Reviews the
// proposed issues for design-quality defects (drift surfaces,
// scope creep, hypothetical-API deps). On approve, the supervisor
// proceeds to write Linear; on reject, the planner's plan is
// discarded and an escalation is filed.
// nil disables the gate; the supervisor writes directly to Linear.
PlanReviewerDispatcher PlanReviewerDispatcher
// SpecDrafterDispatcher runs one spec_drafter task (TASK: draft /
// amend) on the long-lived spec_drafter conversation and returns the
// SHAPE-validated draft (spec-driven phase B). nil disables the
// spec_drafter dispatch — a planning issue parked in spec_drafting is
// then never picked up. When SpecsEnabled is true and this dispatcher +
// SpecVerifier are both wired, the request-ingestion cutover
// (handleRequestArrived) creates /sorcerer-request planning issues
// directly in spec_drafting and sweepSpecDraftingDispatch drives the
// initial draft dispatch on the next scheduler pass.
SpecDrafterDispatcher SpecDrafterDispatcher
// SpecsEnabled is the per-project spec-driven-workflow cutover switch
// (config specs.enabled, default false). When true AND
// SpecDrafterDispatcher + SpecVerifier are wired, a fresh
// /sorcerer-request planning issue is minted directly in spec_drafting
// (scoping the spec path to request-originated issues by construction);
// daemon-internal mints (recognizer / triager / replan / followup) pass
// plan_waiting explicitly and are never routed regardless of the flag.
// False keeps every project on the legacy plan_waiting → plan_ready →
// planning direct path, byte-for-byte unchanged.
SpecsEnabled bool
// SpecVerifier runs the SMT verification pipeline (Phase A's
// internal/spec/verifier) over a parsed spec and returns the structured
// findings. The seam is injected so the lifecycle wiring is testable
// without a live Z3 subprocess; production wires verifier.Verify over a
// shared smt.Driver. nil (alongside a nil SpecDrafterDispatcher)
// disables the spec lifecycle.
SpecVerifier SpecVerifier
// SpecReviewerDispatcher runs one spec_reviewer coherence-judge task
// (TASK: spec_review) over a SMT-clean spec parked at spec_review and
// returns the typed coherence verdict. nil disables the coherence-check
// dispatch — sweepSpecReviewDispatch then never picks up a spec_review
// issue, leaving the operator `sorcerer spec approve` gate as the only path
// out of spec_review (the legacy behavior test daemons keep). When wired,
// applySpecReviewerResult drives the auto-approve / route-back lifecycle.
SpecReviewerDispatcher SpecReviewerDispatcher
// StewardDispatcher runs one steward task on the long-lived, resumable
// steward conversation (SOR-2659). Optional: nil leaves the steward
// dormant, mirroring the optional SpecDrafterDispatcher above — declared
// by this daemon-integration-scaffolding child but populated in production
// only by the go-live child (tests inject a stub). A nil dispatcher keeps
// the steward inert even once a sibling registers a sub-handler / sweep,
// because each production steward sweep self-gates on this field.
StewardDispatcher StewardDispatcher
// WedgeInvestigatorDispatcher dispatches the wedge_investigator role for a
// NOVEL wedge (SPEC-SOR-2820-v3 R4), mirroring StewardDispatcher. Optional:
// nil leaves the investigator dormant — the steward action executor self-gates
// on nil, so a steward dispatch_wedge_investigator action is a no-op until a
// dispatcher is wired (cmd/sorcererd/start.go constructs it next to
// StewardDispatcher).
WedgeInvestigatorDispatcher WedgeInvestigatorDispatcher
// StewardEscalationThreshold is the distinct-issue count N per wedge
// fingerprint within StewardEscalationWindowHours that warrants filing one
// dogfood structural-root issue (SPEC-SOR-2822-v3 R4). Zero resolves to 3
// inside applyEscalationPass (config.StewardEscalationThreshold's default);
// cmd/sorcererd/start.go wires it from cfg.StewardEscalationThreshold().
StewardEscalationThreshold int
// StewardEscalationWindowHours is the look-back window (in hours) over which
// the per-fingerprint distinct-issue escalation count is computed
// (SPEC-SOR-2822-v3 R4). Zero resolves to 72 inside applyEscalationPass
// (config.StewardEscalationWindowHours's default); wired from
// cfg.StewardEscalationWindowHours().
StewardEscalationWindowHours int
// StewardRecoveryAttemptCap is the per-(issue, fingerprint) recovery-attempt
// cap the Cap-3 re-apply suppression gate enforces (SPEC-SOR-2822-v3 R6): a
// recovery entry whose attempt count has reached this cap is never re-applied.
// Zero (or negative) resolves to 1 inside applyBlockedCohortRecoveries
// (config.StewardRecoveryAttemptCap's default — strict R8: a recovery applies
// once, after which the open entry suppresses further application);
// cmd/sorcererd/start.go wires it from cfg.StewardRecoveryAttemptCap().
StewardRecoveryAttemptCap int
// RequireOperatorApproval, when true, leaves a coherence-clean spec parked
// at spec_review for the operator `sorcerer spec approve` override. Default
// false: a clean spec_reviewer verdict auto-transitions spec_review →
// spec_approved (approved_by="auto"). Sourced from config specs
// .require_operator_approval.
RequireOperatorApproval bool
// PlannerInputs supplies the repos + team-key context that the
// supervisor injects into PlannerTask. Lifted onto Config because
// the planner needs project-wide knowledge that lives in config.yaml,
// not in any single Issue's state.
PlannerInputs PlannerInputs
// PRDiffSummarizer returns the per-file diff stat for one OPEN PR,
// shaped for inclusion in a planner task's ExistingIssue.DiffSummary.
// Called from dispatchPlanningIssue / dispatchPlanReviewer on the
// supervisor's main goroutine (same goroutine that calls
// prepareWorktrees) once per non-terminal issue carrying an open PR
// in latestPRSnapshots. nil disables the digest — ExistingIssue
// entries still carry PRURL but no DiffSummary lines.
PRDiffSummarizer func(ctx context.Context, prURL string) ([]role.DiffStatLine, error)
// ReviewerConcernPoster posts ONE GitHub PR review (event=COMMENT) at
// prURL with the supplied body + inline comments — a refer-back's
// concerns become a single operator-visible review on the PR where
// the implementer reads them (SOR-1157). The daemon owns this write;
// the reviewer LLM only emits the concerns, the daemon resolves the
// slug→URL chain and groups + formats them in postReviewerConcerns
// before invoking this hook. A non-nil error fires a
// pr_review_write_failed audit event but never blocks the verdict's
// state transition — the PR-side comment is best-effort. nil disables
// the write (test daemons that don't wire a GitHub surface).
// Production wiring uses (*github.Client).PostPRReview directly as a
// bound method value.
ReviewerConcernPoster func(ctx context.Context, prURL, body string, comments []gh.ReviewComment) error
// PRDiffHunkFetcher returns the per-file new-side `+S,L` hunk ranges
// for prURL — the set of (path, line) pairs that GitHub's review
// API will accept as inline comments (SOR-1161). Called by
// postReviewerConcerns before invoking ReviewerConcernPoster so a
// concern whose (file, line) is off-diff folds into the review body
// instead of triggering HTTP 422 on the entire review POST. A
// non-nil error fires a pr_review_diff_fetch_failed audit event and
// postReviewerConcerns falls back to the OLD behavior (every
// (file, line) concern is posted inline; the 422 may still fire, but
// the failure mode is no worse than today). nil disables the filter
// (test daemons; the audit event does not fire when the hook is
// absent). Production wiring uses (*github.Client).FetchPRDiffHunks.
PRDiffHunkFetcher func(ctx context.Context, prURL string) (map[string][]gh.DiffHunk, error)
// DedupeDispatcher gates POST /v1/issues operator-create requests
// against in-flight issues + open PRs. When nil, the dedupe gate is
// skipped — useful for test daemons that don't wire a clauderunner
// and don't exercise the gate. Production wiring sets this to
// role.NewDedupeDispatcher(clauderunner). The dispatcher's verdict
// is advisory when CreateInput.ForceDedupe is true; the audit event
// (dedupe_check_ran) fires either way.
DedupeDispatcher DedupeDispatcher
// MaxPlannerRevisions is retained for migration safety but unused.
// Per docs/architecture.md, the planner is single-shot: validation
// failure escalates rather than retries.
MaxPlannerRevisions int
// SubscriptionSnapshotter returns a snapshot of the OAuth-pool
// (clauderunner picker) state. Used by the status endpoint to surface
// per-token health (last-used, throttled-until, throttle count, auth-
// dead reason). nil disables; the status endpoint reports an empty
// list. Wired by start.go to cRunner.PickerSnapshot.
SubscriptionSnapshotter func() []SubscriptionSnapshot
// PoolThrottleState reports whether every OAuth token in the
// clauderunner pool is currently throttled, plus the soonest reset.
// RunSchedulerPass consults it before every role-claude-subprocess
// spawn (SOR-1159): when the whole pool is throttled, the spawn is
// doomed — the picker returns PoolExhaustedError before exec, the
// session closes with an error, and three such failures quarantine
// the role's conversation. The scheduler suspends dispatch and emits
// one dispatcher_throttle_paused audit event per cooldown window
// rather than spawning a subprocess that fails and storms the
// operator inbox with quarantine escalations. nil disables the gate
// (test daemons without a clauderunner). Wired by start.go to
// agentcli.Runner.ThrottleState.
//
// Deprecated: SOR-1296 superseded this surface with PoolDispatchState,
// which gates both the throttle case AND the auth-dead-pool case.
// RunSchedulerPass prefers PoolDispatchState when set; PoolThrottleState
// is consulted only when PoolDispatchState is nil. Test daemons that
// want to exercise just the throttle leg may still set this field;
// production wiring goes through PoolDispatchState.
PoolThrottleState func() (allThrottled bool, nextReset time.Time)
// PoolDispatchState is the SOR-1296 successor to PoolThrottleState.
// It reports the pool's spawn-readiness as two mutually-exclusive
// pause signals plus the soonest throttle reset:
// - throttleHeld: every selectable token is throttled (SOR-1159).
// nextReset is the soonest reset; emit dispatcher_throttle_paused.
// - authDeadHeld: every token is auth-dead — zero throttled, zero
// available. The remediation is operator action (clear the auth
// flag on the subscription); the wall-clock cooldown will never
// fire. Emit dispatcher_auth_dead_paused.
// Either pause condition suspends ALL dispatch (ready-leaf loop AND
// dispatchFollowOns) so no doomed role-claude-subprocess spawn is
// issued. Both pause cases dedup against Project.LastThrottlePauseEventAt
// / LastAuthDeadPauseEventAt so a single multi-hour episode cannot
// re-emit. nil disables the gate (test daemons without a clauderunner).
// Wired by start.go to agentcli.Runner.PoolDispatchState.
PoolDispatchState func() (throttleHeld, authDeadHeld bool, nextReset time.Time)
// WatchdogSnapshotter returns the live claude subprocesses the
// stuck-bash watchdog should walk on each tick (SOR-37). Wired by
// start.go to agentcli.Runner.LiveSubprocesses. nil leaves the
// watchdog goroutine disabled regardless of WatchdogEnabled — a
// missing snapshotter at construction time is a misconfiguration
// bug, not a fallback case.
WatchdogSnapshotter func() []watchdog.Subprocess
// LiveSessionMetricsFn returns live per-session metering snapshots for the
// per-issue progress assembly in StatusSnapshot and ProgressByIssue
// (SOR-2859). Wired by start.go to a closure over
// agentcli.Runner.LiveSubprocesses() — the same neutral-DTO bridge
// pattern as WatchdogSnapshotter, so the daemon package takes no
// clauderunner import. nil disables live metering: a test daemon that
// constructs no clauderunner serves progress without it, and StatusSnapshot
// omits the issue_progress block entirely.
LiveSessionMetricsFn func() []LiveSessionMetrics
// WatchdogEnabled controls whether the stuck-bash watchdog
// goroutine starts. Defaults to false (the daemon will not start
// the goroutine); start.go passes config.WatchdogEnabled() through.
WatchdogEnabled bool
// WatchdogInterval is the watchdog's tick cadence. Must be > 0
// when WatchdogEnabled is true.
WatchdogInterval time.Duration
// WatchdogMinStuck is the wall-clock-elapsed window below which
// the detector never trips. Must be > 0 when WatchdogEnabled is
// true.
WatchdogMinStuck time.Duration
// SessionReaper SIGTERMs (graceful=true) or SIGKILLs
// (graceful=false) the subprocess tree of the session whose
// tracked id == sessionID, via the SOR-195 PPID-descendant reaper.
// Returns true when a live subprocess matched. Wired by start.go to
// agentcli.Runner.ReapSession so the daemon package stays free
// of clauderunner-internal coupling (same closure pattern as the
// watchdog snapshotters). nil → the session-abort / age-reaper
// paths skip the kill (test-daemon path; the SM transition still
// runs so scheduler accounting converges).
SessionReaper func(sessionID string, graceful bool, reason string) bool
// GitHubTokenStatus returns the current installation token's expiry
// and owner. Used by the status endpoint. nil disables.
GitHubTokenStatus func() (owner string, expiresAt time.Time)
// GitHubBotLogin returns the canonical bot login (e.g.
// `sorcerer-b3k[bot]`) resolved at boot via the SOR-220
// `github.bot_login` → JWT-signed /app chain. Surfaced under
// the `github:` block in /v1/status. nil omits the field
// (legacy tests without the wire).
GitHubBotLogin func() string
// ConversationRecycler, if set, is asked to close (drop and
// reconstruct) a role's conversation state when its session-
// failure streak hits QuarantineThreshold. Production wiring (v1
// shape via clauderunner) currently uses no recycler — sessions
// are short-lived per-call so there's nothing to recycle.
ConversationRecycler ConversationRecycler
// Issuestore is the local SQLite mirror handle. When non-nil, the
// daemon mirrors every in-process *sm.Issue mutation into the store
// (state transitions, depends_on / pr_urls / materialized_children
// updates, freshly-minted Planning Issues) inside the same
// applyTransition / event-handler scope. Linear remains the source of
// truth for reads in this PLAN; this is observation-only. When nil,
// the daemon runs as before — back-compat for tests that don't wire
// a store.
//
// Typed as the issuestore.IssueStore interface (not the concrete
// *issuestore.Store) so the fault-injection harness in phase 4b can
// substitute a wrapper. Production wiring (the real daemon boot path)
// still passes the real *issuestore.Store — it satisfies the interface
// by construction.
Issuestore issuestore.IssueStore
// SweepCooldown is the minimum wall-clock window between successive
// reviewer dispatches for the SAME `blocked_user` issue whose PR
// HEADs haven't moved. Reads config.Recovery.SweepCooldownSec.
// Zero disables the cooldown entirely (legacy behavior); negative
// values are rejected at config-load time. Defaults to 30 minutes
// in production.
SweepCooldown time.Duration
// VerifierChainStaleMaxAge is the max-age past which a still-in-flight
// post-submit verifier chain is classified stale by verifierChainStale.
// Reads config.Recovery.VerifierChainStaleMaxAgeSec via the config
// resolver (cfg.VerifierChainStaleMaxAge()), so it is always a positive,
// config-sourced value (30m default) in production — never hard-coded at
// the classifier. A zero value (e.g. an un-wired test daemon) is not used
// by any production consumer in this build; tests that exercise
// verifierChainStale set it explicitly.
VerifierChainStaleMaxAge time.Duration
// CommitVerifier verifies that a commit SHA is reachable on the
// origin/main branch of one of the project's configured repos.
// Used by the triager Executor before applying any
// evidence_commit_sha-bearing action (unblock_via_dep_drop,
// force_converge_to_terminal). The wrapper helper iterates over
// d.cfg.PlannerInputs.Repos in input order; the first repo that
// returns found=true wins. Each per-repo call is scoped to a 10s
// context timeout. Production wiring shells out to
// `gh api repos/<repo>/commits/<sha>`; tests inject a fake.
// nil leaves SHA-citing executors in a permanent-reject state
// (they return ErrSHANotFound / equivalent), so a misconfigured
// daemon fails closed rather than silently treating every SHA as
// verified.
CommitVerifier CommitVerifier
// PRResolver resolves a PR URL into its repo/state/branch/title/body
// for the SOR-220 `sorcerer issue adopt-pr` workflow. Production
// wires this to a closure over gh.Client.ViewPR; tests pass a fake.
// nil leaves AdoptPR mutations in a permanent-reject state — the
// daemon refuses the operator call rather than guessing the PR
// shape.
PRResolver PRResolver
// MaxTriagerActionsPerTick caps total throughput of the triager
// router per tick. The first N actions route per their normal
// confidence rule; subsequent actions all land as inbox proposals
// (the operator-approval path) regardless of declared confidence.
// Defaults to 5 in New when zero; reads config.Triager.MaxActionsPerTick
// in production wiring.
MaxTriagerActionsPerTick int
// ReplanMaxAttempts is the SOR-144 replan circuit-breaker's per-
// issue attempt cap. When the discovered-prereq replan path would
// dispatch the (ReplanMaxAttempts + 1)-th attempt for an issue
// within ReplanWindow without forward progress, the supervisor
// halts the issue at `blocked_user` / `plan_blocked` with a
// `replan_loop_halted` event + escalation row instead of firing
// another replan. Defaults to 3 in New when zero; reads
// config.Replan.MaxAttempts in production wiring.
ReplanMaxAttempts int
// ReplanWindow is the wall-clock window over which the circuit-
// breaker accumulates attempts before a reset. Defaults to 30
// minutes in New when zero; reads config.ReplanWindow() in
// production wiring.
ReplanWindow time.Duration
// NarrationRetryBudget is the SOR-227 per-issue retry cap for
// narration-class session failures (failure_class ∈
// {narration_only, deadline_expired_first_activity}). The
// supervisor's Layer-2 branch increments
// `iss.NarrationFailureCount` on every such failure and re-dispatches
// the cycle with a retry preamble; on the Nth consecutive failure
// (where N == NarrationRetryBudget) the issue escalates to
// blocked_user / plan_blocked with reason
// `narration_failure_budget_exhausted (count=N)`. Defaults to 3 in
// New when zero. A single global default is enough for the observed
// pattern; per-role budgets are out of scope (per the issue body's
// "out of scope" list).
NarrationRetryBudget int
// ThrottleRetryCap is the SOR-1316 per-issue cap on consecutive
// session_transient_retry events classified as OAuth-pool-exhausted
// (PoolExhaustedError or the "every OAuth token is throttled"
// stringified equivalent). When the supervisor's circuit-breaker
// branch observes the Nth consecutive throttle retry on a single
// issue (where N == ThrottleRetryCap), the dispatcher stops re-
// spawning that session and routes the issue to blocked_user with
// reason `oauth_pool_exhausted_retry_cap_reached`. Defaults to
// throttleRetryCapDefault (20) in New when zero — matches the
// SOR-1080 / SOR-1081 production wedges that burned ~6,000
// dispatches per hour against a single throttled issue before
// SOR-1316 capped them. Composes with SOR-1241 (the quarantine
// streak still excludes transient throttle failures) and with
// SOR-1281 (the structured rate_limit_event classifier is the
// preferred shape signal; the stringified pool-exhausted text is
// the defense-in-depth fallback).
ThrottleRetryCap int
// SameStateCycleThreshold is the per-issue cap on consecutive
// returns to a cycle-prone state (waiting / ready / feedback)
// with the SAME normalized blocking reason before the
// deterministic same-state-cycle detector force-routes the issue
// to blocked_user. The detector lives inside applyIssueTransition
// (the single supervisor entry for in-band issue SM transitions);
// it increments the per-issue SameStateCycleCount on every return
// to the recorded SameStateCycleState with a matching normalized
// reason, and fires when the post-increment count crosses this
// threshold. The motivating wedge: SOR-1202 on archers cycled
// `ready → waiting` 157 times over ~78 min before the
// recognizer's hourly reflective tick noticed (22,294 lint
// findings emitted). A deterministic per-transition detector is
// instant, free, and operator-surfacing — orthogonal to the
// LLM-driven recognizer's "why is it wedging" classification
// path. Defaults to sameStateCycleThresholdDefault (3) in New
// when zero. Lowering to 1 turns this into "fail fast on the
// first re-cycle"; raising tolerates more cycles before blocking.
SameStateCycleThreshold int
// AmenderExhaustionThreshold is the SOR-1447 per-issue cap on
// consecutive scoped_amender `no_amendments` cycles on the same
// plan-defect signature before the supervisor force-routes the
// issue from `implementing` to `blocked_user` (with reason prefix
// `amender_exhausted_on_falsified_premise`) instead of the
// existing IssueStateWaiting rollback. The counter +
// last-signature live on the issue row
// (ConsecutiveNoAmendments + LastPlanDefectSignature, schema
// migration `issues_amender_exhaustion`); the supervisor's
// applyScopedACAmendResult.no_amendments branch is the sole
// increment site. Defaults to amenderExhaustionThresholdDefault
// (2) in New when zero — the strongest signal we have for an
// un-fixable defect, since the scoped_amender explicitly returned
// NoAmendments after reading the body + the implementer's
// plan_defect report. Routing at N=2 saves one full implementer
// dispatch (~10-20 min of token spend) versus N=3 and routes the
// wider-context triager onto the autonomous closure paths.
AmenderExhaustionThreshold int
// OversizedDispatchEscalateCeiling is the per-project
// evidence-derived dispatch-escalate ceiling in predicted cache
// tokens. A predicted est_cache_tokens value at or above this
// ceiling triggers the oversized sanity backstop rather than an
// unconditional dispatch hold. Consumed by the dispatch and
// proposal gate in the dependent recalibration issue. Production
// wires cfg.OversizedDispatchEscalateCeiling(); the field is NOT
// patched in New — the config-layer default is always applied
// before cmdStart passes the value in.
OversizedDispatchEscalateCeiling int64
// HandlerSlowWarnThreshold and HandlerSlowErrorThreshold are the
// SOR-1347 main-loop slow-handler audit thresholds. The dispatcher
// (handle()) records every handler's duration and emits an
// `operator_mainloop_slow_handler` audit event at severity=warn /
// severity=error when the duration crosses the warn / error tier.
// Defaults applied in New when zero (6s warn, 10s error); the
// production wiring routes config.Daemon.SlowHandler values through
// the corresponding cfg accessors. See ack_timeout.go's threshold
// comment for the operational rationale.
HandlerSlowWarnThreshold time.Duration
HandlerSlowErrorThreshold time.Duration
// HandlerKindBudgets is the SOR-2447 per-handler-kind main-loop
// occupancy-budget table, keyed on the strings handlerKindAndSubject
// produces. An absent (or non-positive) key falls back to
// HandlerSlowWarnThreshold, so every dispatched handler kind resolves
// to a budget (R6). Production wires cfg.SlowHandlerBudgets(); a nil
// map is fine (every kind uses the warn-threshold default).
HandlerKindBudgets map[string]time.Duration
// HandlerSustainedOverrunCount is the number of over-budget firings for
// the same handler kind within HandlerSustainedOverrunWindow that trips
// an operator_mainloop_sustained_overrun detection event (R7). Defaults
// to 3 in New when non-positive.
HandlerSustainedOverrunCount int
// HandlerSustainedOverrunWindow is the wall-clock window for the
// sustained-overrun counter. Defaults to 60s in New when non-positive.
HandlerSustainedOverrunWindow time.Duration
// WedgeDetectorEnabled gates the SOR-1356 same-state-cycle detector.
// When false, the detector is a no-op on every scheduler-pass tick;
// no audit events, no transitions. Defaults to true in New when the
// caller leaves the field unset (the zero value is false, so the
// daemon defaults to enabled only when the test/production wire
// passes true). Production wires the cfg.WedgeDetectorEnabled()
// accessor; tests typically leave it false to skip the detector.
WedgeDetectorEnabled bool
// WedgeDetectorWindow is the sliding-window length the detector
// applies when counting state-pair re-entries. Defaults to 30
// minutes in New when zero.
WedgeDetectorWindow time.Duration
// WedgeDetectorCycleThreshold is the per-(issue, state-pair) cycle
// count that trips the detector. Defaults to 10 in New when zero.
WedgeDetectorCycleThreshold int
// WedgeDetectorLowSignalEventKinds is the noise filter the detector
// applies when extracting the dominant per-issue audit-event
// signature from the events table (AC2). The set is shared with
// the recognizer's trend tables so the two surfaces have a single
// noise definition; production wires cfg.Recognizer.LowSignalEventKinds.
WedgeDetectorLowSignalEventKinds []string
// InvariantsCheckEnabled gates the periodic invariants_check recovery
// sweep kind. When false, the runInvariantsCheck handler short-circuits
// before any snapshot is built — no events, no work. Defaults to true
// in New when the caller leaves it unset (the cfg.InvariantsCheck.Enabled
// nil sentinel maps to true via config.InvariantsCheckEnabled()).
InvariantsCheckEnabled bool
// InvariantsCheckTickInterval is the per-N-ticks safety-net cadence
// the invariants_check handler honors. The recover-sweep ticker fires
// the handler every tick, but the handler only does the actual check
// work every Nth call. Defaults to 5 in New when zero (the cadence
// proposed in docs/structural-coverage/phase-1-invariant-manifest.md
// § 4.7).
InvariantsCheckTickInterval int
// DaemonName is this daemon's registered name (the same value
// start.go announces to sorcerer-ui). Used as Topology.Self so
// callers can compare an OwnerOf result against this daemon
// (SOR-1191). Empty leaves the topology cache's Self field empty.
DaemonName string
// UIEndpoint is the sorcerer-ui base URL the topology cache polls
// (GET <UIEndpoint>/api/daemons) every 60s (SOR-1191). Empty
// disables the topology cache entirely — no goroutine, no cache.
UIEndpoint string
// APIToken is this daemon's HTTP control-plane bearer (the same
// value passed as httpapi.Config.Token). The cross-daemon forwarder
// sends it as the Authorization bearer on forwarded POST /v1/issues
// requests so the receiving peer's dual-auth path (SOR-1214) can
// hash-match it against the topology. Empty leaves the forwarder
// unauthenticated — fine for a daemon with no peers, but a forward
// to an auth-enforcing peer would 401.
APIToken string
}
Config is what Daemon needs to start. All fields are optional unless noted.
type ConversationRecycler ¶
type ConversationRecycler interface {
CloseConversation(role string)
}
ConversationRecycler is implemented by the agent.Manager (via its CloseConversation method, added in this commit). The supervisor holds a non-typed pointer to keep daemon free of agent imports.
type CrashRecoveryPRDiscoveryResultEvent ¶
type CrashRecoveryPRDiscoveryResultEvent struct {
IssueKey string
Branch string
Snaps map[string]DiscoveredPR
DiscoveryFailed bool
Transient bool
SessionErrMsg string
At time.Time
}
CrashRecoveryPRDiscoveryResultEvent is the EventCrashRecoveryPRDiscoveryResult payload. The off-main worker (spawnOrSkipCrashRecoveryPRDiscovery) snapshots the issue's branch + repos and the on-main-computed transient classification, runs the PRSetDiscoverer probe on a session goroutine, and submits this struct for applyCrashRecoveryPRDiscoveryResult to apply on the main loop. The worker writes neither d.state nor the issuestore — it only Submit()s this result, which carries only plain values (no live issuestore handle, no *Daemon reference, no error interface) so the off-main/on-main boundary stays clean.
Transient is the result of isTransientSessionError(sessionErr) computed ON the main loop: the classifier uses errors.As over typed error values, which cannot be marshaled across the plain-value boundary, so the routing decision is made on-main and carried as a plain bool — making the result handler's transient vs permanent decision identical to the inline computation (R5). SessionErrMsg is sessionErr.Error() captured on-main for the comment body + escalation. Snaps is the discoverer's PR-set map, non-nil only when DiscoveryFailed is false; At is the on-main-captured timestamp threaded through the transition so the async path stamps its rows with the same `now` the inline path would have.
type CreateMutationResult ¶
CreateMutationResult is the payload sent back on Mutation.CreateReply. Key is empty on error; otherwise it holds the newly-assigned Linear identifier (e.g. "SOR-N").
type CrossDaemonClient ¶
type CrossDaemonClient struct {
// contains filtered or unexported fields
}
CrossDaemonClient POSTs operator-create requests to peer daemons on behalf of the SOR-1184 routing resolver's `forward` decision.
func NewCrossDaemonClient ¶
func NewCrossDaemonClient(httpClient *http.Client, selfBearer string, selfName string) *CrossDaemonClient
NewCrossDaemonClient builds a CrossDaemonClient. httpClient may be nil — a default client with the per-attempt timeout is substituted. selfBearer is this daemon's HTTP API bearer (sent as Authorization so the peer's dual-auth path can hash-match it against the topology); selfName is this daemon's registered name (sent as X-Sorcerer-Forwarded-From).
func (*CrossDaemonClient) ForwardCreate ¶
func (c *CrossDaemonClient) ForwardCreate(ctx context.Context, targetURL string, body []byte, auditID int64) (destKey string, destDaemon string, err error)
ForwardCreate POSTs body to <targetURL>/v1/issues as a cross-daemon forward. The request carries Content-Type: application/json, the source daemon's bearer, X-Sorcerer-Forwarded-From, and X-Sorcerer-Route-Audit-ID (decimal). Each attempt is bounded by forwardAttemptTimeout.
On HTTP 201 the peer-assigned `{"key":...,"daemon":...}` envelope is parsed and (key, daemon, nil) returned — daemon may be empty if the peer's create response omits it (a plain local mint returns just `{"key":...}`); the caller falls back to the resolver's target name. Every other outcome — 401, any other 4xx/5xx, a 202 (a downstream peer's own queue, out of scope here), a malformed 201 body, or a transport failure — returns a typed *ForwardError.
func (*CrossDaemonClient) ForwardMove ¶
func (c *CrossDaemonClient) ForwardMove(ctx context.Context, targetURL string, body []byte, auditID int64, sourceKey string) (destKey string, destDaemon string, err error)
ForwardMove POSTs body to <targetURL>/v1/issues as the destination half of a cross-daemon operator-move (SOR-1278). Identical wire shape to ForwardCreate plus the additional X-Sorcerer-Source-Key header carrying sourceKey; the peer's POST /v1/issues handler reads it into IssueCreateInput.SourceKey and, after the local mint completes, emits the route_operator_move_received audit row that closes the bidirectional move audit chain. The auditID continues to ride the existing X-Sorcerer-Route-Audit-ID header so the SOR-1216 inbound idempotence path dedups retries on the (source_daemon, audit_id) pair without a parallel channel.
type Daemon ¶
type Daemon struct {
// contains filtered or unexported fields
}
func (*Daemon) AbortActiveRoleSessions ¶
AbortActiveRoleSessions satisfies cmdadapter.RoleSessionAborter so the cmdadapter SetRolePause path can dispatch operator-pause-driven session aborts through the daemon's main-loop chokepoint without importing the daemon package directly (SOR-1364). A thin alias for ApplyOperatorAbortRoleSessions kept so callers reading the cmdadapter seam see the same verb (`abort`) the role-pause adapter uses.
func (*Daemon) AddPoller ¶
AddPoller registers a poller after construction. The poller is started when Run is called; if Run is already running, the poller is started immediately. Must be called from the main goroutine or before Run.
func (*Daemon) AmendBodySizingWarning ¶
AmendBodySizingWarning returns the operator-facing body-sizing warning for newDescription (the NEW, post-amend body), or "" when the body lands in the "safe" risk bucket or forceLargeScope is true. It applies the same wiring the create path uses (issuebody.Parse via bodySizingWarning) to the amend path; the operator CLI surface composes it with a successful ApplyOperatorAmend so the same decomposition nudge create surfaces also fires when an amend grows a body past the safe ceiling.
It is kept separate from ApplyOperatorAmend (the SM-mutation chokepoint, whose return stays a bare error) because the warning is a pure, advisory function of the body — orthogonal to the mutation. The issue_body_features telemetry row is emitted by applyOperatorAmend regardless of forceLargeScope; this method governs only the warning text.
func (*Daemon) AppendEscalation ¶
func (d *Daemon) AppendEscalation(esc store.Escalation)
AppendEscalation inserts one row into the issuestore's escalations table. Public so handlers in other packages can record operator- action items. The Attempted role string is folded into the head of NeedsFromUser as a `[role=<X>] ` prefix so Linear-comment consumers keep the role visible without a dedicated column. PRURLs is JSON- encoded for the schema's `pr_urls` column ("raw JSON map repo→url; caller decodes"). Vestigial WizardID / Mode / Fields are dropped.
func (*Daemon) ApplyOperatorAbortRoleSessions ¶
func (d *Daemon) ApplyOperatorAbortRoleSessions(ctx context.Context, role, reason string) (int, error)
ApplyOperatorAbortRoleSessions dispatches a MutationAbortRoleSessions onto the daemon's main goroutine and returns the number of in-flight sessions the handler walked to aborted (SOR-1364). Submits a typed EventOperatorIssueMutation onto the daemon's event channel; uses the two-phase ack-then-reply pattern so a wedged main goroutine surfaces as `ack timeout` rather than silently dropping the abort.
Returns:
- (count, nil) on success — count may be zero when no session of the named role was in starting / running at dispatch time.
- (0, ctx.Err()) when the caller's ctx is canceled.
- (0, "ack timeout"-sentinel) when the main loop is wedged.
- (0, "handler timeout") when the dispatcher acked but the handler exceeded operatorMutationHandlerTimeout.
Called from the cmdadapter SetRolePause path AFTER the pause row + audit-event have been committed; the row already gates future dispatches and this call gates the in-flight ones.
func (*Daemon) ApplyOperatorActivationOverride ¶
func (d *Daemon) ApplyOperatorActivationOverride(ctx context.Context, key, rid, reason string) error
ApplyOperatorActivationOverride marks a named activation probe satisfied on a plan in plan_activating with the operator's mandatory reason (SOR-2504, spec R6 sole exception). Submits MutationActivationOverride onto the event channel and blocks for the main loop's reply. See applyOperatorActivationOverride.
func (*Daemon) ApplyOperatorAddBlocker ¶
ApplyOperatorAddBlocker declares "blockerKey blocks blockedKey" — the depender (blockedKey) gains blockerKey in its DependsOn slice and a matching Linear `blocks` relation is pushed asynchronously. Submits a typed EventOperatorIssueMutation onto the daemon's event channel and waits up to ~5s for the main loop to ack.
Returns:
- nil on success (including idempotent no-op when the edge is already present).
- httpapi.ErrNotFound wrapping a message naming whichever key is absent from d.state.Issues.
- an error wrapping httpapi.ErrCycle when the new edge would close a cycle in the dep graph (handler maps to 409).
- other errors propagated from the issuestore commit for the handler to surface as 500.
- "ack timeout" when the main loop is wedged.
The Linear-side mirror is fired-and-forgotten on the main loop's behalf inside the handler so the operator HTTP call doesn't block on Linear's GraphQL round-trip; a mirror failure logs and emits `linear_relation_mutate_failed` but does NOT roll back the local mutation (every other Linear mirror in the daemon follows the same pattern).
func (*Daemon) ApplyOperatorAddPlanChildren ¶
func (d *Daemon) ApplyOperatorAddPlanChildren(ctx context.Context, in httpapi.PlanAddChildrenInput) ([]string, []string, error)
ApplyOperatorAddPlanChildren is the operator + assembly_fixer entry point for POST /v1/plan/{key}/add-children (SOR-1448 / SOR-1443 Fix 4). Adds N new implementation children to the named planning issue's MaterializedChildren, dispatches each through the standard implementer + reviewer cycle, and transitions the plan to plan_auto_fixing if it is not already there.
The primitive is ADDITIVE: existing parked children stay parked. Adding fix-up children does NOT trigger a cancel-cascade.
Atomicity: the daemon's main loop applies each child mint inside applyOperatorAddPlanChildren under a single d.stateMu critical section. A per-child mint failure rolls back every other child the same call minted (best-effort delete on the in-memory rows + the issuestore rows), so the operator's "add 3 children" gesture either succeeds in full or fails with no state change.
Returns:
- (added_keys, warnings, nil) on success. added_keys is parallel to in.Children; warnings carries non-fatal operator-facing notes.
- ("", "", err wrapping httpapi.ErrBadRequest) on validation failure (unknown plan, plan state outside the allowed set, unknown repo on a child, etc.). The handler maps to 400.
- ("", "", err wrapping httpapi.ErrNotFound) when the planning issue is unknown. The handler maps to 404.
- ("", "", err) on local failures (issuestore commit, missing project row, etc.). The handler maps to 500.
- ("", "", "ack timeout") when the main loop is wedged.
func (*Daemon) ApplyOperatorAdoptPR ¶
func (d *Daemon) ApplyOperatorAdoptPR(ctx context.Context, in httpapi.IssueAdoptPRInput) (string, error)
ApplyOperatorAdoptPR files a fresh implementation Issue around an existing out-of-band PR (SOR-220 Layer 4). Submits a typed EventOperatorIssueMutation onto the daemon's event channel and waits up to ~5s for the main loop to ack with the assigned key.
Pre-mutation gates (resolve + validate) run on the calling goroutine because they take a network round-trip; the mutation itself (the local-mint + issuestore commit + state-map update) runs on the main goroutine via the existing operator-mutation channel.
Returns:
- (key, nil) on success.
- ("", err wrapping httpapi.ErrBadRequest) on validation failure (PR not open, repo not in allowlist, malformed URL).
- ("", err wrapping httpapi.ErrLinearAPI) on GitHub-side resolve failure (the HTTP handler maps this to 502).
- ("", err) for local failures (issuestore commit, etc.).
- ("", "ack timeout") when the main loop is wedged.
func (*Daemon) ApplyOperatorAmend ¶
ApplyOperatorAmend updates an issue's description in place and writes an audited, recoverable issue_amendments row capturing the pre-amend body in response to an operator-driven request (HTTP POST /v1/issues/{key}/amend or `sorcerer issue amend`). Submits a typed EventOperatorIssueMutation onto the daemon's event channel and waits up to ~5s for the main loop to ack.
Amend touches description only — issue state, state_kind, and every row in the dependencies table are left untouched. The audit row captures actor, reason, timestamp, and the full pre-amend body so prior versions are reconstructible without an out-of-band export.
Returns:
- nil on success.
- httpapi.ErrNotFound when key is absent from d.state.Issues.
- an error wrapping httpapi.ErrInvalidTransition when the issue is in a terminal state (handler maps to 409). Operators who genuinely need to edit a terminal issue's body must use the existing terminal-state escape (ForceUnTerminate) first.
- other errors propagated from the issuestore for the handler to surface as 500.
- "ack timeout" when the main loop is wedged.
func (*Daemon) ApplyOperatorCancel ¶
func (d *Daemon) ApplyOperatorCancel(ctx context.Context, key, reason string, cascadePlanAbandon bool) error
ApplyOperatorCancel transitions the issue at key to its terminal- cancel state (sm.IssueStateAbandoned for implementation issues, sm.PlanStateAbandoned for planning issues), runs the existing supervisor cleanup path (worktree teardown via cleanupWorktrees, optional WIP preservation via daemon.Config.WIPPreserver, Linear push to canceled), and inserts an audit history row plus an optional derived comment row carrying the cancel reason. The issue-row mirror, the history row, and the optional comment row commit in one issuestore transaction; the non-DB side-effects run AFTER the tx.
Operator-initiated cancel is intentional human action — the helper does NOT file an escalation. WIP preservation is best-effort: a missing WIPPreserver or a failed git push does not block the cancel.
SOR-1248: when the target is a `plan_branch` implementation child whose parent Planning Issue is in a non-terminal plan state, canceling the child cascade-abandons the parent and orphans every sibling (with any already-squashed sibling commits lost on plan- branch teardown). The helper refuses the cancel with *httpapi.CascadeImpactError unless cascadePlanAbandon is true.
Returns:
- nil on success.
- httpapi.ErrNotFound when key is absent from d.state.Issues.
- an error wrapping httpapi.ErrInvalidTransition when the SM refuses the move (handler maps to 409). The IssueState table forbids abandoned from merged; the PlanState table forbids it from plan_review / plan_completed / plan_abandoned.
- *httpapi.CascadeImpactError when the cancel would cascade-abandon a non-terminal parent plan and cascadePlanAbandon is false.
- "ack timeout" when the main loop is wedged.
func (*Daemon) ApplyOperatorComment ¶
ApplyOperatorComment appends one operator-authored comment to the issue at key. Submits a typed EventOperatorIssueMutation onto the daemon's event channel (kind MutationComment) and waits up to ~5s for the main loop to ack with the assigned (id, ts) pair.
Returns:
- (id, ts, nil) on success — id is the SQLite-assigned row id, ts is the unix-second timestamp the daemon stamped.
- (0, 0, httpapi.ErrNotFound) when key is absent from d.state.Issues.
- (0, 0, err) for issuestore failures (handler maps to 500).
- (0, 0, "ack timeout") when the main loop is wedged.
The Linear push is fire-and-forget per the existing LinearCommenter contract — failures log + emit a linear_comment_failed event but do NOT roll back the local insert. Operator comments INTENTIONALLY skip the postLinearProgress dedup short-circuit (operators legitimately repeat themselves).
func (*Daemon) ApplyOperatorCreateIssue ¶
func (d *Daemon) ApplyOperatorCreateIssue(ctx context.Context, in httpapi.IssueCreateInput) (string, string, bool, error)
ApplyOperatorCreateIssue files one fresh issue in response to an operator-driven request (HTTP POST /v1/issues or `sorcererd issue create`). Submits a typed EventOperatorIssueMutation onto the daemon's event channel and waits up to ~5s for the main loop to ack with the assigned key.
The third return value, specDriven, reports whether the create was routed to the spec-driven lifecycle (spec_drafting) — true exactly when createroute.Decide returns RouteSpecDrafting for this request's inputs (SPEC-SOR-2828-v1 R7). It is false on every error path and on the cross-daemon forward path (the destination daemon owns its own routing).
Pre-create dedupe gate (SOR-57): when DedupeDispatcher is wired, runs the LLM-backed gate BEFORE the mutation. The gate:
- Captures a race-safe snapshot of d.state.Issues (filtered to non-terminal states) and d.latestPRSnapshots (filtered to OPEN) via a MutationDedupeSnapshot round-trip on the main goroutine.
- Truncates the digest from the oldest end if it exceeds the 30k-token cap, emitting `dedupe_payload_truncated`.
- Dispatches one single-shot dedupe Opus session with the prospective body + the snapshot.
- Always emits `dedupe_check_ran` (including override paths) so the audit trail is uniform.
- On positive match without `force_dedupe`, returns a *DuplicateError that the HTTP handler maps to 409. With `force_dedupe=true`, the verdict is advisory: the gate runs, the audit fires, and the create proceeds.
Post-SOR-1006 (dual-read cutover): CLI-created issues are LOCAL-ONLY. The daemon mints the key from project.last_issue_seq + 1 inside the same issuestore transaction as the issue/history rows; no Linear-side issueCreate is invoked and the new row carries an empty LinearID. A separate revert procedure (docs/operations.md) covers backfilling a CLI-created issue into Linear by hand when an operator wants to roll the migration back.
Returns:
- (key, warning, nil) on success — key is the locally-minted SOR-N and warning is the operator-facing override message when the SOR-1218 routing-conflict detector overrode the prior repos-field decision (otherwise empty).
- ("", "", *DuplicateError) on a dedupe-gate refusal. The HTTP handler maps this to 409 Conflict.
- ("", "", err wrapping httpapi.ErrBadRequest) on a daemon-side validation failure (repo not in cfg.Repos, unknown depends_on key, unsupported kind). The handler maps this to 400.
- ("", "", err) for local failures (issuestore commit, missing project row, etc.). The handler maps these to 500.
- ("", "", "ack timeout") when the main loop is wedged.
func (*Daemon) ApplyOperatorListActivating ¶
func (d *Daemon) ApplyOperatorListActivating(ctx context.Context) ([]httpapi.ActivationPlanView, error)
ApplyOperatorListActivating returns every plan currently in plan_activating with each owned activation criterion's probe status + evidence pointer (SOR-2504). A per-request sync read: it snapshots the candidate set on the main goroutine via MutationActivationList, then assembles the views off-main from each plan's spec + verdict store — no time-based cache.
func (*Daemon) ApplyOperatorRefactorPlan ¶
func (d *Daemon) ApplyOperatorRefactorPlan(ctx context.Context, in httpapi.PlanRefactorInput) (httpapi.PlanRefactorResult, error)
ApplyOperatorRefactorPlan is the operator entry point for POST /v1/plan/{key}/refactor — the operator-driven replan escape hatch. It collapses four manual operations into one main-goroutine- serialized cascade:
- Snapshot the planning issue's current children + emit a typed plan_refactor_dispatched audit event recording the operator's reason, the new body byte-count, and the current children keys.
- Cascade-cancel every non-merged child through the existing per-issue cancel core (the same path `sorcerer issue cancel` uses; the work-preservation guard's operator-cancel carve-out applies per child).
- Amend the planning body to the new content via the existing amend path, which records the prior body in the recoverable history.
- Force-transition the planning issue's plan-state to plan_revising so the planner re-dispatches.
Atomicity by construction: steps 2–4 (every child cancel, the body amend, the plan_revising transition, and the detach of the plan's MaterializedChildren) commit inside ONE issuestore transaction. The transaction either commits — every listed key is canceled, the body is amended, and the plan is in plan_revising — or it rolls back, leaving every issue in its pre-call state and the body unchanged. No partial state is reachable regardless of the underlying lock-contention shape, so the operator return value is honest by construction: the keys in result.CanceledChildren were all canceled, or the call returned an error and nothing changed. On the main goroutine the call is also single-writer serialized, satisfying single-writer-state.
Converge-from-any-state: the handler does NOT require a specific starting plan-state. It accepts plan_awaiting_children, plan_review, or plan_revising (the operator-driven replan states) and converges to (plan_revising, new body, zero in-flight children). The plan_revising → plan_revising self-loop on a re-run is force-applied and harmless; with the children already detached + abandoned a re-run cancels nothing.
Returns:
- (result, nil) on success. result.CanceledChildren lists the keys this call canceled (parallel to the order they appeared in the plan's children); result.NewState is "plan_revising".
- (zero, err wrapping httpapi.ErrNotFound) when the planning issue is unknown. The handler maps to 404.
- (zero, err wrapping httpapi.ErrBadRequest) on validation failure (empty key/body/reason, target is not a planning issue). The handler maps to 400.
- (zero, *ErrRefactorPlanAborted) when the cascade transaction rolls back (e.g. SQLITE_BUSY on a child's persist). The inner error names the failing child; the handler maps to 500 and the operator re-runs. No issue changed state and the body was not amended.
- (zero, "ack timeout") when the main loop is wedged.
func (*Daemon) ApplyOperatorReferBack ¶
func (d *Daemon) ApplyOperatorReferBack(ctx context.Context, key, reason string, concerns []role.ReviewConcern) error
ApplyOperatorReferBack injects operator-supplied refer-back concerns onto an in_review / merging issue and walks it to feedback. The next implementer feedback cycle's task envelope pipes the supplied concerns into REVIEW_FEEDBACK so the operator's intent is delivered to the implementer prompt without the "amend the issue description with a synthetic AC" workaround.
Returns:
- nil on success.
- httpapi.ErrNotFound when key is absent from d.state.Issues.
- an error wrapping httpapi.ErrInvalidTransition when the issue is not in in_review or merging (handler maps to 409).
- "ack timeout" when the main loop is wedged.
func (*Daemon) ApplyOperatorRekey ¶
ApplyOperatorRekey renames the issue at oldKey to a fresh canonical `<teamPrefix>-<N>` minted from project.last_issue_seq. Surgical fix for SOR-222: an orphan-PR adoption synthesized issue `TREAT-500` (a non-canonical key the operator UI can't render); this path renames it to a canonical `SOR-N` while preserving the row's full audit trail across every referencing table.
Submits a typed EventOperatorIssueMutation onto the daemon's event channel and waits up to ~5s for the main loop to ack with the new key. The mutation handler runs on the main goroutine so the d.state.Issues map mutation is single-writer.
Returns:
- (newKey, nil) on success.
- ("", httpapi.ErrNotFound) when oldKey is absent from d.state.Issues.
- ("", err) for issuestore failures.
- ("", "ack timeout") when the main loop is wedged.
func (*Daemon) ApplyOperatorRemoveBlocker ¶
func (d *Daemon) ApplyOperatorRemoveBlocker(ctx context.Context, blockerKey, blockedKey string) error
ApplyOperatorRemoveBlocker is the inverse of ApplyOperatorAddBlocker: drops blockerKey from blockedKey's DependsOn (idempotent — a remove on a missing edge replies nil) and fires the Linear relation remove. Same error envelope as the Add path minus the cycle check.
func (*Daemon) ApplyOperatorReplaceIssue ¶
func (d *Daemon) ApplyOperatorReplaceIssue(ctx context.Context, in httpapi.IssueCreateInput, oldKey string) (string, error)
ApplyOperatorReplaceIssue atomically replaces the issue at oldKey with a freshly-minted live replacement (SPEC-SOR-2826). It is the public submit- wrapper for MutationReplace, modeled on ApplyOperatorCreateIssue (it returns a minted key) rather than ApplyOperatorCancel (which returns no value): the reply chan carries CreateMutationResult so the minted replacement key flows back to the operator. The main-loop handler applyOperatorReplaceIssue (internal/daemon/operator_replace.go, the prior child's deliverable) performs the mint + forward-edge copy + reverse-dependent repoint + old-issue cancel in one issuestore transaction.
Returns:
- (newKey, nil) on success — newKey is the locally-minted SOR-N.
- ("", httpapi.ErrNotFound) when oldKey is absent from d.state.Issues.
- ("", err wrapping httpapi.ErrBadRequest) when the old issue has materialized children, is already terminal, or a --depends-on flag names the old key.
- ("", err wrapping httpapi.ErrCycle) when a --depends-on flag would close a cycle with the replacement.
- ("", err) for local failures (issuestore commit, missing project row).
- ("", "ack timeout") when the main loop is wedged.
func (*Daemon) ApplyOperatorRetitle ¶
ApplyOperatorRetitle corrects an issue's title in place and writes an audited history row in response to an operator-driven request (HTTP POST /v1/issues/{key}/retitle or `sorcerer issue retitle`). It is the structural twin of ApplyOperatorSetPriority — submits a typed MutationRetitle onto the daemon's event channel and waits up to ~5s for the main loop to ack.
Like the priority mutation it touches one field only: issue state, state_kind, session_id, and every dependencies row are left untouched, so the issue's key, in-flight session, and dependency edges are preserved (SPEC-SOR-2786-v1 R7). The audit row captures actor, reason, timestamp, and the prior + new title so the correction is an audited history extension that destroys no prior row (R8).
Returns:
- nil on success.
- httpapi.ErrNotFound when key is absent from d.state.Issues.
- an error wrapping httpapi.ErrInvalidTransition when the issue is in a terminal state (handler maps to 409). Terminal issues are part of the historical record and do not get re-titled.
- other errors propagated from the issuestore for the handler to surface as 500 (including a defense-in-depth empty-title rejection — the wire layer validates non-empty first).
- "ack timeout" when the main loop is wedged.
func (*Daemon) ApplyOperatorRouteMove ¶
func (d *Daemon) ApplyOperatorRouteMove(ctx context.Context, sourceKey, targetDaemon, reason string) (string, string, error)
ApplyOperatorRouteMove executes the SOR-1278 cross-daemon operator- move. Runs on the operator goroutine; main-loop state writes happen inside the two mutations the method submits. Returns the destination daemon's assigned key + daemon name on success.
Failure modes (the HTTP handler maps each to a wire status):
- err wrapping httpapi.ErrBadRequest → 400. Empty sourceKey / targetDaemon / reason, missing / stale topology, move-to-self, or an unknown target daemon.
- httpapi.ErrNotFound → 404. sourceKey absent from state.Issues.
- *httpapi.RouteMoveRefusedError → 409. The source has a live session attached, or the SM does not admit abandonment from its current state.
- *httpapi.RouteMoveForwardError → 502. The destination's POST returned non-201 or the request never reached it; the source issue stays in its pre-move state.
- "ack timeout" when the main loop is wedged (mirrors every other ApplyOperator* method).
func (*Daemon) ApplyOperatorSetPriority ¶
func (d *Daemon) ApplyOperatorSetPriority(ctx context.Context, key string, newPriority int, reason string) error
ApplyOperatorSetPriority updates an issue's Linear priority in place and writes an audited history row in response to an operator-driven request (HTTP POST /v1/issues/{key}/priority or `sorcerer issue priority`). Submits a typed EventOperatorIssueMutation onto the daemon's event channel and waits up to ~5s for the main loop to ack.
The priority update touches one field only — issue state, state_kind, and every row in the dependencies table are left untouched. The audit row captures actor, reason, timestamp, and the prior + new priority so the change is reconstructible from the audit trail without an out-of-band export.
Returns:
- nil on success.
- httpapi.ErrNotFound when key is absent from d.state.Issues.
- an error wrapping httpapi.ErrInvalidTransition when the issue is in a terminal state (handler maps to 409). Terminal issues are part of the historical record and do not get re-prioritized.
- other errors propagated from the issuestore for the handler to surface as 500 (including a defense-in-depth out-of-range rejection — the wire layer validates the range first).
- "ack timeout" when the main loop is wedged.
func (*Daemon) ApplyOperatorSpecAmend ¶
func (d *Daemon) ApplyOperatorSpecAmend(ctx context.Context, key, narrowChange, reason, scope string) error
ApplyOperatorSpecAmend dispatches a TASK: amend on the spec_drafter on the main loop. scope is the local|structural discriminator (empty = full re-author). See applyOperatorSpecAmend.
func (*Daemon) ApplyOperatorSpecApprove ¶
ApplyOperatorSpecApprove transitions spec_review → spec_approved on the main loop. See applyOperatorSpecApprove.
func (*Daemon) ApplyOperatorSpecPatch ¶
func (d *Daemon) ApplyOperatorSpecPatch(ctx context.Context, key string, findingID int64, patches json.RawMessage) error
ApplyOperatorSpecPatch applies an RFC-6902 JSON-Patch to the current spec version on the main loop. See applyOperatorSpecPatch.
func (*Daemon) ApplyOperatorSpecSuppressFinding ¶
func (d *Daemon) ApplyOperatorSpecSuppressFinding(ctx context.Context, key string, findingID int64, reason string) error
ApplyOperatorSpecSuppressFinding suppresses one finding on the main loop. See applyOperatorSpecSuppressFinding.
func (*Daemon) ApplyOperatorSpecVerify ¶
ApplyOperatorSpecVerify re-runs the verifier against the planning issue's current spec version on the main loop. See applyOperatorSpecVerify.
func (*Daemon) ApplyOperatorStewardAnswerQuestion ¶
func (d *Daemon) ApplyOperatorStewardAnswerQuestion(ctx context.Context, id int64, chosenLabel string) error
ApplyOperatorStewardAnswerQuestion resolves one operator question (R17): it reads the question row, finds the option matching chosenLabel, applies that option's action through the EXISTING operator mutation surface (ApplyOperatorTransition / ApplyOperatorCancel — no bespoke writer path), and then expires the question row. Runs OFF the main goroutine (the operator HTTP bridge path, like the other ApplyOperator* helpers), so the per-action Submit+await round-trips do not deadlock the single-writer loop.
Returns a wrapped httpapi.ErrNotFound when the row is absent, httpapi.ErrBadRequest when the row is not an operator-question / the options are unparseable / no option matches chosenLabel / the action kind is unknown, and otherwise the error from the underlying mutation bridge. No mutation is applied unless a matching, well-formed option is found, so a bad request leaves the subject parked and the row intact.
func (*Daemon) ApplyOperatorStewardLedgerCreate ¶
func (d *Daemon) ApplyOperatorStewardLedgerCreate(ctx context.Context, row issuestore.StewardLedgerRow) (issuestore.StewardLedgerRow, error)
ApplyOperatorStewardLedgerCreate persists one steward_ledger row through the single-writer main loop and returns the created row with its AUTOINCREMENT id populated. Submits a typed EventOperatorIssueMutation and waits the two-phase ack/handler window. Returns a wrapped httpapi.ErrBadRequest when the row is nil / row_type empty; other errors propagate from the issuestore.
func (*Daemon) ApplyOperatorStewardLedgerExpire ¶
ApplyOperatorStewardLedgerExpire hard-deletes the steward_ledger row with the given id through the single-writer main loop. Returns a wrapped httpapi.ErrNotFound when no row matches.
func (*Daemon) ApplyOperatorStewardLedgerUpdate ¶
func (d *Daemon) ApplyOperatorStewardLedgerUpdate(ctx context.Context, row issuestore.StewardLedgerRow) error
ApplyOperatorStewardLedgerUpdate overwrites every mutable column of the steward_ledger row identified by row.ID through the single-writer main loop. Returns a wrapped httpapi.ErrNotFound when no row matches; httpapi.ErrBadRequest when row is nil / id zero / row_type empty.
func (*Daemon) ApplyOperatorTransition ¶
ApplyOperatorTransition flips an issue's SM state in response to an operator-driven request (HTTP /v1/issues/{key}/transition or `sorcererd issue transition`). Submits a typed EventOperatorIssueMutation onto the daemon's event channel and waits up to ~5s for the main loop to ack.
Returns:
- nil on success.
- httpapi.ErrNotFound when key is absent from d.state.Issues.
- an error wrapping httpapi.ErrInvalidTransition when the SM refuses the move (handler maps to 409).
- other errors propagated from the issuestore / Linear push for the handler to surface as 500.
- "ack timeout" when the main loop is wedged.
The wire pattern mirrors SubmitOperatorCommand (reply chan + 5s ack timeout) but the event carries a typed Mutation payload, not a stringified prefix form — the operator-mutation PLAN extends this shape rather than overloading the legacy command channel.
func (*Daemon) ApplyProjectApprovedChildren ¶
ApplyProjectApprovedChildren submits a MutationProjectApprovedChildren event onto the daemon's main loop and blocks (two-phase ack/reply, same shape as ApplyOperatorCreateIssue / ApplyOperatorTransition) until the in-memory projection completes or the deadline expires.
Called from handlePlanReview immediately after Store.ReviewProposal (approve) commits off the main goroutine, so the proposal's children enter d.state.Issues synchronously with the approve operation rather than waiting for the next verdict-harvest (session-completion event / scheduler-pass poll) or the 5-minute recovery sweep. This closes the stranded-live-orphan window the db-nonterminal-issue-present-in-memory invariant flags: between the off-main approve-commit and the projection, every approved child is a non-terminal issuestore row absent from d.state.Issues — schedulable, cancelable, and dispatchable by nothing.
The projection lands on the single-writer main goroutine (the sole writer of d.state), so this is the approve-side analog of the ApplyOperatorCreateIssue ack bridge that coordinates an off-main operator create with the main loop.
func (*Daemon) ApplyRecoverSweep ¶
func (d *Daemon) ApplyRecoverSweep(ctx context.Context, kinds []string) (httpapi.RecoverResponse, error)
ApplyRecoverSweep submits a recovery-sweep event onto the daemon's main loop and waits up to ~10s for the handler to reply with the per-issue records. Implements httpapi.RecoverBackend so the HTTP /v1/recover handler can dispatch through the same single-writer pattern operator mutations use.
kinds may be nil/empty (run every sweep) or a subset of the closed list. Validation against the closed list happens at the wire layer before this is reached; the daemon-side path trusts its input.
func (*Daemon) ApplyStewardBlockIntent ¶
func (d *Daemon) ApplyStewardBlockIntent(ctx context.Context, issueKey, diagnosis, reason string) error
ApplyStewardBlockIntent routes a steward block-intent to the main goroutine where the escalation chokepoint runs (R20). It is safe to call off-main (the steward wake goroutine): it submits a typed MutationStewardBlockIntent and awaits the reply via the standard two-phase ack/handler window, mirroring the triager's dispatchApply → EventTriagerActionApply → escalateSubject bridge and the steward-ledger ApplyOperator* mutations.
func (*Daemon) BackfillOrphanAdoptionMetadata ¶
func (d *Daemon) BackfillOrphanAdoptionMetadata(ctx context.Context, fetch OrphanAdoptionPRFetcher)
BackfillOrphanAdoptionMetadata repairs in-memory issues that were orphan-adopted by pre-SOR-233 daemons and therefore landed in d.state.Issues with empty Title AND empty Description. The pre-fix applyOrphanPRs synthesizer set neither field; this backfill fetches the PR's current title + body and writes them back, then mirrors the row so the next operator query renders an identifiable issue.
Identifying the candidates by symptom (PRURLs non-empty AND Title empty AND Description empty) sidesteps the fact that the OrphanAdopted flag is not persisted in the issuestore — after a daemon restart, every rehydrated issue starts with OrphanAdopted=false. The symptom uniquely identifies the bug: post-SOR-233 orphan adoption always populates at least the fallback Title + Description, and no other creation path attaches PRURLs without also setting Title.
Best-effort: per-issue fetch failures are skipped silently (log line only). Mirror failures surface as the existing mirror_drift_local_failed events. Runs on the boot goroutine BEFORE the daemon's main loop or HTTP server has started, so direct d.state.Issues mutation is safe (no other writer exists yet).
func (*Daemon) BootRecover ¶
BootRecover runs the synchronous boot-recovery sweeps that converge the daemon's in-memory state with the rehydrated issuestore + the remote GitHub PR state. SOR-1091 split this out of Run so production can call it BEFORE the HTTP listener opens — the first operator mutation the daemon serves then sees a fully-recovered state and the main loop is never synchronously applying the recovery storm while a 5s operator-mutation ack timeout is racing it.
Idempotent: a second call is a no-op. Run consults the same flag and skips its own recovery block when this has already been called, so tests that bypass start.go and call d.Run directly still get the recovery work for free.
MUST run on the same goroutine that will later own the daemon's state mutations (the main loop). In production this is the boot goroutine in cmd/sorcererd/start.go BEFORE the HTTP listener opens; in tests the same goroutine that later calls d.Run.
func (*Daemon) ClassifyRoute ¶
func (d *Daemon) ClassifyRoute(_ context.Context, body string, repos []string) (RouteDecision, error)
ClassifyRoute runs daemon.ResolveDestination against the live topology snapshot for the supplied body + repos and returns the resolver decision verbatim. The SOR-1279 POST /v1/issues/route-classify dry-run handler calls this through the cmdadapter.RouteClassifier adapter; the call is pure and mutation-free (no audit-event row, no state write). Returns ErrTopologyUnavailable when d.topology is nil — the wiring in start.go avoids constructing this adapter at all in that case, so the error is defensive and surfaces only when a future caller bypasses the wiring guard.
func (*Daemon) CountIssuesWithOpenPRs ¶
CountIssuesWithOpenPRs returns the number of non-terminal issues whose latestPRSnapshots entry contains at least one element with State == "OPEN" and a non-empty PRURL. Mirrors the predicate populateOpenPRDigest applies, restricted to non-terminal issues to match the SOR-117 acceptance criterion. The read of d.latestPRSnapshots + d.state.Issues is marshaled through the supervisor's main goroutine via MutationDedupeSnapshot — the same race-safety pattern the dedupe gate uses.
On ack-timeout or canceled ctx the function returns 0; the count is used to gate plan validator rule 8 (overlap_considered), and 0 is the conservative fallback (skips the check rather than falsely rejecting a valid plan when the daemon is wedged).
func (*Daemon) CreateAutonomousIssue ¶
func (d *Daemon) CreateAutonomousIssue(ctx context.Context, in httpapi.IssueCreateInput) (string, error)
CreateAutonomousIssue (SPEC-SOR-2883) is the single dedupe chokepoint every daemon-internal (autonomous) issue-create routes through — the create-with-dedupe analog of the escalateSubject / terminateSession single-chokepoint idiom. It files one prospective issue by routing it through the EXISTING dedupe gate rather than the bare main-loop persist primitive applyOperatorCreateIssue: it stamps AutonomousCreate on the input and delegates to ApplyOperatorCreateIssue (internal/daemon/operator_create.go), which runs runDedupeBeforeCreate before minting. The dedupe overlap judgment itself is unchanged — this only RUNS the existing gate on the autonomous filing path.
The three gate verdicts map as follows:
- clean (not a duplicate): the issue is persisted and the minted key is returned (R4). The operator-facing warning string ApplyOperatorCreateIssue returns is discarded — autonomous filers have no operator terminal.
- duplicate, no force-dedupe: ApplyOperatorCreateIssue returns a *DuplicateError; the create is NOT persisted (R5). This chokepoint does not propagate the error — it emits an autonomous_create_deduped audit event naming the covering issue key(s) (R6) and returns ("", nil), so an autonomous caller silently drops a deduplicated create rather than failing. Autonomous filers never set force-dedupe (the operator-only override), so the gate's existing refusal guard in runDedupeBeforeCreate applies by construction — this helper sets no force flag of its own.
- gate failure (transient / LLM failure, no verdict): ApplyOperatorCreateIssue itself fails open — it mints the issue with DedupePending=true so the deferred sweep (internal/daemon/dedupe_deferred_sweep.go) re-runs the gate later — and returns the key with a nil error, matching the operator path's defer behavior (R8). This helper returns that key unchanged.
It is safe to call ONLY off the daemon main goroutine: it performs the dedupe gate's main-loop snapshot round-trip (snapshotForDedupe submits onto the main loop and blocks for the reply), so calling it from a handler dispatched directly from d.handle would deadlock the single-writer loop (the no-main-loop-blocking-call invariant). The on-main filer that fans this out is responsible for spawning the goroutine.
Returns:
- (mintedKey, nil) when the issue was filed (clean verdict, or gate-failure fail-open defer).
- ("", nil) when the issue was suppressed as a duplicate.
- ("", err) on any other failure (validation, wedged main loop, etc.).
func (*Daemon) DryRunCreateIssue ¶
func (d *Daemon) DryRunCreateIssue(ctx context.Context, in httpapi.IssueCreateInput, citations plan.CitationChecker) (*httpapi.IssueDryRunResult, error)
DryRunCreateIssue runs the read-only create-time validation chain plus the body-features / risk-bucket analyzer against a candidate issue body and returns a typed report. It NEVER mints a key, persists a row, or invokes the dedupe LLM — the drafting skills (`/sorcerer-issue`, `/sorcerer-plan`) iterate this cheap, deterministic check with the operator before the mutating real create.
The report's exit-code discriminant, Valid, is driven ONLY by the gates a real operator-create enforces:
- the wire-presence checks the HTTP create handler runs (title / body_markdown / repos non-empty);
- kind vocabulary (parseIssueKind);
- kind-aware acceptance-criteria count (validateAcceptanceCriteriaForCreate);
- repos allowlist (validateRepos);
- depends_on existence + no in-batch refs (mirrors validateDependsOn, read against a race-safe state snapshot since this runs off the main goroutine);
- branch-model vocabulary (resolveCreateBranchModel).
The advisory surfaces — cited-file existence, the cross-issue-contracts parse, and the body-sizing warning — are REPORTED but never affect Valid. A real operator create does not reject on a missing cited file, a malformed contracts block, or a large body (the create-time cited-file check lives only in the plan validator; the contracts parse is best-effort; the sizing warning is non-fatal), so keeping them strictly separate from ValidationErrors is what makes the dry-run's exit code agree with a real create's accept/reject decision. A "valid but large" body reports its bucket while staying Valid=true.
citations is the cited-file existence checker (the same one the plan validator's rule 1 uses); nil disables the existence check, which is the correct degraded behavior when no git runner is wired — the report then carries empty absent / unverifiable lists rather than panicking.
The returned error is non-nil only on a genuine daemon-side failure; a draft a real create would reject is reported via Valid=false + ValidationErrors, not via the error.
func (*Daemon) EmitPollerRestarted ¶
EmitPollerRestarted records a poller_restarted audit row (SOR-2630) naming a supervised producer (a boot poller, the scheduler-pass producer, or the triager event dispatcher) and the recovered panic cause. Public so out-of-package wiring (the triager dispatcher's PanicSink in start.go) and in-package producers route their silent-death breadcrumb through one chokepoint. Safe to call from any goroutine — appendEvent is a synchronous issuestore write.
func (*Daemon) EmitPredictionCheck ¶
func (d *Daemon) EmitPredictionCheck(child *sm.Issue, diffFiles []string, fileContent map[string][]byte, now time.Time)
EmitPredictionCheck classifies the actual diff against the child's PredictedFootprint and appends one event (prediction_check OR prediction_check_skipped) to the daemon's audit log. Phase A of CIPB: read-only telemetry — the result does NOT affect any control flow this phase. Phase D's learning loop reads the events to drive the recognizer's planner-quality feedback.
`diffFiles` is the list of file paths the implementer's diff touched (production wires through `git diff --name-only` against the child's working branch and origin/main). `fileContent` is an optional reader the anchor heuristic uses to detect anchor drift; when nil, anchor classification is skipped (file-level counts still emit).
Hooked from the supervisor's reviewer-verdict approve handler at the moment a plan_branch child transitions to awaiting_integration — see d.applyIssueTransition site in supervisor.go.
func (*Daemon) EventChannel ¶
EventChannel returns a write-only channel for pushing events into the daemon. Equivalent to calling Submit from a goroutine; this form lets pollers configured with PollerConfig.Out write directly without the indirection. Safe to call before or during Run.
func (*Daemon) IssueStore ¶
func (d *Daemon) IssueStore() issuestore.IssueStore
IssueStore returns the daemon's issuestore handle. Exposed for tests that want to assert on rows the daemon wrote (events / escalations). Production code outside this package has no business reaching past the daemon's typed surfaces; this is a test affordance.
func (*Daemon) LookupTransitionLegality ¶
LookupTransitionLegality returns the (current_state, legal_targets, found) tuple for an issue key, used by the recognizer's transition_issue SM-aware pre-flight (Fix F / SOR-1330). Kind-aware: planning issues read PlanState + plan-table transitions; implementation issues read IssueState + issue-table transitions.
Reads d.state.Issues under d.stateMu.RLock so the snapshot is race-safe against the main goroutine's writes. Returns (issueState, sortedLegalTargets, true) for a known key and ("", nil, false) for an unknown key — the recognizer's caller falls through to the CLI on (false, ...), which surfaces the "unknown issue" error through the CLI's own validation rather than silently swallowing it here.
func (*Daemon) PRSnapshotsAll ¶
PRSnapshotsAll submits a MutationPRSnapshotsAll onto the main loop and waits up to operatorMutationAckTimeout for the reply. Same timeout / cancel shape as PRSnapshotsForIssue. Returns a deep copy of d.latestPRSnapshots keyed by issue key (nil when the supervisor hasn't seen any polls yet).
func (*Daemon) PRSnapshotsForIssue ¶
PRSnapshotsForIssue submits a MutationPRSnapshotsForIssue onto the main loop and waits up to operatorMutationAckTimeout for the reply. Mirrors the timeout / context-cancel shape every other operator mutation uses. Returns a copy of d.latestPRSnapshots[key] (nil when the supervisor hasn't seen a poll for the issue yet).
Errors:
- "ack timeout" when the main loop is wedged.
- ctx.Err() when the caller's context is canceled first.
- the per-mutation handler's error when the dispatcher rejects the payload (currently unreachable — preserved so future failure modes can surface to the caller without changing the signature).
func (*Daemon) ProgressByIssue ¶
ProgressByIssue is the bulk per-issue progress lookup the SPA's GET /v1/issues path consumes (wired onto cmdadapter.IssueReader.ProgressByIssue in start.go). It fetches its own issuestore rows and sessions off the WAL-multi-reader connection — never d.state, never the mutation channel — then delegates to gatherProgressFromRows, so it is safe to call from the off-main HTTP handler goroutine (the R11 snapshot-answerable contract). Returns (nil, nil) when the daemon has no issuestore.
func (*Daemon) RecomputeStageBaselines ¶
RecomputeStageBaselines is the exported, off-band wrapper start.go's stage-baseline-refresh poller invokes (SOR-2859). It delegates to the package-internal recomputeStageBaselines over the daemon's own issuestore — a heavy historical aggregation that runs as its own poller off the single-writer main loop, never inline on a serving path. A nil issuestore (a test daemon constructed without one) is a no-op, never a panic.
func (*Daemon) RequireOperatorApproval ¶
RequireOperatorApproval reports whether this project is configured to require a manual operator `sorcerer spec approve` rather than auto-approving a coherence-clean spec. Exported so package cmdadapter can project the value onto the SpecView wire envelope without reaching the unexported d.cfg directly.
func (*Daemon) Run ¶
Run is the daemon's main entry point. Blocks until ctx is canceled, SIGTERM arrives, or a fatal error.
Goroutines started by Run:
- Debouncer goroutine
- Each Poller's goroutine
- Signal watcher
Returns nil on graceful shutdown, error otherwise.
func (*Daemon) RunSchedulerPass ¶
RunSchedulerPass is the concrete SchedulerPass implementation used when the caller does not supply one in Config. It:
- Builds the dep graph and picks ready leaves (schedule.Pick).
- Transitions ready leaves from waiting → ready (phase 2; agent dispatch is wired in phase 4 when the implementer role is live).
- Manages the Project SM: - idle/frozen → active when there is live work - active → idle when no issues are non-terminal and none are in-flight - active → frozen when BlockedAll (every candidate is blocked_user)
Return value is informational. The debouncer logs failures but does not halt; the next pass will retry.
func (*Daemon) SetEventBroadcast ¶
func (d *Daemon) SetEventBroadcast(fn func(issuestore.Event)) error
SetEventBroadcast installs the non-blocking audit-event broadcast callback invoked from appendEvent after each SaveEvent succeeds (SOR-1177). The seam is single-subscriber by design: a second install returns an error rather than silently overwriting the first subscriber. fn must be non-nil and non-blocking. Call before Run / from the main goroutine so the lock-free read in appendEvent is race-free.
func (*Daemon) SetEventHook ¶
SetEventHook installs an inspector for tests. Called inline on every event before handle dispatches.
func (*Daemon) SetPlanActivatingPoller ¶
SetPlanActivatingPoller wires the activation poller handle onto the daemon so the follow-on dispatch (dispatchFollowOnPlanning's plan_activating case) can Trigger an immediate sweep on a daemon restart that finds a plan already in plan_activating, rather than waiting out the first scheduled tick. Called once from cmd/sorcererd/start.go before d.Run; before that call the plan still converges on the timer-driven cadence.
func (*Daemon) SetPlanPRMergePendingCIPoller ¶
SetPlanPRMergePendingCIPoller wires the SOR-1360 pending-CI poller handle onto the daemon so applyPlanPRMergeVerdict's verdict=merge arm can call Trigger after the state transition. Called once from cmd/sorcererd/start.go before d.Run; before that call applyPlanPRMergeVerdict falls back to the timer-driven cadence for the first poll.
func (*Daemon) SetTriagerNotifyBlocked ¶
SetTriagerNotifyBlocked installs the event-driven triager notification hook. The supervisor's transition-write path calls the hook on every transition INTO blocked_user or plan_blocked. Safe to call before Run; production wiring (cmd/sorcererd/start.go) wires it to TriagerEventDispatcher.Notify. Test wiring can install a fake to observe notifications without spinning up the full dispatch goroutine.
Idempotent: calling it twice replaces the prior hook; nil clears it.
func (*Daemon) State ¶
State returns the current State pointer. Read-only callers may use it for inspection; writes must happen through handlers on the main goroutine.
func (*Daemon) StatusSnapshot ¶
StatusSnapshot returns the operator-facing status map the HTTP control plane serves at GET /v1/status. Reads state.json off disk for the project SM + updated_at (still authoritative there); issue/session counts come from the issuestore — the post-v3 source of truth and the only race-safe reader for an HTTP-handler goroutine (d.state writes happen on the main goroutine; iterating d.state.Issues here would be a data race). Runtime data (subscriptions, pollers, GitHub token) is read via concurrency-safe callbacks.
func (*Daemon) Submit ¶
Submit pushes an event onto the daemon's channel. Used by tests and by external goroutines (the request-watcher, the HTTP control plane's operator handlers) to inject events. Blocking semantics: if the channel is full, Submit blocks — backpressure on the producer.
func (*Daemon) SubmitNonblocking ¶
SubmitNonblocking pushes an event onto the channel; returns true if accepted, false if the channel was full. Used by per-session timers where dropping a duplicate timeout is acceptable.
func (*Daemon) SubmitOperatorCommand ¶
SubmitOperatorCommand pushes an operator command onto the event channel and waits up to 5s for the main loop to ack it. Used by the HTTP control plane. Returns nil on success, the applied error on rejection, or "ack timeout" on a stalled main loop.
The command string carries any payload — for replan it's of the form "replan:<issue>:<reason>", matching the wire shape applyOperatorCommand expects.
func (*Daemon) SweepCapabilityGapRelease ¶
SweepCapabilityGapRelease is the exported wrapper the SOR-3084 release poller invokes (and tests drive synchronously). Delegates to sweepCapabilityGapRelease so the unexported method stays close to its helpers. MUST be invoked on the main goroutine (sole writer to d.state).
func (*Daemon) SweepDedupePending ¶
SweepDedupePending re-runs the dedupe gate for every issue in d.state.Issues that carries DedupePending=true. Fix D (SOR-1330) makes the dedupe gate gracefully degrade at create time when the LLM call times out or returns a transient error — the create proceeds with DedupePending=true and this sweep picks the gate up when capacity returns.
On a successful re-check the marker is cleared and a `dedupe_resolved` event fires. When the deferred re-check finds the issue overlaps a still-in-flight one, a high-visibility `dedupe_deferred_found_duplicate` event fires naming both keys so the operator can manually cancel one — we do NOT auto-cancel.
Concurrency: the sweep runs on a caller-controlled goroutine. The per-issue state mutations route through the operator-mutation surface (so the single-writer invariant holds); the read of d.state.Issues snapshots under d.stateMu.RLock before the dispatch loop.
Returns the count of (resolved, still-pending, found-duplicate) outcomes for observability — the caller writes a sweep summary event.
func (*Daemon) SweepEphemeralWorktreePrune ¶
SweepEphemeralWorktreePrune is the exported wrapper the production poller's PollFn invokes (off the main goroutine) and tests invoke synchronously. Mirrors the off-main I/O shape of SweepPlanPRMergePendingCI.
func (*Daemon) SweepPerIssueWorktreeGC ¶
func (d *Daemon) SweepPerIssueWorktreeGC()
SweepPerIssueWorktreeGC is the exported wrapper the EventPerIssueWorktreeGCSweep dispatch invokes (and tests invoke synchronously). Like sweepPlanMainMerge it MUST be invoked on the main goroutine — it reads d.state.Issues — and the production poller drives it by submitting EventPerIssueWorktreeGCSweep onto the main loop rather than calling it from the off-main PollFn.
func (*Daemon) SweepPlanActivating ¶
SweepPlanActivating is the exported wrapper the activation poller invokes from its goroutine. Delegates to sweepPlanActivating; returns the number of plans inspected for poller-stats / test observation.
func (*Daemon) SweepPlanAssembleStaleRerere ¶
SweepPlanAssembleStaleRerere is the exported wrapper the test harness invokes synchronously. Production wiring submits EventPlanAssembleStaleRerereSweep onto the main loop; the dispatch arm in applyEvent calls the unexported sweepPlanAssembleStaleRerere directly so the constraint holds at the production call site.
MUST be invoked on the main goroutine (sole writer to d.state).
func (*Daemon) SweepPlanMainMerge ¶
SweepPlanMainMerge is the exported wrapper the CIPB Phase C poller invokes. Delegates to sweepPlanMainMerge so the unexported method stays close to its supporting helpers. MUST be invoked on the main goroutine (sole writer to d.state).
The wrapper exists for tests that drive the sweep synchronously without spinning up Run; production wiring submits the EventPlanMainMergeSweep event onto the main loop's event channel.
func (*Daemon) SweepPlanPRMergePendingCI ¶
SweepPlanPRMergePendingCI is the exported wrapper the SOR-1360 poller invokes from its goroutine. Delegates to sweepPlanPRMergePendingCI; exists separately so the unexported method can stay close to its supporting helpers while the start.go wiring takes only an exported surface. Returns the number of plans inspected for poller-stats / test observation.
func (*Daemon) Topology ¶
Topology returns the daemon's peer-cache handle (SOR-1191). nil when the daemon was constructed without a UI endpoint (registration disabled). cmd/sorcererd/start.go threads it into the httpapi server so the SOR-1214 cross-daemon auth path on POST /v1/issues can verify forwarded-request bearers against the named source daemon's AuthTokenHash without ever holding the peer's raw token.
func (*Daemon) TriggerPoller ¶
TriggerPoller wakes the named poller for an immediate firing without waiting out its configured cadence (SOR-147). Returns true when a matching poller is present and was kicked; false otherwise. Used by the triager executor's escalate_to_recognizer path to fast-track the recognizer's next tick within seconds rather than waiting an hour for the periodic timer.
Safe to call from any goroutine — daemon.Poller.Trigger is goroutine-safe. The poller slice is appended to before Run; this helper iterates it read-only and never mutates.
type DaemonInfo ¶
DaemonInfo is one peer daemon as seen through the UI registry: its operator-readable name, control-plane URL, the OWNER/REPO slugs it is the merge-authority for, and the hex-lowercase SHA-256 of the peer's bearer token (SOR-1214) used to authenticate cross-daemon-forwarded inbound requests against the peer's identity without holding the raw token. AuthTokenHash is empty until the UI registry exposes it (older peers, static-config-only rows).
type Debouncer ¶
type Debouncer struct {
// contains filtered or unexported fields
}
Debouncer collapses a burst of "scheduler-needs-to-run" signals into a single SchedulerPass invocation. Many events arriving within `quiet` of each other → exactly one pass after the last one.
Design:
- Trigger() is non-blocking: it sets a "dirty" flag and (re)arms the timer.
- The internal goroutine waits on the timer; when it fires, the dirty flag is consumed and the SchedulerPass runs.
- While a pass is running, additional Trigger() calls re-set the dirty flag; on completion, if dirty is set, we re-arm immediately.
- This means even a long-running pass can't lose a follow-up trigger.
This is a deliberate departure from "tick at fixed rhythm": the scheduler runs *only* when something changes, but a flurry of changes produces one consolidated pass, not N races.
func NewDebouncer ¶
func NewDebouncer(quiet time.Duration, pass SchedulerPass) *Debouncer
NewDebouncer constructs a Debouncer. quiet is the debounce window (~100ms in production); pass is the SchedulerPass to invoke.
func (*Debouncer) AdvanceQuietForTest ¶
func (d *Debouncer) AdvanceQuietForTest()
AdvanceQuietForTest deterministically closes any in-flight or next-arriving quiet window: the next select in waitQuiet observes a pre-buffered signal and returns immediately, letting runPass fire without waiting on the wall-clock timer. Non-blocking; coalesces if called repeatedly before being consumed.
Test-only seam. Production wiring relies on the real `quiet` timer; nothing in the daemon ever calls this. Tests use it when they need a burst of triggers to coalesce into one pass by construction — set `quiet` to a value far larger than the test's execution budget, push the burst, poll TriggerCount() until every trigger has been delivered, then call AdvanceQuietForTest to close the window. SOR-297: replaces the prior wall-clock dependency that lost the race against -race + parallel-package deschedules between Submit calls.
func (*Debouncer) LastPassAt ¶
LastPassAt returns the wall-clock instant the most recent SchedulerPass completed, or the zero time if none has completed yet. Read by the flow-liveness monitor (SOR-2630) off the main goroutine.
func (*Debouncer) LastTriggerAt ¶
LastTriggerAt returns the wall-clock instant of the most recent Trigger, or the zero time if none has arrived. Paired with LastPassAt it distinguishes an idle daemon (no trigger pending → lastTriggerAt <= lastPassAt) from a wedged producer (a trigger arrived but no pass serviced it → lastTriggerAt > lastPassAt).
func (*Debouncer) PassCount ¶
PassCount returns the number of SchedulerPass invocations completed. Used by tests; in production, events.log records each pass.
func (*Debouncer) RestartCount ¶
RestartCount returns the number of times the supervised loop recovered a panic and restarted (SOR-2630). Used by tests; in production each restart emits a poller_restarted audit row via panicSink.
func (*Debouncer) SetPanicSink ¶
SetPanicSink installs the callback invoked with the recovered panic value on every supervised restart. Call before Start. nil-safe.
func (*Debouncer) SetRestartBackoff ¶
SetRestartBackoff overrides the initial post-panic backoff. Call before Start. Tests set a small value to keep restart-loop runtime down; production leaves the supervisorInitialBackoff default.
func (*Debouncer) Start ¶
Start launches the debouncer goroutine. The goroutine runs until ctx is canceled or Stop is called.
func (*Debouncer) Stop ¶
func (d *Debouncer) Stop()
Stop halts the goroutine. Any in-flight SchedulerPass runs to completion; pending triggers are dropped.
func (*Debouncer) Trigger ¶
func (d *Debouncer) Trigger()
Trigger schedules a SchedulerPass run. Non-blocking: marks the dirty flag and notifies the goroutine. If the goroutine is already running a pass, the trigger is honored on completion.
Trigger may be called from any goroutine.
func (*Debouncer) TriggerCount ¶
TriggerCount returns the cumulative number of Trigger() invocations observed by this Debouncer. Monotonic; safe to call from any goroutine.
Used by tests that drive a burst of triggers through the daemon's real Submit → EventTriggersScheduler → Trigger wiring and need to wait for every trigger to have been delivered before deterministically closing the quiet window (see AdvanceQuietForTest). Production code has no reason to read this — the events.log records each pass.
type DedupeDispatcher ¶
type DedupeDispatcher func(context.Context, role.DedupeTask) (role.DedupeVerdict, error)
DedupeDispatcher is the typed function the daemon calls to run one dedupe LLM session. role.NewDedupeDispatcher returns the production implementation; tests wire a fake. Returning a verdict with Status=DedupeStatusOK and IsDuplicate=true triggers the 409 refusal path (unless the create request carries force_dedupe=true).
type DedupeSnapshotIssue ¶
type DedupeSnapshotIssue struct {
Key string
Kind string
State string
Title string
Description string
CreatedAt int64
// Repos is the row's affected-repo list (iss.Repos), deep-copied at
// snapshot time. Used only in-process by buildDedupePayload's
// relevance ranking (repo-overlap signal) — it is NOT serialized
// into the LLM payload (the wire struct carries kind/key/title/
// state/description only).
Repos []string
}
DedupeSnapshotIssue is one row of the dedupe digest's issues slice. Carries enough description for the dedupe LLM to judge overlap without re-fetching from Linear or the issuestore. State is the effective state for the row's kind — IssueState text for Kind=implementation, PlanState text for Kind=planning.
type DedupeSnapshotPR ¶
DedupeSnapshotPR is one row of the dedupe digest's PRs slice. The state field is mirrored from PRSnapshot.State and is "OPEN" for every entry the dedupe gate consumes — the snapshot handler filters CLOSED / MERGED PRs out before replying.
type DedupeSnapshotResult ¶
type DedupeSnapshotResult struct {
Issues []DedupeSnapshotIssue
PRs []DedupeSnapshotPR
Err error
}
DedupeSnapshotResult is the reply payload from MutationDedupeSnapshot. Issues filters out terminal-state rows; PRs filters to OPEN. Err is non-nil only when the main loop failed to assemble the snapshot (currently unreachable — preserved so future error modes can surface to the caller).
type DiffStatTotals ¶
DiffStatTotals is the summed git-diff numstat across an implementer cycle's per-repo worktrees: file count plus added / removed line totals. Feeds the implementer_cycle_summary event's files_modified / lines_added / lines_removed fields. All-zero on the no-branch / git-error path (best-effort telemetry).
func ParseGitDiffNumstat ¶
func ParseGitDiffNumstat(raw string) DiffStatTotals
ParseGitDiffNumstat parses the output of `git diff --numstat` into summed totals. Each numstat line is `<added>\t<removed>\t<path>`; a binary file reports `-\t-\t<path>` (counted as a modified file with zero line deltas). Blank lines are skipped. A line whose path is empty or whose first two fields don't parse as the numstat shape is skipped rather than aborting the whole parse — the helper is best-effort telemetry, not a validator. Peer of ParseGitDiffNameOnly.
type DiscoveredPR ¶
DiscoveredPR is the typed shape PRSetDiscoverer returns: the PR's URL plus the head commit SHA at the moment of discovery. The dedupe sweep uses HeadSHA to detect operator commits that bypass the blocked_user time-based cooldown — a HEAD that differs from the recorded escalation snapshot proves new work has landed and the reviewer should re-evaluate immediately.
type DiscoveryRoleConfig ¶
type DiscoveryRoleConfig struct {
// Model is the per-project full model id for this discovery phase (e.g.
// claude-opus-4-8); "" → the daemon omits --model for the dispatch.
// Threaded verbatim from the discovery role's triple (config.Roles.TripleFor).
Model string
// Effort is the per-project effort override (low|medium|high|xhigh);
// "" → the daemon omits --effort. Threaded verbatim from the discovery
// role's triple (config.Roles.TripleFor).
Effort string
// DeviationThresholdPct is the resolved files-edited-not-cited percentage
// at/above which the role's *_phase_discovery_deviation event fires.
// Already defaulted to 30 by config.Roles.DeviationThresholdPctFor at
// boot; an explicit 0 is honored.
DeviationThresholdPct int
}
DiscoveryRoleConfig carries the per-project discovery-phase knobs the supervisor consults at dispatch + telemetry time for one discovery role (implementer_discovery / reviewer_discovery). Projected from config.Roles at boot (cmd/sorcererd/start.go). The zero value (empty Model/Effort, DeviationThresholdPct 0) is NOT the production default — start.go always resolves DeviationThresholdPct through config's DeviationThresholdPctFor so it lands as 30 when the knob is absent. Empty Model/Effort mean the daemon omits the corresponding CLI flag for the discovery dispatch (R4: no resolution-layer substitution).
type DispatchOutcome ¶
type DispatchOutcome string
DispatchOutcome is the 3-valued classification of a CIPB role-session dispatch attempt (SPEC-SOR-3197-v1). It carves the no_provider class — "no selectable OAuth token to start the session" — OUT of the broad infrastructure_transient class so the rest of the SOR-3197 fix can exclude no_provider attempts from every retry / liveness budget and from human-block escalation (the SOR-2991/SOR-3155 false-plan_blocked class) while keeping genuinely-spawned-then-failed sessions bounded by the existing SPEC-SOR-2782 budgets.
Both this type and ClassifyDispatchOutcome are exported so the SPEC-SOR-3197-v1 prop-test helpers (the external `gen` package) can call the production decision function directly rather than re-modeling it — the same export-for-prop-test pattern ActionsWaitTimeoutFires follows.
const ( // DispatchOutcomeNoProvider: the dispatch never reached a running // session because the OAuth picker reported pool exhaustion — every // token throttled / already tried (*agentcli.PoolExhaustedError) // or the wrapped spawn_picker_unavailable spawn class. Excluded from // every retry / liveness budget and from human-block escalation. DispatchOutcomeNoProvider DispatchOutcome = "no_provider" // DispatchOutcomeInfraTransient: a session spawned and then hit // transient host trouble — SIGKILL, a context deadline, or a // non-picker spawn failure. Bounded by the SPEC-SOR-2782 retry / // liveness budgets. DispatchOutcomeInfraTransient DispatchOutcome = "infra_transient" // DispatchOutcomeGenuine: any other error — a real resolver / role // outcome that consumes the genuine-resolution budget exactly as // today. DispatchOutcomeGenuine DispatchOutcome = "genuine" )
func ClassifyDispatchOutcome ¶
func ClassifyDispatchOutcome(err error) DispatchOutcome
ClassifyDispatchOutcome maps a dispatch-attempt error to its DispatchOutcome class. The no_provider carve-out sits IN FRONT of the infrastructure-transient check so the pool-exhausted / picker-unavailable shapes are pulled out of infra_transient (the class they would otherwise fall into via isMergeResolverInfraTransient's SpawnPickerUnavailable arm, which is left untouched — that arm simply becomes unreachable through this classifier). Priority order, evaluated against the first match:
- no_provider — isOAuthPoolExhaustedError catches the unwrapped typed *agentcli.PoolExhaustedError (and the "every OAuth token is throttled" substring fallback); the SpawnFailureClassOf == SpawnPickerUnavailable check catches the wrapped *spawnError form the runner hands the daemon.
- infra_transient — isMergeResolverInfraTransient covers SIGKILL, context.DeadlineExceeded, and every other (non-picker) spawn class.
- genuine — the fallback; nil falls through here (both discriminators return false for nil).
Exported so the SPEC-SOR-3197-v1 prop-test helpers can call the production decision function directly (see the DispatchOutcome type comment).
type DispatchSituation ¶
type DispatchSituation string
DispatchSituation is the 4-valued classification of a CIPB integration dispatch situation (SPEC-SOR-3197-v1), mapped at each seam (by a later SOR-3197 child) from the integration-dispatch gate-held bit plus the DispatchOutcome ClassifyDispatchOutcome assigns to a spawned-then-failed attempt. It is the sole input domain — together with the budget-exhausted bit — of RouteDispatchSituation.
const ( // DispatchSituationGateHeld: the integration-dispatch gate suppressed the // attempt before any session spawned (IntegrationDispatchGateHeld true). // No session ran, so there is no outcome to count — routes to Hold. DispatchSituationGateHeld DispatchSituation = "gate_held" // DispatchSituationNoProvider: a spawn was attempted but the OAuth picker // reported pool exhaustion (DispatchOutcomeNoProvider). No session ran — // routes to Hold, excluded from every budget and from escalation. DispatchSituationNoProvider DispatchSituation = "no_provider" // DispatchSituationInfraTransient: a session spawned then hit transient // host trouble (DispatchOutcomeInfraTransient). Bounded by the // SPEC-SOR-2782 budgets — CountRetry until the budget is exhausted, then // Escalate. DispatchSituationInfraTransient DispatchSituation = "infra_transient" // DispatchSituationGenuine: any other (real resolver / role) outcome // (DispatchOutcomeGenuine) — always Escalate, as today. DispatchSituationGenuine DispatchSituation = "genuine" )
type DuplicateEntry ¶
type DuplicateEntry = httpapi.DuplicateEntry
DuplicateEntry mirrors httpapi.DuplicateEntry.
type DuplicateError ¶
type DuplicateError = httpapi.DuplicateError
DuplicateError and DuplicateEntry are re-exported from the httpapi package so daemon callers can construct the typed refusal without importing httpapi explicitly at every call site. The wire shape (key + one-line overlap) lives at the source of truth in internal/daemon/httpapi/server.go.
type ErrRefactorPlanAborted ¶
type ErrRefactorPlanAborted struct {
// contains filtered or unexported fields
}
ErrRefactorPlanAborted wraps the underlying issuestore error when the single-transaction `plan refactor` cascade rolls back. It names the failing child (the inner error carries a `child <KEY>:` prefix) so the operator CLI can surface which write tripped contention; the operator simply re-runs the command. Under the atomic model the result is binary — every listed key is canceled, or none are — so there is no partial-cancel set to report when this error is returned.
func (*ErrRefactorPlanAborted) Error ¶
func (e *ErrRefactorPlanAborted) Error() string
func (*ErrRefactorPlanAborted) Unwrap ¶
func (e *ErrRefactorPlanAborted) Unwrap() error
type Event ¶
type Event struct {
Kind EventKind
At time.Time
// EventSessionResult / EventSessionTimeout / EventSessionCrashed
SessionID string
SessionError error // nil on success
// SessionCrashMessage is the watchdog's last-known forensic line
// for an EventSessionCrashed event (e.g. role + pgid). Empty for
// other event kinds.
SessionCrashMessage string
// EventSessionLifecycle payload. SessionPhase is "starting" or
// "running"; SessionStateDir is the runner's per-session state dir
// (so the heartbeat watchdog can locate <dir>/heartbeat). Empty for
// other event kinds.
SessionPhase string
SessionStateDir string
// EventSessionSpawnFailed payload. nil for other event kinds.
SpawnFailure *SpawnFailureEvent
// EventSessionInitFailed payload. nil for other event kinds.
SessionInitFailure *SessionInitFailureEvent
// EventRequestArrived
RequestPath string
// EventOperatorCommand
Command string
CommandReply chan<- error // optional: caller blocks until handler ack
// EventGitHubPRsPolled payload: freshly-fetched PR state per issue
// and repo. The handler calls ApplyGitHubPoll with this slice.
PRSnapshots []PRSnapshot
// PRDiffSummaries carries pre-computed per-PR-URL diff-summary strings,
// keyed by PR URL. Populated by the github-prs poller goroutine
// alongside PRSnapshots (one gh diff-stat call per OPEN PR, off the
// main goroutine); written to d.prDiffSummaryCache by the
// EventGitHubPRsPolled handler on the main goroutine. Nil on
// synthetic/test events — the handler treats a nil map as empty
// (no cache writes for absent entries; eviction on MERGED/CLOSED still
// runs off the PRSnapshots slice).
PRDiffSummaries map[string]string
// EventSessionResult typed payloads. Exactly one is populated per
// event (matching the Session's Role); they coexist on Event because
// the alternative (per-role event kinds) would explode the dispatch
// table without buying clearer code.
ImplementerResult *role.ImplementerResult
ReviewerVerdict *role.ReviewerVerdict
MergeResults []gh.MergeSetResult
PlanReviewerVerdict *role.PlanReviewerVerdict
// LandingGateTail / LandingGateClass carry the captured fresh-landing-gate
// failure tail and its flake-vs-real classification from the off-main
// dispatchPlanPRMergeSet closure to the on-main applyPlanPRMergeResults
// handler (SPEC-SOR-3238-v1 R12/R13/R16/R17). Set ONLY on the merge-result
// Event a failed landing gate submits (via NewMergeResultEventWithTail);
// LandingGateTail is nil and LandingGateClass is "" on every success path and
// on a non-*PlanLandingGateFailure plumbing error (no tail to capture). The
// handler reads them to enrich the plan_pr_merge_failed message with the
// failing-test identity instead of the generic "no merge results returned"
// string, and to emit the landing_gate_flake_detected audit event when the
// class is flake.
LandingGateTail *LandingGateTail
LandingGateClass GateFailureClass
// ImplementerCycleDiffStat carries the best-effort git-diff numstat
// totals (files changed / lines added / lines removed) for an
// implementer cycle, computed off-main in the dispatch goroutine from
// the cycle's worktree branch. Set on EventSessionResult for
// implementer sessions whose stat computation succeeded; nil (→ zero
// totals in the implementer_cycle_summary event) on the no-stater /
// no-branch / git-error path and on the panic / boot-recovery paths.
ImplementerCycleDiffStat *DiffStatTotals
// ImplementerEditedFiles carries the off-main `git diff --name-only`
// union of repo-relative paths the implementer cycle actually edited,
// computed in the dispatch goroutine (submitImplementerResult) alongside
// ImplementerCycleDiffStat. Feeds the implementer_phase_discovery_deviation
// metric's "actually edited" set, compared against the discovery
// artifact's "## Files to touch" prediction. nil on the no-stater /
// git-error / panic / boot-recovery paths (→ the deviation metric sees
// zero edited files; it still won't fire unless a discovery section was
// cited, so an empty list is benign).
ImplementerEditedFiles []string
// PlanPRFixTipSHAs carries the post-fix origin plan-branch tip SHA per
// repo, probed OFF the main goroutine in the dispatchPlanPRFix session
// goroutine after the autonomous plan-PR-fix implementer returns success
// (the fix force-with-lease-pushes the plan branch, advancing the origin
// tip; role.ImplementerResult carries no SHA, so the daemon probes
// origin). applyPlanPRFixResult records it into planIss.PlanCommitSHAs
// via recordPlanBranchTip so the recorded SHA tracks the live tip by
// construction (plan-branch-tip-consistency). Set only on a successful
// continuous_plan_branch plan-PR-fix result whose probe succeeded; nil on
// every other EventSessionResult (the standard implementer path, legacy
// plan_branch fixes, the no-resolver / probe-error / panic / boot-recovery
// paths). recordPlanBranchTip no-ops on an empty SHA, so a nil/partial map
// never clobbers a previously-good recorded tip.
PlanPRFixTipSHAs map[string]string
// PlanPRResults carries the per-repo plan-PR-open outcomes for a
// "plan_pr_open" session (SOR-1139). Set on EventSessionResult when
// the session role is "plan_pr_open"; applyPlanPROpenResults reads
// it to record iss.PRURLs and transition the planning issue to
// plan_awaiting_pr_merge.
PlanPRResults []PlanPROpenResult
// PlannerResult is the planner's typed result (proposal id + any
// in-process echoes of the envelope). Set on EventSessionResult
// when the session role is "planner". Under the v2 proposals
// lifecycle, the production path populates only ProposalID — the
// envelope's children live in the issuestore and are read back
// when the supervisor needs to project them.
PlannerResult *role.PlannerResult
// PlannerValidationErrors carries validator errors that survived
// all revision attempts. nil on success.
PlannerValidationErrors []string
// PlannerNewIssueKeys carries the Linear keys created from the
// planner's output, in input order. Populated when the writer ran
// successfully (in part or in full).
PlannerNewIssueKeys []string
// PlannerOriginIssue is set when the planner ran on a replan
// trigger (TriggerDiscoveredPrereq). It identifies the issue that
// raised the discovered-prereq escalation; applyPlannerResult uses
// it to attach the new prereqs to the origin issue's DependsOn.
PlannerOriginIssue string
// SpecDrafterResult is the spec_drafter's SHAPE-validated draft (the
// extracted spec YAML + parsed *dsl.Spec). Set on EventSessionResult
// when the session role is "spec_drafter"; applySpecDrafterResult runs
// the verification pipeline over it and drives the spec lifecycle.
SpecDrafterResult *role.SpecDrafterResult
// SpecDrafterTaskKind is the dispatched task flavor ("draft" / "amend")
// the spec_drafter ran. applySpecDrafterResult reads it to emit
// spec_drafted vs spec_amended and to chain previous_version_id.
SpecDrafterTaskKind string
// SpecReviewerVerdict is the spec_reviewer's validated coherence verdict.
// Set on EventSessionResult when the session role is "spec_reviewer";
// applySpecReviewerResult reads it to auto-approve a coherent spec or route
// a drift verdict back through the bounded revise loop.
SpecReviewerVerdict *role.SpecReviewerVerdict
// EventOrphanPRsDiscovered payload.
OrphanPRs []OrphanPRRef
// IssueKey is the subject issue key for events that carry one
// (currently EventOperatorIssueMutation; previously also Linear-push
// grace / mirror-check, both removed). Most events use Mutation.IssueKey
// instead; this top-level field is retained for callers that route
// only an issue key without a full Mutation payload.
IssueKey string
// EventOperatorIssueMutation payload: the typed mutation +
// reply chan. The dispatcher reads Mutation.Kind to route to
// the right per-mutation handler.
Mutation *Mutation
// EventSteward payload (SOR-2659). StewardSubKind selects which
// init()-registered steward sub-handler handleStewardEvent invokes.
// Empty for every other event kind.
StewardSubKind string
// EventRecoverySweep payload. The handler dispatches the requested
// recovery sweeps on the main goroutine and sends the response
// envelope back through Recover.Reply.
Recover *RecoverEvent
// EventForceConvergeForTest payload. See ApplyForceConvergeForTest
// for the full rationale; the handler runs (*sm.Issue).ForceConverge
// on the main goroutine and signals the caller via Done.
ForceConvergeForTest *ForceConvergeForTestPayload
// EventTriagerActionApply payload. See ApplyTriagerAction in
// triager_executors.go for the call shape. The handler dispatches
// on TriagerAction.Kind to one of the per-action SM mutations
// (drop dep, force-converge to terminal, archive session, requeue
// issue, mark blocked, cascade orphan proposal) on the main
// goroutine and signals the caller via Reply with the outcome.
TriagerActionPayload *TriagerActionApplyPayload
// PlanKey identifies the planning issue a plan-branch event targets.
// Shared by the CIPB event family (plan-branch create, child squash,
// main-merge, plan-PR, stale-rerere sweep, assembly_fixer).
PlanKey string
// GateReadSet is the EventGateReadSetObserved payload (SPEC-SOR-2773 tier
// 1): the worktree-relative file-read set a plan-branch gate observed for
// the plan named by PlanKey. applyGateReadSetObserved writes it into
// d.state.PlanInputsReadSets[PlanKey]. Nil for other event kinds.
GateReadSet []string
// EventPlanPRPendingCIObserved payload (SOR-1360). PendingCIObservation
// carries the typed classification + per-PR diagnostic string the
// pending-CI poller derived for the plan; applyPlanPRPendingCIObserved
// routes per the classification. Nil for other event kinds.
PendingCIObservation *PlanPRPendingCIObservation
// EventActivationPollObserved payload (SOR-2503). ActivationPollObservation
// carries the activation driver's aggregate poll verdict for one plan;
// applyActivationPollObserved routes per AllSatisfied / FailedRIDs. Nil for
// other event kinds.
ActivationPollObservation *ActivationPollObservation
// EventAssemblyFixerResult payload (SOR-1443 Fix 4). AssemblyFixer
// carries the typed result + any liberal-parser coercions the
// dispatcher emitted; AssemblyFixerErr carries any transport /
// conversation / validator-exhausted error. Nil for other event
// kinds.
//
// AssemblyFixerRawPayload (SOR-1476) carries the raw JSON the agent
// submitted on the validator-reject path. Populated only when the
// dispatcher's err is a *role.AssemblyFixerParseError (i.e. the role
// returned a payload but ParseAssemblyFixerResult rejected it). Empty
// on success AND on transport errors that produced no payload to
// inspect. applyAssemblyFixerResult emits the secondary
// assembly_fixer_payload_rejected audit event only when this field is
// non-empty.
AssemblyFixer *AssemblyFixerEventPayload
AssemblyFixerErr error
AssemblyFixerRawPayload json.RawMessage
// AssemblyFixerSessID is the id of the tracked assembly_fixer session
// dispatchAssemblyFixer opened + attached for this cycle (empty for
// legacy / test events that synthesize the result without a session).
// applyAssemblyFixerResult routes it through terminateSession for every
// outcome so the plan's iss.SessionID is cleared the moment the result
// is applied — closing the plan_auto_fixing re-dispatch storm by
// construction (the pass.go follow-on guard keys off iss.SessionID).
AssemblyFixerSessID string
// AssemblyFixerTriggeringChildKey is the key of the parked child P
// whose per-child squash failure dispatched this assembly_fixer cycle.
// Populated only on the CPB-squash dispatch path
// (dispatchCPBSquashFailureToAssemblyFixer); empty on the main-merge
// (dispatchPlanMainMergeFailureToAssemblyFixer) and follow-on
// re-dispatch (pass.go) paths, which carry no triggering child. The
// apply site (applyAssemblyFixerAction) uses it to strip a
// file_implementation_issue corrective child's self-dependency on P —
// any proposed depends_on edge that reaches P directly or transitively
// — before the child persists, closing the SOR-2321 ↔ SOR-2338
// cross-graph deadlock by construction. Empty here makes the strip a
// no-op, so a legitimate dependency on the main-merge path is never
// dropped.
AssemblyFixerTriggeringChildKey string
// AssemblyFixerResolverDiagnosis is the formatted merge-resolver verdict
// dispatchAssemblyFixer loaded from the plan's latest persisted
// merge_resolver_diagnostics row on a conflict-triggered dispatch. It
// rides the result event to the apply site, where the
// file_implementation_issue arm injects it verbatim into the corrective
// child's body as a `## Resolver diagnosis` section so the downstream
// implementer starts with the files + port direction the resolver named.
// Empty on gate-only + main-merge-non-conflict + follow-on dispatches,
// making the body injection a no-op there.
AssemblyFixerResolverDiagnosis string
// EventCapabilityGapPark payload (SPEC-SOR-2923-v1 R2, SOR-3083).
// CapabilityGapPark carries the (plan, gap issue, capability signature) the
// site-3 filing goroutine resolved; applyCapabilityGapParkEvent parks the
// plan through parkPlanOnCapabilityGap. Nil for other event kinds.
CapabilityGapPark *CapabilityGapParkPayload
// EventCapabilityGapReleaseProbeResult payload (SOR-3084). CapabilityGapReleaseProbe
// carries the off-main git-ancestry probe's per-plan deployed verdict;
// applyCapabilityGapReleaseProbeResult drives the release / re-park /
// circuit-break disposition from it. Nil for other event kinds.
CapabilityGapReleaseProbe *CapabilityGapReleaseProbeResultPayload
// EventPlanBranchCreateResult payload (CIPB Phase B). PlanKey
// identifies the planning issue; PlanBranchCreate carries the
// aggregate per-repo result. Nil for other event kinds.
PlanBranchCreate *PlanBranchCreateResult
// EventCPBChildSquashResult payload (CIPB Phase B). CPBChildSquash
// carries the typed per-child-per-repo squash outcome. Nil for other
// event kinds.
CPBChildSquash *CPBChildSquashResult
// EventCPBMergeResolverResult payload (CIPB Phase B, SOR-2739).
// CPBMergeResolver carries the off-budget merge_resolver dispatch
// outcome. Nil for other event kinds.
CPBMergeResolver *CPBMergeResolverResult
// EventPlanMainMergeResult payload (CIPB Phase C).
// PlanMainMergeData carries the typed per-repo periodic-main-merge
// outcome — populated on EventPlanMainMergeResult, nil for every
// other event kind. PlanKey at the top-level Event identifies the
// planning issue the merge sweep targeted.
PlanMainMergeData *PlanMainMergeResultEvent
// EventPlanMainMergeTipProbeResult payload (CIPB Phase C, SOR-2187).
// PlanMainMergeTipProbeData carries the off-main origin/<default> tip
// probe's per-repo result — populated on EventPlanMainMergeTipProbeResult,
// nil for every other event kind. No PlanKey: a single probe covers the
// union of every in-flight CIPB plan's repos, and the result handler maps
// changed repos back to the plans that cover them.
PlanMainMergeTipProbeData *PlanMainMergeTipProbeResultEvent
// EventMainMergeRelevanceResult payload (CIPB Phase C, SOR-2876).
// MainMergeRelevanceData carries the off-main-computed per-plan relevance
// verdict for one interim main-merge trigger (the completion fan-out or the
// tip-change probe, discriminated by the payload's Trigger field) —
// populated on EventMainMergeRelevanceResult, nil for every other event
// kind. No top-level PlanKey: a single relevance pass covers every eligible
// in-flight CIPB plan, and the handler maps relevant / deferred keys back to
// the plans.
MainMergeRelevanceData *MainMergeRelevanceResultEvent
// EventPlanPRReadyFlipResult payload (CIPB Phase C AC6).
// PlanPRReadyFlipResults carries the per-repo flip outcome; nil
// for other event kinds. PlanKey at the top-level Event
// identifies the planning issue. The reused
// PlanPROpenResult-shaped slice is shared with the legacy plan-
// PR-open session shape because the per-repo outcome surface
// (Repo / URL / Err) is identical — only the meaning differs
// (URL on success is the still-set existing PR URL, not a
// newly-opened one).
PlanPRReadyFlipResults []PlanPROpenResult
// PlanBranchRecreateResults carries the per-repo outcome of the
// recovery-walk plan-branch self-heal dispatch
// (maybeDispatchRecreatePlanBranch). Set on EventPlanBranchRecreateResult;
// nil for other event kinds. PlanKey at the top-level Event identifies
// the planning issue.
PlanBranchRecreateResults []PlanBranchRecreateRepoResult
// PerChildPROpenResults carries the per-repo outcome of a
// "per_child_pr_open" session: the daemon-side per-child PR-open
// the verifier chain gates AFTER the implementer pushes the branch
// (the load-bearing reason for moving `gh pr create` into the
// daemon — a red chain leaves no stale PR behind on GitHub).
// Reuses the PlanPROpenResult shape (Repo / URL / Err) because the
// per-repo outcome surface is identical to the plan-PR-open case;
// applyPerChildPROpenResults differentiates by the session role.
PerChildPROpenResults []PlanPROpenResult
// PerChildPRSummary is the implementer-supplied summary the
// daemon-side per-child PR-open propagates from the
// `dispatchPerChildPROpen` call site to the `applyPerChildPROpenResults`
// main-loop handler so the in_review transition comment renders the
// same `### Summary` section it used to under the LLM-driven
// PR-open shape. Set on the EventSessionResult emitted by the
// per_child_pr_open session; ignored for every other event kind.
PerChildPRSummary string
// PerChildRequirementTraceJSON is the implementer's raw
// requirement_trace JSON the daemon-side per-child PR-open propagates
// from the `dispatchPerChildPROpen` call site to the
// `applyPerChildPROpenResults` main-loop handler so the in_review
// transition seats the structured requirement→code trace on the issue
// row (IssueTransitionEffects.SetRequirementTrace). Set on the
// EventSessionResult emitted by the per_child_pr_open session; empty /
// ignored for every other event kind and for issues that traced
// nothing.
PerChildRequirementTraceJSON string
// PerChildVerifiedSpecVersion is the plan's current spec-version ID the
// daemon-side per-child PR-open propagates from the
// `dispatchPerChildPROpen` call site to the `applyPerChildPROpenResults`
// main-loop handler so the in_review transition seats the re-gate stamp
// on the issue row (IssueTransitionEffects.SetVerifiedAgainstSpecVersion,
// SPEC-SOR-2945-v3 R9). The stamp is resolved on the main goroutine at
// dispatch time (currentPlanSpecVersion reads d.state) and captured in
// the session closure. Set on the EventSessionResult emitted by the
// per_child_pr_open session; empty / ignored for every other event kind
// and for a child with no spec-bearing planning parent.
PerChildVerifiedSpecVersion string
// VerifyChain is the EventImplementerVerifyChainResult payload —
// the aggregate outcome of running the implementer verifier chain
// off-main. Nil for other event kinds.
VerifyChain *ImplementerVerifyChainResult
// ForwardRefLint is the EventForwardRefLintResult payload — the
// aggregate outcome of running the forward-referencing-paths
// predispatch lint off-main. Nil for other event kinds.
ForwardRefLint *ForwardRefLintResult
// SpecVerificationResult is the EventSpecVerificationResult payload —
// the outcome of running the spec SMT verification off-main. Nil for
// other event kinds.
SpecVerificationResult *SpecVerificationResultEvent
// BlockedUserPROpenResult is the EventBlockedUserPROpenResult payload —
// the per-issue outcome of running the blocked_user PR-open probes
// off-main. Nil for other event kinds.
BlockedUserPROpenResult *BlockedUserPROpenResultEvent
// CrashRecoveryPRDiscoveryResult is the EventCrashRecoveryPRDiscoveryResult
// payload — the outcome of running the crash-before-marker PR-set recovery
// discovery off-main. Nil for other event kinds.
CrashRecoveryPRDiscoveryResult *CrashRecoveryPRDiscoveryResultEvent
// PickResult is the EventPickResult payload — the outcome of running the
// scheduler Pick (dependency-graph build + ready-leaf selection) off-main.
// Nil for other event kinds.
PickResult *PickResultEvent
}
Event is one item the main loop's `select` consumes. Fields are unioned — only the ones relevant to the Kind are populated. Discriminated by Kind; the dispatcher reads the appropriate field.
At fields are wall-clock timestamps stamped by the producer, used for latency metrics and audit-trail correlation.
func NewActivationPollObservedEvent ¶
func NewActivationPollObservedEvent(planKey string, allSatisfied bool, failedRIDs []string, evidenceSummary, reason string, now time.Time) Event
NewActivationPollObservedEvent constructs the typed observation event the SOR-2503 activation driver submits to the main loop after one poll of a plan's activation probes.
func NewAssemblyFixerResultEvent ¶
func NewAssemblyFixerResultEvent(planKey, sessID, triggeringChildKey, resolverDiagnosis string, result role.AssemblyFixerResult, coercions []string, err error) Event
NewAssemblyFixerResultEvent constructs the typed result event the assembly-fixer dispatch goroutine submits to the main loop (SOR-1443 Fix 4). result + coercions are the dispatcher's typed reply (the post-parse AssemblyFixerResult and the per-coercion descriptors); err is the transport / conversation / validator-exhausted error (nil on the happy path).
SOR-1476: when err is a *role.AssemblyFixerParseError, the rejected raw payload is lifted onto the event's AssemblyFixerRawPayload field so applyAssemblyFixerResult can emit the secondary assembly_fixer_payload_rejected audit event without re-parsing the error chain. Transport-only errors leave the field nil — the new event kind is reserved for "we received a payload but it was schema- invalid".
sessID is the tracked assembly_fixer session dispatchAssemblyFixer opened + attached for this cycle; applyAssemblyFixerResult terminates it (clearing the plan's iss.SessionID) for every outcome. A blank sessID is valid — terminateSession is a no-op on an empty id.
triggeringChildKey is the parked child P whose per-child squash failure dispatched this cycle (the CPB-squash path); it is carried onto the event's AssemblyFixerTriggeringChildKey field so the apply site can strip a corrective child's self-dependency on P. It is empty on the main-merge and follow-on re-dispatch paths, which carry no triggering child, making the strip a no-op there.
resolverDiagnosis is the formatted merge-resolver verdict the dispatch loaded for a conflict-triggered cycle; it rides onto the event's AssemblyFixerResolverDiagnosis field so the apply site can inject it into the corrective child's body. Empty on every non-conflict dispatch.
func NewCapabilityGapReleaseProbeResultEvent ¶
func NewCapabilityGapReleaseProbeResultEvent(results []CapabilityGapReleaseProbePlanResult) Event
NewCapabilityGapReleaseProbeResultEvent wraps the off-main release probe's per-plan deployed verdicts as an EventCapabilityGapReleaseProbeResult. The wall-clock At field is stamped by the producer; the handler stamps audit events from it.
func NewCapabilityGapReleaseSweepEvent ¶
func NewCapabilityGapReleaseSweepEvent() Event
NewCapabilityGapReleaseSweepEvent constructs an EventCapabilityGapReleaseSweep — the SOR-3084 deploy-gated release sweep tick the poller fires. No payload; the sweep handler reads d.state + the issuestore directly. The wall-clock At field is stamped by the producer for latency observability.
func NewGitHubPRsPolledEvent ¶
func NewGitHubPRsPolledEvent(snapshots ...PRSnapshot) Event
NewGitHubPRsPolledEvent fires after a successful GitHub PR poll.
func NewImplementerResultEvent ¶
func NewImplementerResultEvent(sessionID string, result role.ImplementerResult, sessionError error) Event
NewImplementerResultEvent wraps a typed implementer outcome as a SessionResult event. Used by the supervisor's goroutines to feed results back to the main loop.
func NewMainMergeRelevanceResultEvent ¶
NewMainMergeRelevanceResultEvent wraps the off-main relevance goroutine's per-plan verdict as an EventMainMergeRelevanceResult. The wall-clock At field is stamped by the producer; the handler stamps audit events from it.
func NewMergeResultEvent ¶
func NewMergeResultEvent(sessionID string, results []gh.MergeSetResult, sessionError error) Event
NewMergeResultEvent wraps the per-repo merge outcomes.
func NewMergeResultEventWithTail ¶
func NewMergeResultEventWithTail(sessionID string, tail *LandingGateTail, class GateFailureClass) Event
NewMergeResultEventWithTail wraps an EMPTY merge-result set produced by a failed fresh landing gate (SPEC-SOR-3238-v1): MergeResults is nil (the plan did not land) and the captured failure tail + flake-vs-real classification ride to the on-main handler. tail may be nil (a non-*PlanLandingGateFailure plumbing error left nothing to capture); class is always set by the dispatch path's re-run classification. applyPlanPRMergeResults's empty-results guard then surfaces the tail on the plan_pr_merge_failed event (R17) and emits the landing_gate_flake_detected audit event on a flake (R16).
func NewOperatorCommandEvent ¶
NewOperatorCommandEvent constructs an EventOperatorCommand. reply may be nil for fire-and-forget commands; if non-nil, the handler sends a nil-or-error to it once the command has been applied.
func NewOperatorIssueMutationEvent ¶
NewOperatorIssueMutationEvent wraps a typed Mutation as an event. The reply channel must be set on the Mutation by the caller (the daemon-side ApplyOperator* methods own the timeout shape).
func NewOrphanPRsDiscoveredEvent ¶
func NewOrphanPRsDiscoveredEvent(refs []OrphanPRRef) Event
NewOrphanPRsDiscoveredEvent constructs the event the orphan-PR poller emits. Empty refs is permitted (and is the success path on "no orphans found"); the handler is a no-op in that case.
func NewPerChildPROpenResultEvent ¶
func NewPerChildPROpenResultEvent(sessionID, summary, requirementTraceJSON, verifiedSpecVersion string, results []PlanPROpenResult, sessionError error) Event
NewPerChildPROpenResultEvent wraps the per-repo per-child-PR-open outcomes for the daemon-side per_child_pr_open session. Reuses the PlanPROpenResult shape because the per-repo outcome surface is identical; applyPerChildPROpenResults differentiates by the session role. summary is the implementer's r.Summary propagated to the in_review transition comment; requirementTraceJSON is the implementer's raw requirement_trace JSON propagated to the in_review SetRequirementTrace effect; verifiedSpecVersion is the plan's current spec-version ID propagated to the in_review SetVerifiedAgainstSpecVersion re-gate stamp.
func NewPerIssueWorktreeGCSweepEvent ¶
func NewPerIssueWorktreeGCSweepEvent() Event
NewPerIssueWorktreeGCSweepEvent constructs an EventPerIssueWorktreeGCSweep — the periodic per-issue worktree GC backstop sweep tick the poller fires (SOR-2817). No payload; the sweep handler reads d.state directly. The wall-clock At field is stamped by the producer for latency observability.
func NewPlanAssembleStaleRerereSweepEvent ¶
func NewPlanAssembleStaleRerereSweepEvent() Event
NewPlanAssembleStaleRerereSweepEvent constructs an EventPlanAssembleStaleRerereSweep. The periodic poller fires this fire-and-forget; the handler runs sweepPlanAssembleStaleRerere on the main goroutine. No payload — the sweep walks d.state.Issues directly and reads the events table for the rerere-replay signature.
func NewPlanMainMergeSweepEvent ¶
func NewPlanMainMergeSweepEvent() Event
NewPlanMainMergeSweepEvent constructs an EventPlanMainMergeSweep — the periodic-merge sweep tick the CIPB Phase C poller fires. No payload; the sweep handler reads d.state directly. The wall-clock At field is stamped by the producer for latency observability.
func NewPlanMainMergeTipProbeResultEvent ¶
NewPlanMainMergeTipProbeResultEvent wraps the off-main tip-probe's per-repo outcome as an EventPlanMainMergeTipProbeResult. The wall-clock At field is stamped by the producer; the handler stamps audit events from it.
func NewPlanPROpenResultEvent ¶
func NewPlanPROpenResultEvent(sessionID string, results []PlanPROpenResult, sessionError error) Event
NewPlanPROpenResultEvent wraps the per-repo plan-PR-open outcomes.
func NewPlanPRPendingCIObservedEvent ¶
func NewPlanPRPendingCIObservedEvent(planKey string, class pendingCIClassification, reason string, now time.Time, timeout time.Duration, failedChecks []FailedPlanPRCheck, stallChecks []FailedPlanPRCheck) Event
NewPlanPRPendingCIObservedEvent constructs the typed observation event the SOR-1360 pending-CI poller submits to the main loop after a poll. failedChecks carries the per-failure detail when class is pendingCIClassFailedCheck (SOR-1366); stallChecks carries the (repo, head-SHA) re-dispatch targets when class is pendingCIClassStillPending (SOR-3152). Both are nil/empty for every other class.
func NewPlanReviewerResultEvent ¶
func NewPlanReviewerResultEvent(sessionID string, verdict role.PlanReviewerVerdict, sessionError error) Event
NewPlanReviewerResultEvent wraps the plan_reviewer's verdict.
func NewPlannerResultEvent ¶
func NewPlannerResultEvent(sessionID, originIssue string, result role.PlannerResult, errs []string, newKeys []string, sessionError error) Event
NewPlannerResultEvent wraps the planner pipeline's outcome. errs is the surviving validator errors (nil on success); newKeys is the Linear keys created (may be partial if writeErr is set); writeErr is any error returned from the LinearIssueWriter; originIssue is set when the planner ran on TriggerDiscoveredPrereq (it identifies the issue whose DependsOn should be updated to point at the new prereqs).
func NewRecoverySweepEvent ¶
func NewRecoverySweepEvent(kinds []string, reply chan<- httpapi.RecoverResponse) Event
NewRecoverySweepEvent constructs an EventRecoverySweep with the given kinds filter and reply channel. The caller buffers Reply so a slow main loop doesn't block the producer.
func NewRequestEvent ¶
NewRequestEvent constructs an EventRequestArrived for a request file.
func NewReviewerResultEvent ¶
func NewReviewerResultEvent(sessionID string, verdict role.ReviewerVerdict, sessionError error) Event
NewReviewerResultEvent wraps a typed reviewer verdict.
func NewScheduleEvalEvent ¶
func NewScheduleEvalEvent() Event
NewScheduleEvalEvent is fired by the debouncer goroutine onto the main event channel after its quiet window expires. The main goroutine handles it by running SchedulerPass — keeping d.state single-writer.
func NewSessionCrashedEvent ¶
NewSessionCrashedEvent fires when the dead-claude watchdog (SOR-51) observes a clauderunner-tracked subprocess that has exited without emitting a terminal session-result. lastMessage, when non-empty, carries a short forensic line (typically the tracked subprocess's role + pgid). The handler routes the session through the same timed_out / rollback path applySessionTimeout uses for the max-age case.
func NewSessionInitFailedEvent ¶
func NewSessionInitFailedEvent(f SessionInitFailureEvent) Event
NewSessionInitFailedEvent carries a clauderunner session-init-failure report from the runner's OnSessionInitFailed hook back onto the daemon's main loop. SessionID / IssueKey are mirrored onto the top-level Event fields so the slow-handler instrumentation can derive a subject.
func NewSessionLifecycleEvent ¶
NewSessionLifecycleEvent carries a subprocess-lifecycle crossing (phase = "starting" | "running") from a clauderunner reader goroutine back onto the daemon's main loop. stateDir is the runner's per-session state directory; the handler records it on the Session so the heartbeat watchdog can stat <stateDir>/heartbeat.
WARNING for test callers: the At field is stamped with time.Now().UTC() because the runner reader goroutines that produce this event in production have no fake-clock context. applySessionLifecycle consumes At as the SM-transition timestamp, which propagates to Session.LastActivityAt. Tests that pin a fake clock with time.Date(…) and then assert against the heartbeat-sweep promotion gate (mt.After(LastActivityAt) in recover_sweep.go) must overwrite the wall-clock stamp by calling sess.TouchActivity(now) after dispatch, or the wall-clock LastActivityAt will mask the fake-clock heartbeat mtime once wall-clock now crosses the fake-clock heartbeat instant.
func NewSessionSpawnFailedEvent ¶
func NewSessionSpawnFailedEvent(f SpawnFailureEvent) Event
NewSessionSpawnFailedEvent carries a clauderunner spawn-failure report from the runner's OnSpawnFailed hook back onto the daemon's main loop. SessionID / IssueKey are mirrored onto the top-level Event fields so the slow-handler instrumentation can derive a subject.
func NewSessionTimeoutEvent ¶
NewSessionTimeoutEvent fires when a per-session AfterFunc trips.
func NewShutdownEvent ¶
func NewShutdownEvent() Event
NewShutdownEvent fires on SIGTERM/SIGINT. Triggers graceful drain.
func NewSpecDrafterResultEvent ¶
func NewSpecDrafterResultEvent(sessionID, taskKind string, result role.SpecDrafterResult, sessionError error) Event
NewSpecDrafterResultEvent wraps the spec_drafter's SHAPE-validated draft (or a session error). taskKind is the dispatched flavor ("draft" / "amend"); the session's IssueKey identifies the planning issue, so — unlike the planner — no origin-issue field is needed.
func NewSpecReviewerVerdictEvent ¶
func NewSpecReviewerVerdictEvent(sessionID string, verdict role.SpecReviewerVerdict, sessionError error) Event
NewSpecReviewerVerdictEvent wraps the spec_reviewer's validated coherence verdict (or a session error). The session's IssueKey identifies the planning issue whose spec was coherence-checked.
func NewStewardEvent ¶
NewStewardEvent constructs an EventSteward routed to the steward sub-handler registered for subKind (SOR-2659). handleStewardEvent looks up stewardHandlers[subKind] on the main goroutine; an unregistered sub-kind is logged and dropped (default-autonomous — no escalation).
func NewStewardReactiveWakeEvent ¶
func NewStewardReactiveWakeEvent() Event
NewStewardReactiveWakeEvent constructs the EventSteward for the debounced reactive wake (SOR-2667). Exported so cmd/sorcererd/start.go can wire the blocked-cohort routing hook (SetTriagerNotifyBlocked) to the steward without referencing the unexported stewardSubKindWakeReactive constant.
type EventKind ¶
type EventKind int
EventKind enumerates the things that can happen to the daemon. Used as a tag for routing and for logging.
const ( // EventSessionResult fires when an agent session terminates. The // supervisor flips the Session SM; downstream handlers may flip the // Issue SM and trigger a scheduler pass. EventSessionResult EventKind = iota // EventRequestArrived fires when a new file appears in // <project>/.sorcerer/requests/. The daemon dispatches to the // planner role. EventRequestArrived // EventOperatorCommand fires on a command from the HTTP control // plane (sorcererd freeze, replan, etc.). Payload is the command // line. EventOperatorCommand // EventGitHubPRsPolled fires after the GitHub PR poller pulls fresh // state for in-flight issues. Triggers a scheduler pass to surface // any `mergeable→CLEAN` or `state→MERGED` transitions. EventGitHubPRsPolled // EventSessionTimeout fires when a per-session AfterFunc trips. The // supervisor flips the Session SM to timed_out and may quarantine // the conversation depending on policy. EventSessionTimeout // EventShutdown fires on SIGTERM/SIGINT. Begins graceful drain. EventShutdown // EventScheduleEval fires when the debouncer's quiet window expires // after one or more scheduler-relevant triggers. The handler runs // SchedulerPass on the main goroutine — preserving the single-writer // invariant on d.state. EventScheduleEval // EventOrphanPRsDiscovered carries the orphan-PR poller's findings. // The handler synthesizes one Issue per ref with OrphanAdopted=true // so the reviewer + merge gate picks them up. EventOrphanPRsDiscovered // EventOperatorIssueMutation carries a typed operator-driven // mutation (transition / comment / blocks / cancel / create) onto // the main loop. The handler dispatches on the embedded Mutation // payload and, where applicable, commits the SM update + history // row + (optional) comment row in one issuestore transaction. // // Distinct from EventOperatorCommand: that event's payload is a // stringified prefix form (`replan:<key>:<reason>`) which doesn't // extend cleanly to typed multi-field mutations. Operator-driven // freeze / unfreeze / stop / replan stay on the legacy event; // mutation endpoints added in this PLAN ride this one. EventOperatorIssueMutation // EventRecoverySweep fires when the HTTP /v1/recover endpoint or the // periodic recovery-sweep poller asks the daemon to re-run the // boot-time autonomous-unblock sweeps without restarting. The // handler runs on the main goroutine, exercising the same // sweepBlockedUserPROpen / sweepStuckAwaitingChildren paths the boot // recovery uses, and replies through the payload's Reply chan with // the per-issue records the HTTP envelope or audit-trail row needs. EventRecoverySweep // EventSessionCrashed fires when the dead-claude watchdog (SOR-51) // observes that a clauderunner-tracked subprocess has exited without // emitting a terminal session-result. The handler walks the Session // SM forward to timed_out and rolls the owning issue back through // the same path applySessionTimeout uses, with a distinct // escalation Rule / audit-event kind so operator dashboards can // distinguish a dead-subprocess recovery from a wall-clock // max-age timeout. The watchdog and the max-age timer are // independent backstops covering disjoint failure modes (process // state vs. wall-clock-elapsed). EventSessionCrashed // EventForceConvergeForTest dispatches an out-of-band ForceConverge // onto the daemon's main goroutine. Test-only: integration tests // occasionally need to pin a fixture issue at a state the in-band // transition table cannot reach directly (e.g. implementing → // merged), and doing so from the test goroutine while d.Run is // active violates the single-writer invariant from CLAUDE.md § // "Design invariants". Routing through the event channel restores // the invariant: ForceConverge runs on the same goroutine that owns // every other write to d.state. See ApplyForceConvergeForTest. EventForceConvergeForTest // EventTriagerActionApply dispatches one autonomy-safe triager action // onto the daemon's main goroutine. The executor (triager_executors.go) // writes the triager_decisions row first, then submits this event for // the SM mutation. The dispatcher branches on TriagerActionPayload.Kind // to call the right per-action handler under the single-writer // invariant; on completion the handler signals through Reply with the // outcome / err. Production-only — tests typically wire the executor // against a fake daemon that bypasses this event. EventTriagerActionApply // EventSessionLifecycle carries a clauderunner subprocess-lifecycle // crossing (SessionPhase = "starting" | "running", SessionStateDir = // the runner's per-session state dir) back onto the main goroutine. // The runner fires the hook from a reader goroutine; the handler // (applySessionLifecycle) advances the Session SM under the // single-writer invariant so the sessions table tracks the // subprocess's real progress instead of sitting at pending until a // terminal event. EventSessionLifecycle // EventSessionSpawnFailed carries a clauderunner spawn-failure report // (SpawnFailure payload) back onto the main goroutine. The runner // fires the OnSpawnFailed hook when a dispatch fails before the // `claude` subprocess starts; applySessionSpawnFailed records a // per-error-class session_spawn_failed audit row so an operator can // later query WHY a dispatch ghosted. Pure telemetry — the handler // does NOT transition the Session SM (SOR-1154's dispatch-side // session_aborted emission owns the SM-level transition). EventSessionSpawnFailed // EventSessionInitFailed carries a clauderunner session-init-failure // report (SessionInitFailureEvent payload) back onto the main // goroutine (SOR-1265). The runner fires the OnSessionInitFailed // hook when the `claude` subprocess starts but exits non-zero at // session init (stream-json `result` frame reports `is_error: true` // with `num_turns <= 1`); applySessionInitFailed records a // `clauderunner_session_init_failed` audit row carrying the // distinguishable reason field. Pure telemetry — the typed // *agentcli.SessionInitError returned synchronously from // runner.Run still drives the supervisor's permanent / transient // classification (isTransientSessionError returns false for it). EventSessionInitFailed // EventPlanPRPendingCIObserved carries the SOR-1360 pending-CI // poller's per-plan classification (all-clean / still-pending / // failed-check / conflict / unknown) back onto the main goroutine. // applyPlanPRPendingCIObserved routes the plan per the classification // — including dispatching MergeSet on the all-clean branch, which is // the action boundary at which the daemon re-evaluates whether the // plan PR is actually safe to merge. EventPlanPRPendingCIObserved // EventPlanAssembleStaleRerereSweep fires the periodic sweep that // recovers planning issues parked in plan_blocked because the one-shot // assembler's `git merge` auto-replayed a stale rerere resolution out // of the bare clone's `rr-cache`. The handler walks every plan_branch // planning issue in plan_blocked whose latest plan_assembly_conflict // event message contains the `using previous resolution` signature, // clears the per-(repo) rerere cache under the plan-branch mutation // mutex (so it never races against a concurrent main-merge / squash / // assembly), and re-arms the plan to the retired one-shot-assembler's // re-entry state via the ForceConvergePlan escape hatch (retired-model // note: that assembler-era state name is unrelated to the live CIPB // integration-window plan_integrating state) — the same Force:true shape // applyPlanExternalMergeRecovery uses. One plan_assembly_stale_rerere_cleared // audit row is emitted per repo + one plan_assembly_stale_rerere_recovered // audit row attribution event lands with the transition. EventPlanAssembleStaleRerereSweep // EventAssemblyFixerResult carries the typed result of one // assembly_fixer dispatch back to the main loop (SOR-1443 Fix 4). // applyAssemblyFixerResult applies each emitted action through the // matching mutation surface (file_implementation_issue → add- // children primitive; refer_back_child → reviewer-style feedback // transition; escalate_capability_gap → sorcerer-side planning-issue // create). On status=failed or transport error, the outer-retry // counter increments; on outer-budget exhaustion the daemon // synthesizes a capability-gap escalation and routes the plan to // plan_blocked. Payload: AssemblyFixer.Result + AssemblyFixer.Coercions // + AssemblyFixerErr. EventAssemblyFixerResult // EventPlanBranchCreateResult carries the per-repo outcome of one // dispatchPlanBranchCreate invocation (CIPB Phase B). The handler // records the per-repo "plan branch exists in this repo" sentinel on // the planning issue's PlanCommitSHAs and emits per-repo // plan_branch_created / plan_branch_creation_failed audit events. // PlanKey identifies the planning issue; PlanBranchCreate carries the // aggregate per-repo result. Nil for other event kinds. EventPlanBranchCreateResult // EventCPBChildSquashResult carries the per-child-per-repo outcome of // one spawnMergeChildIntoPlanBranch invocation (CIPB Phase B). The // handler routes per the outcome: // - All repos succeeded → child transitions merging → merged, the // plan-branch SHAs are recorded on iss.PlanCommitSHAs, and the // next scheduler pass fires dispatchPlanPR if every materialized // child is now shipped. // - Conflict (merge_resolver exhausted) → child transitions merging // → feedback with the merge_resolver concerns as the synthetic // refer-back. // - Gate failure → child transitions merging → feedback with the // gate output as the refer-back context (Phase B simplification; // full assembly_fixer-at-per-child is deferred). // - Generic plumbing failure → child transitions merging → // blocked_user with an operator escalation. // CPBChildSquash carries the typed result; nil for other event kinds. EventCPBChildSquashResult // EventCPBMergeResolverResult carries the outcome of one off-budget // merge_resolver dispatch the daemon spawned on a per-child squash // conflict (SOR-2739). The conflict arm of applyCPBChildSquashResult // carves a fresh conflicted worktree and dispatches the merge_resolver on // its OWN session budget (decoupled from the squash episode's // childSquashRepoTimeout), then posts this event. The handler // applyMergeResolverResult routes per the outcome: // - Success → records the durable CPBResolutionRecordedAt marker and // re-dispatches a fresh git-only finalize (re-squash whose git rerere // auto-replays the recorded resolution → gate → push). // - Failure → escalates through escalateSubject (the resolver gave up; // no further autonomous retry). // CPBMergeResolver carries the typed result; nil for other event kinds. EventCPBMergeResolverResult // EventPlanMainMergeSweep is the CIPB Phase C main-merge sweep // tick. The poller fires this fire-and-forget; under the // event-triggered model (SOR-2187) sweepPlanMainMerge no longer // applies a cadence gate — it spawns ONE off-main // origin/<default-branch> tip-change probe over the union of every // in-flight cpb plan's repos. The probe's result event // (EventPlanMainMergeTipProbeResult) dispatches dispatchPlanMainMerge // only for plans whose default-branch tip actually advanced. No // payload — the sweep reads d.state.Issues and writes // d.state.MainTipProbeInFlight directly. EventPlanMainMergeSweep // EventPlanMainMergeResult carries the typed outcome of one // dispatchPlanMainMerge invocation across every repo of a // continuous_plan_branch planning issue (CIPB Phase C). The handler // applyPlanMainMergeResult clears PlanMainMergeInFlight, records // per-repo success/failure on iss.LastMainMergeTimes, increments / // resets MainMergeConsecutiveFailures, and routes the plan to // plan_blocked when the M=3 threshold trips. PlanMainMergeData // carries the typed result; nil for other event kinds. EventPlanMainMergeResult // EventPlanPREarlyDraftOpenResult carries the per-repo outcome of // one dispatchEarlyDraftPlanPR invocation (CIPB Phase C AC5). The // handler applyPlanPREarlyDraftOpenResult records iss.PRURLs[repo] // per successful open and emits per-repo audit events; failures // stay absent from PRURLs so the next child-squash cycle retries. // PlanKey identifies the planning issue; PlanPRResults carries the // per-repo open outcomes (shape identical to the legacy // plan_pr_open session's payload — reused so the dispatch shape is // uniform). EventPlanPREarlyDraftOpenResult // EventPlanPRReadyFlipResult carries the per-repo outcome of one // flipPlanPRToReady invocation (CIPB Phase C AC6). The handler // applyPlanPRReadyFlipResult transitions the plan // plan_awaiting_children → plan_awaiting_pr_merge on full success // and dispatches the final reviewer cycle; on partial failure it // routes the plan to plan_blocked with an operator escalation. // PlanKey identifies the planning issue; PlanPRReadyFlipResults // carries the per-repo flip outcomes. EventPlanPRReadyFlipResult // EventPlanBranchRecreateResult carries the per-repo outcome of the // recovery-walk plan-branch self-heal (maybeDispatchRecreatePlanBranch) // for a session-less plan_awaiting_pr_merge continuous_plan_branch plan. // The handler applyPlanBranchRecreateResult clears // PlanBranchRecreateInFlight, routes the plan to plan_blocked when any // repo's recreate push failed (plan_branch_externally_deleted), and // otherwise re-dispatches the final-review/merge step the recovery walk // deferred. PlanKey identifies the planning issue; // PlanBranchRecreateResults carries the per-repo outcomes. EventPlanBranchRecreateResult // EventImplementerVerifyChainResult carries the aggregate outcome // of running the daemon-owned implementer verifier chain off-main // against the implementer's freshly-pushed branch. The chain MUST // run off-main because each verifier may shell out to long-running // gates (pre-push-gates.sh, full test suites, etc.) that would // otherwise block the daemon's main loop for minutes per // implementer success, freezing every other handler. The handler // applyImplementerVerifyChainResult records the per-verifier audit // events, emits the chain-level outcome event, and routes the // implementer-result continuation: on chain green it picks up // where applyImplementerResult left off (per_child PR-open OR // applyImplementerDeferredChildResult per branch model); on chain // red it projects the failing verifier(s) into synthetic // ReviewConcerns and routes to feedback / blocked_user via the // existing refer-back / exhausted machinery. VerifyChain carries // the typed result; nil for other event kinds. EventImplementerVerifyChainResult // EventForwardRefLintResult carries the outcome of the async // forward-referencing-paths predispatch lint. The lint MUST run // off-main because it shells out to `git cat-file -e // refs/heads/main:<path>` per cited path per ready issue // (SOR-1446 behavior), and the synchronous shape blocked the // scheduler tick for seconds when a planner emitted a cohort of // ready leaves (observed: 7.0s schedule_eval after a 12-child // plan landed). The handler applyForwardRefLintResult clears // d.state.ForwardRefLintInFlight[key], emits one // predispatch_lint_finding audit event when any path is missing, // and on a clean result directly dispatches the implementer (the // issue is already in ready; waiting for the next scheduler tick // would add unnecessary latency). On a flagged result the issue // stays in ready with the lint findings cached so the operator // can amend; a future amend re-runs the lint by clearing the // in-flight marker via the operator-mutate handler. ForwardRefLint // carries the typed result; nil for other event kinds. EventForwardRefLintResult // EventPlanMainMergeTipProbeResult carries the per-repo result of the // CIPB Phase C off-main origin/<default-branch> tip-change probe // (SOR-2187). sweepPlanMainMerge spawns the probe (it MUST run off-main // because it shells out to `git ls-remote` per in-flight repo, which the // single-writer main loop cannot block on); applyPlanMainMergeTipProbeResult // clears MainTipProbeInFlight, compares each probed tip against // d.state.LastObservedMainTips, records advances, and dispatches a // main-merge ONLY for in-flight CIPB plans whose repos' tip changed — // the external-push fallback to the daemon's own merge-to-main fan-out. // PlanMainMergeTipProbeData carries the typed result; nil for other // event kinds. EventPlanMainMergeTipProbeResult // EventSpecVerificationResult carries the outcome of the async spec SMT // verification run off-main (SOR-2272). The verification MUST run // off-main because the SpecVerifier shells out to a Z3 subprocess that // can take up to its 30s timeout per run; the synchronous inline shape // froze the single-writer main loop for that whole window (the // `ERROR slow handler: kind=session_result ... dur_ms≈30000` symptom), // blocking every other event. The handler applySpecVerificationResult // clears d.state.SpecVerifyInFlight[key], re-reads the issue under a // state guard, and routes by site discriminator — lifecycle: persist // metrics + findings + transition to spec_revising (error findings) or // spec_review (clean); operator-diagnostic: delete + re-persist findings // only, no metrics, no transition. SpecVerificationResult carries the // typed payload; nil for other event kinds. EventSpecVerificationResult // EventBlockedUserPROpenResult carries the outcome of the off-main // blocked_user PR-open probe (SOR-2448). The probe MUST run off-main // because sweepBlockedUserPROpen calls the GitHub PRSetDiscoverer hook // once per blocked_user issue and each call is a network round-trip; the // synchronous inline shape froze the single-writer main loop for the // aggregate of those round-trips. spawnOrSkipBlockedUserPROpen snapshots // the blocked_user candidates on the main loop, runs the probes on a // session goroutine, and Submit()s this result; the handler // applyBlockedUserPROpenResult clears d.state.BlockedUserPROpenInFlight, // re-reads each issue under a state guard, and applies the autonomous // unblock decision (evaluateBlockedUserUnblock) on the main loop — // mirroring the EventSpecVerificationResult probe-then-mutate pattern. // BlockedUserPROpenResult carries the typed payload; nil for other event // kinds. EventBlockedUserPROpenResult // EventInvariantsCheckComplete signals that the off-main invariants walk // (runInvariantsCheck) finished, so the main loop can clear the daemon-wide // InvariantsCheckInFlight re-entrancy marker (SOR-2448). The read-only walk // itself writes no d.state — it emits its violation / heartbeat audit rows // through the goroutine-safe d.appendEvent path — so the ONLY d.state touch // the off-main path used to make was clearing this marker, which the worker // previously did off-main and raced the main-loop guard read. The walk now // Submit()s this event from a deferred call (so the marker is cleared even // if the walk panics) and applyInvariantsCheckComplete clears the marker on // the main loop, keeping every d.state write on-main — mirroring how // applySpecVerificationResult / applyBlockedUserPROpenResult clear their // in-flight markers. Carries no payload; the marker is daemon-wide. EventInvariantsCheckComplete // EventCrashRecoveryPRDiscoveryResult carries the outcome of the off-main // crash-before-marker PR-set recovery discovery (SOR-2449). When an // implementer session ends with a session error but the issue is mid- // implementation with a branch + repos set, the durable work may already be // on GitHub even though the session never emitted IMPLEMENT_OK. The recovery // runs the PRSetDiscoverer GitHub hook — a 30s-timeout network round-trip — // which MUST run off-main because the synchronous inline shape froze the // single-writer main loop for ~4-5s per crash-recovery inside the // session_result handler. spawnOrSkipCrashRecoveryPRDiscovery snapshots the // branch + repos + the on-main transient classification, runs the probe on a // session goroutine, and Submit()s this result; the handler // applyCrashRecoveryPRDiscoveryResult clears // d.state.CrashRecoveryPRDiscoveryInFlight[key], re-reads the issue under a // state guard, and applies the recovered-PR-set -> in_review transition (or // the transient/permanent failure routing) on the main loop — mirroring the // EventSpecVerificationResult / EventBlockedUserPROpenResult probe-then- // mutate pattern. CrashRecoveryPRDiscoveryResult carries the typed payload; // nil for other event kinds. EventCrashRecoveryPRDiscoveryResult // EventPickResult carries the outcome of the off-main scheduler Pick // (SOR-2450). schedule.Pick — the O(N²) dependency-graph build + ready-leaf // selection RunSchedulerPass runs every pass — MUST run off-main because the // synchronous inline shape froze the single-writer main loop for 6+ seconds // on a large issue cohort, blocking every other event. spawnOrSkipSchedulerPick // captures a deep copy of d.state.Issues on the main loop (deepCopyIssuesForPick), // runs schedule.Pick on a session goroutine, and Submit()s this result; the // handler applyPickResult clears d.state.PickInFlight, then re-reads each // picked issue live and runs the dispatch loop + follow-ons + project-SM // transitions on the main loop — re-validating each picked issue's // dispatchability against current state before acting, so the scheduler's // decision is identical to the prior inline path (R1/R3/R5). PickResult // carries the typed payload; nil for other event kinds. EventPickResult // EventActivationPollObserved carries the activation driver's per-plan // poll verdict back onto the main goroutine (SOR-2503). The poller // (SweepPlanActivating) fires every owned activation criterion's probe // trigger, dispatches the evidence-producing workflow for a // dispatch_workflow trigger, evaluates + records each probe, and submits // this event with the aggregate decision: AllSatisfied → the main-loop // handler applyActivationPollObserved transitions the plan plan_activating // → plan_completed (the evidence-citing closing transition + cascade + // fan-out main-merge); FailedRIDs non-empty → the plan routes to // plan_blocked through the escalation chokepoint; neither (some probes // still pending) leaves the plan in plan_activating for the next poll. // ActivationPollObservation carries the typed payload; nil for other event // kinds. EventActivationPollObserved // EventSteward routes one steward-prefixed event to the steward // sub-router (SOR-2659). StewardSubKind selects which init()-registered // sub-handler runs; handleStewardEvent dispatches on it. This single // case is the daemon-integration seam every downstream steward child // plugs into via registerStewardHandler — they never add their own // EventKind or handle() case. Deliberately ABSENT from // EventTriggersScheduler: a steward event must not cascade a scheduler // pass (a steward sub-handler that needs one triggers it explicitly). EventSteward // EventPerIssueWorktreeGCSweep is the periodic per-issue worktree GC // backstop sweep tick (SOR-2817). The poller fires this fire-and-forget; // sweepPerIssueWorktreeGC snapshots the reapable owner-key set from // d.state.Issues ON the main goroutine, then spawns an off-main goroutine // that lists the per-issue worktree wrappers and os.RemoveAll's only the // ones whose owner resolved terminal. No payload — the sweep reads // d.state directly. Deliberately ABSENT from EventTriggersScheduler: a GC // sweep removes leaked worktree directories only and never transitions an // issue, so it needs no scheduler re-eval. EventPerIssueWorktreeGCSweep // EventMainMergeRelevanceResult carries the per-plan relevance verdict // computed off-main for one of the two CIPB Phase C INTERIM main-merge // triggers (SOR-2876): the completion fan-out (fanOutMainMergeAfterMergeToMain) // or the external tip-change probe (applyPlanMainMergeTipProbeResult). The // off-main goroutine fetches the per-repo main-advance delta (changed-file // set since LastObservedMainTips), intersects it with each plan's predicted // footprint and build-input closure, and submits this event with the // relevant / deferred plan-key partition; the handler // applyMainMergeRelevanceResult dispatches an interim main-merge through the // shared dispatchPlanMainMerge chokepoint for every relevant plan and emits a // plan_main_merge_relevance_deferred audit event for every deferred one. The // delta MUST be computed off-main because it shells out to `git fetch` + // `git diff` per repo, which the single-writer main loop cannot block on. // MainMergeRelevanceData carries the typed verdict; nil for other event kinds. EventMainMergeRelevanceResult // EventGateReadSetObserved carries the file-read set a plan-branch gate // (CIPB per-child squash or periodic main-merge) observed for one plan // (SPEC-SOR-2773 tier 1). The off-main gate goroutine posts it via // d.Submit after the gate passed; applyGateReadSetObserved writes // ev.GateReadSet into d.state.PlanInputsReadSets[ev.PlanKey] — the // event-driven refresh the no-time-based-cache-refresh invariant requires // (the cache is freshened only when a gate actually re-ran on the plan's // current content, never on a timer). Deliberately ABSENT from // EventTriggersScheduler: recording a read-set transitions no issue, so it // needs no scheduler re-eval (the next main-merge probe reads the cache). EventGateReadSetObserved // EventCapabilityGapPark parks a plan on a self-capability gap // (SPEC-SOR-2923-v1 R2, SOR-3083). It is the deferral seam site 3 // (applyAssemblyFixerAction's escalate_capability_gap arm) uses: that arm // runs while the plan is in plan_auto_fixing — an ILLEGAL park source // (plan_auto_fixing → plan_capability_gap_parked is not a legal SM edge) — so // the off-main filing goroutine submits this event AFTER the // applyAssemblyFixerResult re-cycle drives the plan back to // plan_awaiting_children (both serialized through the main loop, so the // re-cycle commits before this event is processed). applyCapabilityGapParkEvent // then parks the plan through parkPlanOnCapabilityGap. CapabilityGapPark // carries the typed payload; nil for other event kinds. Deliberately ABSENT // from EventTriggersScheduler: a parked plan has no dispatchable leaves, so the // park needs no scheduler re-eval. EventCapabilityGapPark // EventCapabilityGapReleaseSweep is the SOR-3084 deploy-gated release sweep // tick. The poller fires this fire-and-forget; sweepCapabilityGapRelease // snapshots every held capability_gap_block + its parked plan + gap issue on // the main goroutine and spawns ONE off-main git-ancestry probe (one // `git merge-base --is-ancestor` per gap-issue repo) to decide whether each // gap fix is DEPLOYED in the running daemon build. No payload — the sweep // reads d.state.Issues + the issuestore and writes // d.state.CapabilityGapReleaseProbeInFlight directly. Deliberately ABSENT // from EventTriggersScheduler: the sweep itself transitions nothing (its // off-main probe submits EventCapabilityGapReleaseProbeResult, which does). EventCapabilityGapReleaseSweep // EventCapabilityGapReleaseProbeResult carries the per-plan git-ancestry // probe result back to the main goroutine (SOR-3084). The handler // applyCapabilityGapReleaseProbeResult clears // d.state.CapabilityGapReleaseProbeInFlight and, per result, leaves a // not-deployed plan parked (R4), or for a deployed plan consumes one // deploy-gated retry and re-enters integration (R3/R5) — or circuit-breaks to // plan_blocked once the retry budget is exhausted (R6). CapabilityGapReleaseProbe // carries the typed payload; nil for other event kinds. EventCapabilityGapReleaseProbeResult )
type FailedPlanPRCheck ¶
type FailedPlanPRCheck struct {
Repo string
PRURL string
CheckName string
WorkflowName string
HeadSHA string
}
FailedPlanPRCheck is the lightweight per-failure record the poller extracts from a plan PR's statusCheckRollup. CheckName is the gh statusCheckRollup entry's `name` field; WorkflowName is the parent workflow's display name (gh's `workflowName`, often distinct from the per-job `name` — e.g. workflow "CI" with job "go test ./..."). PRURL is the plan PR's URL so the fix implementer can navigate to the PR's checks tab. HeadSHA is the plan PR's head commit OID (gh's `headRefOid`), used by the SOR-2686 failure-confirmation arm to re-request the failed Actions runs for that SHA before a transient flake can synthesize a code-defect refer-back.
func PartitionTracematrixChecks ¶
func PartitionTracematrixChecks(failed []FailedPlanPRCheck) (tracematrix, other []FailedPlanPRCheck)
PartitionTracematrixChecks splits a slice of failed plan-PR checks into the tracematrix-verify slice (routed to the daemon-fix generator-defect signal) and the other slice (which feeds synthesizePlanPRCIFailureVerdict's refer-back path unchanged). A tracematrix-verify failure never appears in the other slice, so it never synthesizes a refer-back nor spawns a corrective child. (SPEC-SOR-2970-v1 R4.)
type FlowLivenessMonitor ¶
type FlowLivenessMonitor struct {
// contains filtered or unexported fields
}
FlowLivenessMonitor is the independent event-flow supervisor goroutine (SOR-2630). It is decoupled from the Debouncer behind accessor closures so it is trivially testable: production wires lastPass/lastTrigger/retrigger to the live Debouncer; tests inject closures that simulate a frozen / healthy producer and a fake exitFn that records the exit instead of killing the test process.
func (*FlowLivenessMonitor) Run ¶
func (m *FlowLivenessMonitor) Run(ctx context.Context)
Run blocks until ctx is canceled (or the monitor triggers a process exit). On every checkInterval tick it evaluates flow liveness. Uses a reset clock.Timer rather than a ticker to match the poller-loop idiom in this package (and because the no-time-based-cache-refresh analyzer scopes time.NewTicker out of internal/daemon — this monitor is a supervisor liveness check, not an operator-facing cache refresh). The timer comes from the injected clock so a clock-driven test drives it deterministically (no-ambient-time invariant; SPEC-SOR-3238 R6).
type ForceConvergeForTestPayload ¶
type ForceConvergeForTestPayload struct {
IssueKey string
ToState sm.IssueState
Actor string
Reason string
Now time.Time
// Done is closed by the handler once ForceConverge has been
// applied. The caller waits on this channel so the test goroutine
// resumes only after the mutation is visible on the main loop.
Done chan struct{}
}
ForceConvergeForTestPayload is the typed payload of EventForceConvergeForTest. Test-only — see ApplyForceConvergeForTest.
type ForwardError ¶
type ForwardError struct {
// StatusCode is the peer's HTTP status, or 0 for a transport-level
// failure (connection refused, timeout, DNS).
StatusCode int
// Body is the (truncated) peer response body, or the transport
// error text when StatusCode is 0.
Body string
// contains filtered or unexported fields
}
ForwardError is the typed error CrossDaemonClient.ForwardCreate returns on every non-201 outcome: a transport failure (StatusCode 0), an auth mismatch (401), any other 4xx/5xx, or a 202 (a downstream peer queued the create — the daemon-of-daemons case is out of scope, so the caller treats a 202 as transient). The caller distinguishes auth-mismatch from transient via AuthMismatch(); SOR-1216's create path queues every failure for the retry sweep regardless.
func (*ForwardError) AuthMismatch ¶
func (e *ForwardError) AuthMismatch() bool
AuthMismatch reports whether the peer rejected the forward with 401 — a configuration problem (the source daemon's bearer hash is not in the peer's topology) rather than a transient fault.
func (*ForwardError) Error ¶
func (e *ForwardError) Error() string
Error renders a compact, single-line summary.
func (*ForwardError) Unwrap ¶
func (e *ForwardError) Unwrap() error
Unwrap exposes the underlying transport error (nil for an HTTP-status rejection) so errors.Is/As against net-layer errors keeps working.
type ForwardRefLintResult ¶
ForwardRefLintResult is the EventForwardRefLintResult payload — the typed outcome of running the forward-referencing-paths predispatch lint on a session goroutine. IssueKey identifies the issue the lint ran against. Missing carries the slice of cited paths that did not exist on origin/<default> at lint time (the same shape plan.DetectForwardReferencingPaths returns); empty on a clean lint. ProbeErr is set when the lint probe itself failed (e.g. git cat-file exited non-zero with stderr matching neither the "exists" nor the "missing" pattern); the handler treats a probe error as fail-open to match the synchronous shape's behavior — emit a predispatch_lint_probe_failed event and allow dispatch.
type ForwardRequiredError ¶
type ForwardRequiredError = httpapi.ForwardRequiredError
ForwardRequiredError is re-exported from the httpapi package so daemon callers can construct the typed forward refusal without importing httpapi explicitly at every call site. The wire shape (target daemon name + control-plane URL + rationale) lives at the source of truth in internal/daemon/httpapi/server.go, where the POST /v1/issues handler maps it to a 409 forward_required envelope.
type GateFailureClass ¶
type GateFailureClass string
GateFailureClass is the flake-vs-real verdict ClassifyLandingGateFailure assigns a captured landing-gate failure. The zero value ("") means unclassified — used on every non-landing-gate failure path (a plain plumbing error, or a result set that never went through the gate); a captured failure is always classified to real or flake before it is acted on (spec R13).
const ( // GateFailureClassReal marks a failure that reproduced deterministically on // re-run — a genuine regression that must block the plan. GateFailureClassReal GateFailureClass = "real" // GateFailureClassFlake marks a failure that did NOT reproduce on re-run — // an intermittent flake, managed non-silently (spec R16) rather than treated // as a hard block-cause. GateFailureClassFlake GateFailureClass = "flake" )
func ClassifyLandingGateFailure ¶
func ClassifyLandingGateFailure(ctx context.Context, rerun func(context.Context) error) GateFailureClass
ClassifyLandingGateFailure classifies a captured landing-gate failure by a single INJECTABLE black-box re-run (spec R13): a re-run that reproduces the failure (returns a non-nil error) classifies real; one that does not (returns nil) classifies flake. The rerun closure is the caller's resolved-gate invocation, so the daemon makes no language / test-framework assumption. A nil rerun (no way to probe intermittency) classifies real — the conservative verdict that never mislabels a reproducible failure as a flake.
type ImplementerCycleDiffStater ¶
type ImplementerCycleDiffStater interface {
// CycleDiffStat runs `git diff --numstat origin/<default>...HEAD` in
// each worktree (keyed repo→dir) and returns the summed totals. A
// per-repo failure contributes zero and the loop continues; a
// fully-failed computation returns zero totals with a non-nil error
// the caller logs.
CycleDiffStat(ctx context.Context, worktreePaths map[string]string) (DiffStatTotals, error)
// CycleDiffNameOnly runs `git diff --name-only origin/<default>...HEAD`
// in each worktree and returns the union of edited repo-relative paths
// (the name-only sibling of CycleDiffStat). Feeds the implementer_phase_
// discovery_deviation metric's "actually edited" set. Same best-effort
// contract: a per-repo failure contributes nothing and the loop
// continues; a fully-failed computation returns nil with a non-nil error.
CycleDiffNameOnly(ctx context.Context, worktreePaths map[string]string) ([]string, error)
}
ImplementerCycleDiffStater computes the per-cycle git-diff numstat totals across an implementer dispatch's per-repo worktrees, for the implementer_cycle_summary telemetry. Invoked off-main in the dispatch goroutine — never on the single-writer main loop.
func NewImplementerCycleDiffStater ¶
func NewImplementerCycleDiffStater(client *gh.Client, projectRoot string) ImplementerCycleDiffStater
NewImplementerCycleDiffStater constructs the production-wiring ImplementerCycleDiffStater from a github.Client and the daemon's project root. cmd/sorcererd/start.go wires it into daemon.Config.ImplementerCycleDiffStater.
type ImplementerDispatcher ¶
type ImplementerDispatcher func(ctx context.Context, task role.ImplementerTask) (role.ImplementerResult, error)
ImplementerDispatcher runs one implementer task. Implementations are responsible for: rendering the per-task message via role.FormatImplementerTask, invoking the agent conversation, parsing the typed result via role.ParseImplementerResult.
Returning a typed result (even role.ImplementerResult{Status: "discovered-prereq"}) is a successful dispatch; only transport / conversation failures should return a non-nil error.
type ImplementerVerifyChainResult ¶
type ImplementerVerifyChainResult struct {
IssueKey string
Result role.ImplementerResult
Results []VerifierResult
FailingRepo string
Passed bool
}
ImplementerVerifyChainResult is the EventImplementerVerifyChainResult payload. The off-main goroutine snapshots the implementer-result continuation context onto this struct (so the main-loop handler can resume the post-chain dispatch without re-reading the implementer's envelope), runs the chain against each repo, and submits the result for applyImplementerVerifyChainResult to apply on the main loop.
Passed=true means every repo's chain reported green; the handler invokes the continuation (per_child PR-open OR plan-branch deferred arm) identically to the legacy synchronous shape. Passed=false means at least one verifier failed; the handler projects the failure(s) into synthetic ReviewConcerns and routes the issue to feedback (cycle++) or blocked_user (cap exhausted) via the existing refer- back machinery.
IssueKey + Result carry the inputs the continuation needs (the implementer's typed result is small enough to copy by value; we keep it on the payload rather than re-reading d.state.Issues so a concurrent operator mutation that drops Repos / Branch / etc. between chain dispatch and chain completion doesn't corrupt the continuation logic — single-writer-on-main is preserved, but the inputs are frozen on dispatch).
type IssuePRClosedRecovery ¶
type IssuePRClosedRecovery struct {
IssueKey string
FromState sm.IssueState
// ClosedRepos lists the repos whose PR snapshot was CLOSED, sorted
// for deterministic event-message ordering.
ClosedRepos []string
}
IssuePRClosedRecovery records one implementation issue whose PR set the github-prs poller observed as fully CLOSED (no PR MERGED). The detection-vs-apply split mirrors PlanExternalMergeRecovery: detection is a pure function called inside the EventGitHubPRsPolled handler; the daemon's main-goroutine apply walk routes through queueTransition + applyTransitionEffects so the canonical Linear-sync / cleanup / escalation plumbing fires.
func DetectIssuePRsClosed ¶
func DetectIssuePRsClosed(state *store.State, snapshots []PRSnapshot) []IssuePRClosedRecovery
DetectIssuePRsClosed walks the in-memory issue map looking for implementation issues in a post-implementing non-terminal state whose PR snapshot set is non-empty AND fully CLOSED (no PR is MERGED).
Pure function — does NOT mutate state. The caller (daemon.go's EventGitHubPRsPolled handler) applies each recovery via applyIssuePRClosedRecovery.
snapshots are the same []PRSnapshot the merged-arm in ApplyGitHubPoll consumes. The detector skips issues whose snapshot set contains a MERGED PR — that case is handled by the all-PRs-merged short-circuit in ApplyGitHubPoll (which has the precedent) and a mixed CLOSED + MERGED state would race the merged path on apply-order.
Returns one IssuePRClosedRecovery per qualifying issue, sorted by IssueKey for deterministic event ordering.
type IssuePRDraftConverted ¶
type IssuePRDraftConverted struct {
IssueKey string
Repo string
PRURL string
FromState sm.IssueState
}
IssuePRDraftConverted records one (issue, repo) pair whose PR has flipped from non-draft to draft between two consecutive github-prs polls. Bounded-scope per the SOR-1425 audit: detection only — no SM transition. The audit event makes the conversion grep-able for the recognizer / operator.
func DetectIssuePRsDraftConverted ¶
func DetectIssuePRsDraftConverted( state *store.State, priorByIssue map[string][]PRSnapshot, current []PRSnapshot, ) []IssuePRDraftConverted
DetectIssuePRsDraftConverted compares per-issue prior and current PRSnapshot slices, returning one IssuePRDraftConverted per (issue, repo) pair whose IsDraft flipped from false to true. The caller (daemon.go's EventGitHubPRsPolled handler) supplies the prior snapshot slice captured BEFORE the d.latestPRSnapshots overwrite — same diff-against- prior pattern as maybeFireEventDrivenBlockedUserUnblock.
Only issues in a post-implementing non-terminal state are inspected; terminal issues and pre-PR states (waiting/ready/implementing) cannot meaningfully draft-convert.
Returns the results sorted by (IssueKey, Repo) for deterministic event ordering across polls.
type IssueTransitionEffects ¶
type IssueTransitionEffects struct {
To sm.IssueState
Actor string
Reason string
Force bool
PreserveWIP string
ClearSession bool
EventKind string
EventMessage string
Comment string
CleanWorktree bool
Escalation *store.Escalation
// AttachSessionID is the mirror image of ClearSession: when non-empty,
// the chokepoint sets iss.SessionID = AttachSessionID AFTER the SM
// mutation and BEFORE persistTransitionHistory, so the attach lands in
// the same issuestore transaction as the state write (and rolls back
// with it on a persist error — the transitionFieldSnapshot already
// captures sessionID). It is the in-band caller's way to satisfy the
// session-attached-on-transition-in invariant: a transition INTO a
// session-required state (sm.IsSessionRequiredState) must atomically
// attach a session. Callers that already opened a session set this so
// open+attach+transition is one atomic unit (dispatchImplementer's
// ready-entry arm, applyImplementerDiscoveryResult). The full-row
// re-projection in persistTransitionHistory is safe here because
// session-required states are implementation-issue states only — never
// planning issues, whose mid-construction child junctions are what the
// narrow attachIssueSession UPDATE exists to protect. AttachSessionID
// and ClearSession are mutually exclusive on one edge.
AttachSessionID string
// EventPayload is the typed event a caller built via a generated
// store.New<Kind> constructor (events.gen.go). When non-zero
// (EventPayload.Kind != ""), the chokepoint emits it verbatim —
// layering the post-transition NewState + Timestamp on top — and
// ignores EventKind / EventMessage. Callers migrating off the
// untyped literal seat the constructor's output here; the legacy
// EventKind / EventMessage path stays in place for kinds whose
// schema entry has not yet shipped.
EventPayload store.Event
// BlockedReason, TriagerBlockedAt, TriagerBlockedReason are the
// post-transition metadata fields the chokepoint writes onto *iss as
// part of the atomic step that runs before persistTransitionHistory's
// mirror commit. Optional/zero-value semantics: an empty string / zero
// time.Time means "leave the field unchanged", matching today's
// behavior at call sites that never pre-mutated the fields. When set,
// the chokepoint writes the value AFTER the SM mutation (Transition /
// ForceConverge) so a non-empty BlockedReason wins over the SM's
// auto-derived BlockedReason on blocked_user / plan_blocked targets;
// callers who want the SM's auto-write to stand simply leave Effects.
// BlockedReason at its zero value. The cluster-1 rollback covers these
// fields via transitionFieldSnapshot, so a persist failure leaves them
// at their pre-call values along with State / PlanState.
BlockedReason string
TriagerBlockedAt time.Time
TriagerBlockedReason string
// ReviewerReferBackConcerns is the JSON-encoded []role.ReviewConcern
// buffer the verdict-refer-back path writes onto *iss BEFORE the
// in_review → feedback transition so the next feedback-N implementer
// dispatch threads the concerns onto the REVIEW_FEEDBACK envelope
// block. Same zero-value semantic as BlockedReason: empty means leave
// unchanged. Covered by the cluster-1 rollback via transitionFieldSnapshot.
ReviewerReferBackConcerns string
// From is the IssueState the issue was in before queueTransition
// performed the in-place SM mutation. Set ONLY by queueTransition so
// applyTransitionEffects can build a history row with the correct
// from_state column; ignored by applyIssueTransition (which reads
// iss.State directly before its own mutation). Zero-value is fine
// for the applyIssueTransition path — the helper does not consume
// this field.
From sm.IssueState
// CommentRow is an optional issuestore.Comment that callers want
// committed inside the same transaction as the issue-row mirror +
// history row. Used by the operator-mutate cancel path so the
// derived cancel comment lands atomically with the SM transition.
// Nil means no comment to write (the common case).
CommentRow *issuestore.Comment
// SetDiscoveryMarkdown / SetReviewDiscoveryMarkdown (phased-dispatch
// foundation), when non-empty, write the discovery artifact onto
// iss.DiscoveryMarkdown / iss.ReviewDiscoveryMarkdown AFTER the snapshot
// and BEFORE the persist, so the forward phased transitions
// (discovering → implementing carries the discovery result;
// in_review → review_judging carries the review-discovery result)
// persist the column atomically with the state change. Empty leaves the
// column unchanged. The future discovery-result handler seats the
// markdown here rather than mutating *iss directly so the write lands
// inside the cluster-1 snapshot/restore bracket — a failed persist rolls
// the column back to its pre-transition value alongside State.
SetDiscoveryMarkdown string
SetReviewDiscoveryMarkdown string
// ClearDiscoveryMarkdown / ClearReviewDiscoveryMarkdown (phased-dispatch
// foundation), when true, zero iss.DiscoveryMarkdown / iss.Review
// DiscoveryMarkdown on *iss AFTER the snapshot and BEFORE the persist,
// so the operator-recovery edges (implementing → discovering,
// review_judging → in_review) discard the stale discovery artifact
// atomically with the state change. The cluster-1 rollback covers the
// columns via transitionFieldSnapshot, so a failed persist leaves the
// column at its pre-clear value alongside State. Zero-value (false)
// leaves the column unchanged — the common case for every other edge.
// A Set and a Clear for the same column never coexist on one edge; if
// both were set, the Clear wins (applied last).
ClearDiscoveryMarkdown bool
ClearReviewDiscoveryMarkdown bool
// ClearDiscoveringHold (SOR-2891), when true, zeros the five durable
// held-discovering hold columns (DiscoveringHoldFootprint /
// DiscoveringHoldMarkdown / DiscoveringHoldSince / DiscoveringHoldBlockerKey
// / DiscoveringHoldReason) on *iss in the post-snapshot / pre-persist window,
// so the durable hold record is cleared ATOMICALLY with the discovering →
// implementing release transition — the exact inverse of the write-gate hold
// that set them (supervisor_result_implementer.go). Without this the released
// issue would carry a stale durable record indefinitely: `sorcerer issue show`
// would render a false "held" bullet on an actively-implementing issue,
// rehydrateDiscoveringFootprintHeld would resurrect a phantom registry entry
// after a restart, and the stale non-zero DiscoveringHoldSince would mask the
// strand-detection invariant's implementing leg. The cluster-1 rollback covers
// the five columns via transitionFieldSnapshot, so a failed persist restores
// the pre-clear hold record alongside State / SessionID. Zero value (false)
// leaves the columns unchanged — the common case for every edge except the
// hold-release transition in dispatchHeldDiscoveringIssue.
ClearDiscoveringHold bool
// SetPredictedFootprint (CIPB Phase E Option D), when non-nil,
// replaces iss.PredictedFootprint with the pointed-to value in the
// post-snapshot / pre-persist window so the in_review footprint
// backfill commits atomically with the in_review transition (and
// rolls back with it on a persist error — transitionFieldSnapshot
// captures the column). Nil leaves the column unchanged — the common
// case for every edge except the three in_review callers that compute
// a backfill for an exploration / legacy-empty footprint. The
// computed footprint sets Backfilled=true so subsequent in_review
// re-entries refresh it.
SetPredictedFootprint *footprint.PredictedFootprint
// SetRequirementTrace, when non-nil, replaces iss.RequirementTrace with
// the pointed-to value in the post-snapshot / pre-persist window so the
// implementer-emitted structured requirement→code trace commits
// atomically with the in_review transition (and rolls back with it on a
// persist error — transitionFieldSnapshot captures the column). Nil
// leaves the field unchanged — the common case for every edge except
// the result-handler in_review callers that decode a non-empty
// requirement_trace from the implementer result. Seated by
// applyIssueEffectsFootprintFields alongside SetPredictedFootprint.
SetRequirementTrace *spectrace.RequirementTrace
// SetVerifiedAgainstSpecVersion, when non-empty, writes iss.SpecAmend
// VerifiedSpecVersion (the durable re-gate stamp added by the
// bounded-recovery child's migration) in the post-snapshot / pre-persist
// window so the spec version a child last gated against commits
// atomically with the post-verify in_review transition (and rolls back
// with it on a persist error — transitionFieldSnapshot.specAmend
// VerifiedSpecVersion captures the column). The fan-out point
// applyImplementerResultPostVerifier computes the plan's current spec id
// once and threads it onto each branch-model in_review seat, mirroring
// how SetRequirementTrace is threaded. Empty leaves the column
// unchanged — the common case for every edge except a spec-driven
// child's gate-pass (and for a child with no spec-bearing planning
// parent, where the empty stamp is the correct "never gated" value).
SetVerifiedAgainstSpecVersion string
// SetReviewDiscoveryFootprint, when non-nil, replaces
// iss.ReviewDiscoveryFootprint with the pointed-to value in the
// post-snapshot / pre-persist window so the reviewer-discovery cited
// path-set commits atomically with the review_discovering → review_judging
// transition (and rolls back with it on a persist error —
// transitionFieldSnapshot.reviewDiscoveryFootprint captures the column).
// The reviewer-side peer of SetPredictedFootprint: applyReviewerDiscoveryResult
// extracts the typed per_ac_mapping path-set once and seats it here so
// emitReviewerDiscoveryDeviation reads the structured field, not a re-parse
// of the derived ReviewDiscoveryMarkdown (spec R8). Nil leaves the column
// unchanged — the common case for every edge except the one discovery-result
// seat.
SetReviewDiscoveryFootprint *footprint.PredictedFootprint
// BackfillAuditEvent (CIPB Phase E Option D), when its Kind is
// non-empty, is emitted AFTER persistTransitionHistory succeeds —
// alongside (not in place of) EventPayload / EventKind. It carries the
// predicted_footprint_backfilled audit row paired with a
// SetPredictedFootprint mutation; the emit is gated on persist success
// so a rolled-back transition never logs a phantom backfill. Zero value
// (Kind == "") means no backfill event — the common case.
BackfillAuditEvent store.Event
}
IssueTransitionEffects declares the side-effects a supervisor transition must apply, exactly matching the rows in the per- transition table at docs/state-machines.md §2.2 ("Side-effects on entry"). The `applyIssueTransition` helper walks this struct in a fixed order so call sites can't forget a field — adding a new edge means filling the struct, not remembering five separate function calls.
Field semantics:
- To: target state (required).
- Actor: caller identity for the History entry — `daemon` for ordinary autonomous transitions, `daemon:<sweep-kind>` for recovery-sweep / drift-converge / session-timeout paths (e.g. `daemon:boot_recovery`, `daemon:session_timeout`, `daemon:plan_awaiting_children`), `operator` for operator-initiated paths; required.
- Reason: free-text reason for the History entry (required).
- Force: when true, use ForceConverge (drift / boot-recovery); when false, use Transition (in-band) and propagate the ErrInvalidTransition error to the caller.
- PreserveWIP: when non-empty, run preserveWIPIfConfigured(iss, PreserveWIP, now) BEFORE the state mutation so the WIP branch captures the worktree as it was during the failed cycle.
- ClearSession: when true, clear iss.SessionID after the state mutation. Required on any transition that exits a session-bearing state per §2.11 ("Session-id lifecycle invariant"). The effect fires AFTER the state change so History records the session id that was active at the moment of transition.
- EventKind / EventMessage: the chokepoint ALWAYS appends one store.Event after the state mutation, stamping the post-transition state name into NewState. When EventKind is empty it defaults to the defaultIssueTransitionEventKind sentinel (see emitTransitionEvent).
- Comment: free-form comment string carried for audit purposes.
- CleanWorktree: when true, run cleanupWorktrees AFTER all the above. Required on transitions to merged/abandoned per §2.7.
- Escalation: when non-nil, append the escalation. Used for blocked_user transitions where the operator must act.
Side-effects this struct does NOT carry as fields but applyIssueTransition still applies based on the target state:
Blocked-user HEAD snapshot: when To == IssueStateBlockedUser and the issue has open PRs (len(iss.PRURLs) > 0), applyIssueTransition records the current head SHA of each PR into iss.LastEscalationHeads BEFORE the mirror runs, so the recovery sweep's dedupe gate can detect operator commits that should bypass the time-based cooldown. Best-effort: a PRSetDiscoverer failure (nil hook, GitHub timeout, partial set) does NOT block the transition; the affected repos' entries are left empty and the sweep treats them as "no recorded HEAD" (only cooldown lapse can recover those PRs).
cascadeClosePlanningAncestors: when an implementation issue lands in merged, planning ancestors are walked and closed inside-out (SOR-7); detailed in the helper's docstring.
type LLMVerifierRunner ¶
type LLMVerifierRunner interface {
RunVerification(ctx context.Context, specID, requirementID, statement string, tracedTargets []tracedcode.ResolvedTarget) (result, rationale string, err error)
}
LLMVerifierRunner is the oracle-runner handle the llm_test executor uses to emit a structured pass/fail verdict for a requirement's traced code. The executor hands it the requirement's own statement as the oracle's rubric (R3) and the resolved traced targets as the judged input (R6); the runner returns the structured verdict. The production implementation is backed by internal/specoracle (via an Oracle JudgeUncached dispatch — the executor owns the verdict cache, so the runner must not cache a second time) and wired in cmd/sorcererd/start.go; a nil handle (the default, threaded from daemon.Config.LLMVerifierRunner) disables llm_test invocations.
type LandingGateTail ¶
LandingGateTail is the structured failure tail CaptureLandingGateTail extracts from a *github.PlanLandingGateFailure. FailingTest is a best-effort failing-test identity (empty when none is parseable, e.g. a deadline kill with no output); Diagnostic is the bounded trailing slice of the combined gate output (≤ landingGateDiagnosticMaxLines lines). Both are best-effort — capture never fails and always returns a valid (possibly zero-value) tail.
func CaptureLandingGateTail ¶
func CaptureLandingGateTail(f *gh.PlanLandingGateFailure) LandingGateTail
CaptureLandingGateTail extracts a structured failure tail from a landing-gate failure (spec R12/R17). The *github.PlanLandingGateFailure already carries the full Stdout/Stderr copied off the ephemeral worktree BEFORE the gate's deferred teardown, so this extraction never races the teardown. It is best-effort and total: a nil failure or empty output yields a zero-value tail rather than an error.
type LiveSessionMetrics ¶
type LiveSessionMetrics struct {
SessionID string
IssueKey string // empty until populated by JoinLiveMetrics
OutputTokens int // cumulative output tokens
InputTokens int // cumulative input + cache-read tokens
Turns int // assistant-frame turn count
LatestSalientLine string // most recent salient activity line
RecentSalientLines []string // bounded ≤5 ring, oldest first
}
LiveSessionMetrics is the neutral daemon-facing DTO for one role session's live metering — the cumulative output / input(+cache-read) token counts, the assistant-frame turn count, and the latest plus a bounded recent-salient ring of "what's happening" activity lines.
It mirrors the neutral-type boundary watchdog.Subprocess / WatchdogSnapshotter use (internal/daemon/daemon.go): the daemon consumes its OWN type, and the adapter from agentcli.LiveSubprocess → LiveSessionMetrics lives in the wiring layer (cmd/sorcererd/start.go, the integration child) as a closure, so the daemon package takes no new clauderunner import.
func JoinLiveMetrics ¶
func JoinLiveMetrics(snapshots []LiveSessionMetrics, sessions []issuestore.Session) []LiveSessionMetrics
JoinLiveMetrics returns a copy of snapshots with each entry's IssueKey populated from the durable sessions list, matched on SessionID. A snapshot whose SessionID has no matching session row is returned unchanged (empty IssueKey) — "live" metering is eventually consistent with the persisted sessions list, so a just-started session that hasn't been persisted yet legitimately joins to no key. Nil / empty inputs yield an empty (non-nil) slice without panic.
type LivenessSessionWriteback ¶
type LivenessSessionWriteback struct {
// contains filtered or unexported fields
}
LivenessSessionWriteback is the per-session monotonic guard the LivenessRecorder closure (cmd/sorcererd/start.go) uses to feed the liveness tracker's advancing keep-alive (liveness.Observation. LastObservedActivityAt — the canonical max over genuine-work tiers {1,3,4,5}; see internal/liveness) back to a session row's displayed last_activity_at. It closes the SOR-2871 class where a genuinely- progressing long-running session (a ~15-min discovery) read as frozen at its ~2-second dispatch-time value because the tracker's keep-alive observations were written only to the liveness_observations audit table, never back to the session's displayed liveness.
It honors one-liveness-tracker-per-subprocess by NOT introducing a second activity timestamp or a parallel liveness path: Advance reads the existing canonical LastObservedActivityAt the tracker already computed, it never recomputes it. It honors single-writer-state / no-time-based-cache-refresh by writing off-main (from the clauderunner tick goroutine) and ONLY when the keep-alive advances past the last value written for that session — so it never churns the row on every tick and adds no heavy per-tick DB read or write.
The map grows one entry per session ID observed over the daemon process lifetime (bounded by the issues processed); the zero value is ready to use. Safe for concurrent use: the daemon shares one LivenessSessionWriteback across every running session's tick goroutine (there is exactly one tick goroutine per session, so concurrent calls for the same session ID do not occur, but the mutex makes the guard correct even if they did).
func (*LivenessSessionWriteback) Advance ¶
func (w *LivenessSessionWriteback) Advance(obs liveness.Observation) (int64, bool)
Advance reports whether the observation's keep-alive timestamp advances past the last value written for its session, and if so returns that advancing unix-second timestamp for the caller to write through UpdateSessionLastActivity. It is the pure, table-testable monotonic guard the AC mandates: the DB write happens in the caller, AFTER the returned ok, so the lock scope is just the map read+write.
- A zero-valued keep-alive (no genuine-work tier fired this tick, so AggregateKeepAlive returned the zero time.Time) or an empty SessionID returns (0, false) — nothing to write back.
- A keep-alive at or behind the last written value returns (0, false) — no advance, so no DB write (the fire-only-on-advance guard).
- An advancing keep-alive records it as the new high-water mark and returns (newTS, true).
type Logger ¶
Logger is a minimal interface so the daemon doesn't pull in any specific logging dependency. log.Default() satisfies this.
type LoopHarness ¶
type LoopHarness struct {
// contains filtered or unexported fields
}
LoopHarness is the staged-scenario synthesizer for recovery-loop tests. Each instance owns its own dispatcher fakes (no package-level mutable global) and a pair of FIFO queues:
- staged outcomes: builder methods (StageMainMergeGateFailure, StageAssemblyFixerEnvelopeRejection, StageReviewerVerdict) push one synthetic outcome per call; the matching dispatcher closure consumes them in order, falling back to a generic no-op when the queue is drained.
- captured envelopes: every closure appends the task envelope the daemon dispatched, exposed via the Captured* accessors so tests can assert which envelopes flowed through the daemon under a given staged scenario.
The shape mirrors the dispatcher-fake pattern of fakeDispatchers in supervisor_test.go but is self-contained.
File-split note: the LoopHarness struct, its Stage* builder methods, its Captured* / Counts accessors, and the small private staged-outcome helpers all live in THIS file so packages outside the daemon package (notably internal/invariants/property) can link against the public Stage* vocabulary. The dispatcher-closure methods (ImplementerDispatcher / ReviewerDispatcher / MergeResolverDispatcher / AssemblyFixerDispatcher / PlanMainMerger) reference daemon-package dispatcher function types that are only meaningful when wired into supervisor.Config; those closure methods live in the sibling loop_test_harness.go file.
Construct via NewLoopHarness; never share an instance across parallel subtests without first calling Reset (or allocating a fresh one).
func NewLoopHarness ¶
func NewLoopHarness() *LoopHarness
NewLoopHarness allocates a fresh harness with empty staged queues and empty dispatch captures.
func (*LoopHarness) AssemblyFixerDispatcher ¶
func (h *LoopHarness) AssemblyFixerDispatcher() AssemblyFixerDispatcher
AssemblyFixerDispatcher returns a closure compatible with Config.AssemblyFixerDispatcher. Captures every dispatched task and consumes StageAssemblyFixerEnvelopeRejection entries FIFO; when the queue is drained, returns a typed "no actions" failed result so the daemon's outer-retry budget exercises bit-for-bit.
func (*LoopHarness) CapturedAssemblyFixerTasks ¶
func (h *LoopHarness) CapturedAssemblyFixerTasks() []role.AssemblyFixerTask
CapturedAssemblyFixerTasks returns a defensive copy of every AssemblyFixerTask the daemon dispatched through the harness's assembly-fixer closure.
func (*LoopHarness) CapturedImplementerTasks ¶
func (h *LoopHarness) CapturedImplementerTasks() []role.ImplementerTask
CapturedImplementerTasks returns a defensive copy of every ImplementerTask the daemon dispatched through the harness's implementer closure. Order is dispatch order.
func (*LoopHarness) CapturedMergeResolverTasks ¶
func (h *LoopHarness) CapturedMergeResolverTasks() []role.MergeResolverTask
CapturedMergeResolverTasks returns a defensive copy of every MergeResolverTask the daemon dispatched through the harness's merge-resolver closure.
func (*LoopHarness) CapturedReviewerTasks ¶
func (h *LoopHarness) CapturedReviewerTasks() []role.ReviewerTask
CapturedReviewerTasks returns a defensive copy of every ReviewerTask the daemon dispatched through the harness's reviewer closure.
func (*LoopHarness) CapturedStewardTasks ¶
func (h *LoopHarness) CapturedStewardTasks() []role.StewardTask
CapturedStewardTasks returns a defensive copy of every StewardTask the daemon dispatched through the harness's steward closure. Order is dispatch order.
func (*LoopHarness) ConversationRunner ¶
func (h *LoopHarness) ConversationRunner(inner role.ConversationRunner) role.ConversationRunner
ConversationRunner returns inner verbatim when no Faulter is installed; otherwise returns a *faultinjection.FaultingConversationRunner wrapping inner with the harness's Faulter. Lets a scenario stage per-role subprocess faults (mid-output crashes, parse failures, timeouts) on `Stage("ConversationRun:<role>", …)` without reauthoring the dispatcher wiring — the test still calls role.NewImplementerDispatcher / NewReviewerDispatcher / etc. with the wrapped runner and the daemon's dispatch path consults the Faulter at the subprocess boundary.
func (*LoopHarness) Counts ¶
func (h *LoopHarness) Counts() CallCounts
Counts returns a snapshot of the per-closure call counters.
func (*LoopHarness) Faulter ¶
func (h *LoopHarness) Faulter() faultinjection.Faulter
Faulter returns the chokepoint the harness routes wrap-helper calls through, or nil when none was installed via WithFaulter. Useful for scenarios that want to Stage decisions directly against the same faulter handle the harness uses.
func (*LoopHarness) ImplementerDispatcher ¶
func (h *LoopHarness) ImplementerDispatcher() ImplementerDispatcher
ImplementerDispatcher returns a closure compatible with Config.ImplementerDispatcher. Captures every dispatched task; returns an empty role.ImplementerResult on every call (the daemon's supervisor treats the empty result as a no-op IMPLEMENT_OK).
func (*LoopHarness) Issuestore ¶
func (h *LoopHarness) Issuestore(inner issuestore.IssueStore) issuestore.IssueStore
Issuestore returns inner verbatim when no Faulter is installed; otherwise returns a *faultinjection.FaultingIssueStore wrapping inner with the harness's Faulter. Scenarios set `Config.Issuestore = h.Issuestore(realStore)` so the daemon's every-mutating-call routes through the Faulter when staged faults are present, and unchanged through inner when none are. The pass-through default is the load-bearing compatibility property for the existing recovery-loop cohort — a cohort that never calls WithFaulter never sees the FaultingIssueStore wrapper, so its SaveIssue / SaveEvent / etc. timings + error surfaces are identical to the pre-harness shape.
func (*LoopHarness) MergeResolverDispatcher ¶
func (h *LoopHarness) MergeResolverDispatcher() MergeResolverDispatcher
MergeResolverDispatcher returns a closure compatible with Config.MergeResolverDispatcher. Captures every dispatched task; the default behavior is "resolver landed the squash cleanly" — the returned result echoes the task's ConflictingPaths as ResolvedPaths and synthesizes a CommitSHA derived from ChildKey. This is the happy-path resolver for tests that pair StageSquashConflict with a successful autonomous resolution.
func (*LoopHarness) PlanMainMerger ¶
func (h *LoopHarness) PlanMainMerger() PlanMainMerger
PlanMainMerger returns a closure compatible with Config.PlanMainMerger (the CIPB Phase C periodic-main-merge handler callback). Consumes StageMainMergeGateFailure entries FIFO; when the queue is drained, synthesizes a clean *gh.PlanMainMergeResult with AlreadyUpToDate=true so subsequent sweeps no-op. The closure does the same stderr-signature classification the production MergeDefaultIntoPlanBranch does — substrings matching gh.MatchTransientGateFailureSignature surface as *gh.MainMergeGateTransientFailure (RetryCount set to gh.MainMergeGateMaxRetries to mirror an exhausted in-place retry loop), every other stderr surfaces as *gh.MainMergeGateFailure unchanged — so a test stages one stderr and exercises whichever daemon-side route the production classifier would have taken.
func (*LoopHarness) Reset ¶
func (h *LoopHarness) Reset()
Reset clears every staged outcome and every captured envelope on the receiver. Equivalent to constructing a fresh harness on the same pointer; useful for subtest loops that reuse one allocation.
func (*LoopHarness) ReviewerDispatcher ¶
func (h *LoopHarness) ReviewerDispatcher() ReviewerDispatcher
ReviewerDispatcher returns a closure compatible with Config.ReviewerDispatcher. Captures every dispatched task; consumes StageReviewerVerdict entries FIFO, falling back to a zero verdict (which the daemon's parser would reject — tests staging the reviewer path must seed at least one verdict per expected call).
Phased review dispatch activation: a read-only discovery dispatch (DispatchRole == roleReviewerDiscovery) auto-succeeds with a canned, validation-passing artifact and does NOT consume the staged-verdict queue, so staged verdicts continue to map 1:1 to JUDGMENT dispatches. Direct harness-unit calls (which leave DispatchRole empty) are unaffected and consume the queue as before.
func (*LoopHarness) StageAssemblyFixerEnvelopeRejection ¶
func (h *LoopHarness) StageAssemblyFixerEnvelopeRejection(parseErr error, coercions []string) *LoopHarness
StageAssemblyFixerEnvelopeRejection queues a synthetic envelope rejection the AssemblyFixerDispatcher closure will surface on its next invocation. parseErr is returned as the dispatcher's error (mirroring the production role.NewAssemblyFixerDispatcher → ParseAssemblyFixerResult → *role.AssemblyFixerParseError path); coercions populates the returned Coercions slice for audit-event emission exercise.
func (*LoopHarness) StageMainMergeGateFailure ¶
func (h *LoopHarness) StageMainMergeGateFailure(stderr string, exitCode int) *LoopHarness
StageMainMergeGateFailure queues one synthetic per-push-gate failure the PlanMainMerger closure will surface on its next invocation. The closure decides which typed error class to return based on the stderr substring: a hit against gh.MatchTransientGateFailureSignature returns *gh.MainMergeGateTransientFailure (so the daemon routes through plan_blocked_transient_gate_failure + the recognizer re-arm path); a miss returns *gh.MainMergeGateFailure (so the daemon routes through the assembly_fixer chain). Mirrors the StageGateFailure builder pattern above; the queued type differs (main-merge gate vs dry-run gate) but the consume / fallback semantics are identical. Chainable: returns the receiver.
func (*LoopHarness) StageReviewerVerdict ¶
func (h *LoopHarness) StageReviewerVerdict(v role.ReviewerVerdict) *LoopHarness
StageReviewerVerdict queues a synthetic reviewer verdict the ReviewerDispatcher closure will return on its next invocation. Use for staging refer-back / rebase / merge / escalate routes through the daemon's review-result handling.
func (*LoopHarness) StageSpecDraft ¶
func (h *LoopHarness) StageSpecDraft() *LoopHarness
StageSpecDraft records one synthetic spec_drafter draft submission in the spec-lifecycle staging vocabulary (spec-driven phase B). The property corpus invokes it as the first stage of the draft → verify → revise → review lifecycle it drives; the staged count is observable via StagedSpecDraftCount. Chainable: returns the receiver.
func (*LoopHarness) StageSpecReviewVerdict ¶
func (h *LoopHarness) StageSpecReviewVerdict(v role.SpecReviewerVerdict) *LoopHarness
StageSpecReviewVerdict records one synthetic spec_reviewer coherence verdict — the terminal stage of the spec lifecycle (the spec_review → spec_approved gate). Mirrors StageReviewerVerdict's queue shape; the staged count is observable via StagedSpecReviewVerdictCount. Chainable: returns the receiver.
func (*LoopHarness) StageSpecRevise ¶
func (h *LoopHarness) StageSpecRevise() *LoopHarness
StageSpecRevise records one synthetic spec_drafter revise submission (the autonomous resolution of an open finding). The staged count is observable via StagedSpecReviseCount. Chainable: returns the receiver.
func (*LoopHarness) StageSpecVerifyFindings ¶
func (h *LoopHarness) StageSpecVerifyFindings() *LoopHarness
StageSpecVerifyFindings records one synthetic SMT-verification pass that surfaced spec findings (the spec_verifying → spec_revising trigger). The staged count is observable via StagedSpecVerifyFindingsCount. Chainable: returns the receiver.
func (*LoopHarness) StageStewardResult ¶
func (h *LoopHarness) StageStewardResult(r role.StewardResult) *LoopHarness
StageStewardResult queues one synthetic steward wake result the StewardDispatcher closure returns on its next invocation (SOR-2667). Use it to stage the R15 arms: a result carrying a resolving `Actions` entry, or one carrying an `OperatorQuestion`. When the queue is drained the closure returns a plain STEWARD_OK result (no actions / no question), so a periodic sweep wake firing after the staged one never re-applies the staged effect. Chainable: returns the receiver.
func (*LoopHarness) StagedAssemblyFixerRejectionCount ¶
func (h *LoopHarness) StagedAssemblyFixerRejectionCount() int
StagedAssemblyFixerRejectionCount reports how many StageAssemblyFixerEnvelopeRejection outcomes are queued.
func (*LoopHarness) StagedMainMergeGateFailureCount ¶
func (h *LoopHarness) StagedMainMergeGateFailureCount() int
StagedMainMergeGateFailureCount reports how many StageMainMergeGateFailure outcomes are queued.
func (*LoopHarness) StagedReviewerVerdictCount ¶
func (h *LoopHarness) StagedReviewerVerdictCount() int
StagedReviewerVerdictCount reports how many StageReviewerVerdict outcomes are queued.
func (*LoopHarness) StagedSpecDraftCount ¶
func (h *LoopHarness) StagedSpecDraftCount() int
StagedSpecDraftCount reports how many StageSpecDraft outcomes were staged.
func (*LoopHarness) StagedSpecReviewVerdictCount ¶
func (h *LoopHarness) StagedSpecReviewVerdictCount() int
StagedSpecReviewVerdictCount reports how many StageSpecReviewVerdict outcomes are queued.
func (*LoopHarness) StagedSpecReviseCount ¶
func (h *LoopHarness) StagedSpecReviseCount() int
StagedSpecReviseCount reports how many StageSpecRevise outcomes were staged.
func (*LoopHarness) StagedSpecVerifyFindingsCount ¶
func (h *LoopHarness) StagedSpecVerifyFindingsCount() int
StagedSpecVerifyFindingsCount reports how many StageSpecVerifyFindings outcomes were staged.
func (*LoopHarness) StewardDispatcher ¶
func (h *LoopHarness) StewardDispatcher() StewardDispatcher
StewardDispatcher returns a closure compatible with Config.StewardDispatcher (SOR-2667). Captures every dispatched task and consumes StageStewardResult entries FIFO; when the queue is drained it returns a plain STEWARD_OK result (no actions, no operator question), so a periodic / repeated wake never re-applies a staged resolving action or operator question.
func (*LoopHarness) StewardTaskCount ¶
func (h *LoopHarness) StewardTaskCount() int
StewardTaskCount reports how many times the harness's StewardDispatcher closure was invoked — the "steward attempted" signal a test polls before asserting an outcome (the wake ran off-main).
func (*LoopHarness) WithFaulter ¶
func (h *LoopHarness) WithFaulter(f faultinjection.Faulter) *LoopHarness
WithFaulter installs f as the harness's per-seam fault chokepoint. While set, the wrap helpers (Issuestore, ConversationRunner, WrapViewPR, WrapOpenPlanPR) consult f on every call to the wrapped inner; while nil (the default + post-Reset state), every wrap helper is a pure pass-through and the harness behavior is byte-equivalent to a cohort without fault injection. Chainable: returns the receiver.
Coexists with the existing Stage* builders without interference: the dispatcher-closure methods (ImplementerDispatcher, ReviewerDispatcher, PlanMainMerger, etc. in the sibling loop_test_harness.go file) consume staged outcome queues; the wrap helpers consult the Faulter for chokepoint faults. A scenario stages role-output outcomes via the existing builders AND chokepoint faults via the Faulter — the two surfaces do not collide.
func (*LoopHarness) WrapOpenPlanPR ¶
func (h *LoopHarness) WrapOpenPlanPR(inner func(ctx context.Context, repoSlug, planBranch, title, body string) (string, error)) func(ctx context.Context, repoSlug, planBranch, title, body string) (string, error)
WrapOpenPlanPR returns inner verbatim when no Faulter is installed; otherwise returns the faultinjection.WrapOpenPlanPR closure consulting the harness's Faulter on op "OpenPlanPR" before delegating. Scenarios wrap their Config.PlanPROpener wiring with this helper.
func (*LoopHarness) WrapViewPR ¶
func (h *LoopHarness) WrapViewPR(inner func(ctx context.Context, prURL string) (*gh.PR, error)) func(ctx context.Context, prURL string) (*gh.PR, error)
WrapViewPR returns inner verbatim when no Faulter is installed; otherwise returns the faultinjection.WrapViewPR closure consulting the harness's Faulter on op "ViewPR" before delegating. Scenarios wrap their Config.PlanPRViewer wiring with this helper to stage merge-then-404 / mergeable-state-flicker / blank-reason faults without touching the inner closure.
type MainMergeRelevanceResultEvent ¶
type MainMergeRelevanceResultEvent struct {
Trigger string
RelevantPlanKeys []string
DeferredPlanKeys []string
}
MainMergeRelevanceResultEvent is the EventMainMergeRelevanceResult payload (CIPB Phase C, SOR-2876). The off-main relevance goroutine partitions the eligible in-flight CIPB plans into RelevantPlanKeys (a relevant main-advance — dispatch an interim main-merge) and DeferredPlanKeys (an irrelevant advance — no interim merge; defer to the next relevant advance or the final merge). Trigger names which interim trigger produced the verdict (one of the relevanceTrigger* labels). The relevant/deferred key slices are the only payload — the handler re-reads each plan's live state on the main goroutine before acting, so a plan that advanced past candidacy between spawn and result is skipped.
type MergeResolverDispatcher ¶
type MergeResolverDispatcher func(ctx context.Context, task role.MergeResolverTask) (role.MergeResolverResult, error)
MergeResolverDispatcher runs one autonomous merge-resolver task and returns the typed result. Invoked from inside the plan-branch assembler's locked ephemeral worktree when a child's squash conflicts AND Config.MergeResolverDispatcher is non-nil; the dispatcher renders the per-task message via role.FormatMergeResolverTask, runs the `merge_resolver` conversation, and parses the typed MergeResolverResult via role.ParseMergeResolverResult. Returning a typed `gave_up` result is a successful dispatch — the caller decides whether to retry (the daemon's resolver closure budget-guards N=2 attempts per conflicting child); only transport / conversation failures should return a non-nil error.
type MergeRunner ¶
type MergeRunner func(ctx context.Context, targets []gh.MergeTarget, opts gh.MergeOptions) []gh.MergeSetResult
MergeRunner runs the merge sequence for an issue's PR set. Returns per-repo results; a nil error means the runner itself worked, but individual targets may still have Err set.
type Mutation ¶
type Mutation struct {
Kind MutationKind
IssueKey string
// MutationTransition payload.
ToState string
Reason string
// MutationAddBlocker / MutationRemoveBlocker payload. The wire
// semantics read "BlockerKey blocks BlockedKey", i.e. BlockedKey's
// DependsOn gains/loses BlockerKey. IssueKey is left empty for
// these mutations — the handler reads the two keys directly so
// 404 messages can surface whichever side is missing.
BlockerKey string
BlockedKey string
// MutationComment payload. Body carries the comment text
// verbatim; the caller is responsible for trimming. Also re-used
// by MutationAmend, where Body carries the new issue description
// the operator is amending the issue to, and by MutationRetitle,
// where Body carries the new issue title.
Body string
// MutationSetPriority payload. Priority carries the new Linear
// priority value (0..4); the handler validates the range and
// records the prior value in the audit row. Unused by every other
// mutation kind.
Priority int
// MutationCancel payload. CascadePlanAbandon carries the operator's
// explicit opt-in to the plan-branch cascade-abandon gate (SOR-1248).
// When false (the default), the cancel handler refuses with a typed
// *httpapi.CascadeImpactError if the issue is a `plan_branch`
// implementation child whose parent plan is non-terminal. When true,
// the gate is bypassed and the cascade proceeds.
CascadePlanAbandon bool
// MutationCreate payload. CreateInput carries the typed
// IssueCreateInput payload (title / body / kind / repos /
// depends_on / project / priority); CreateReply receives the
// assigned key + error from the dispatcher. The caller buffers
// CreateReply so a slow main loop doesn't block the dispatcher.
//
// CreateRouteDecision, when non-nil, carries the cross-daemon
// resolver's decision that already ran in
// ApplyOperatorCreateIssue's pre-flight (SOR-1277). It is threaded
// through the mutation so the main-loop handler can emit a
// `route_resolved_for_key` audit event with Subject=<minted-key>
// without re-running the resolver. Nil on paths where the
// resolver did not run (no topology, or inbound forwarded create).
CreateInput httpapi.IssueCreateInput
CreateReply chan<- CreateMutationResult
CreateRouteDecision *RouteDecision
// CreateDedupePending, when true, marks the newly-minted issue
// with DedupePending=true. Set by the pre-mint dedupe gate when
// the LLM call timed out / errored transiently so the create
// proceeds anyway and a background sweep re-runs the dedupe
// gate. Fix D (SOR-1330). MutationCreate-only.
CreateDedupePending bool
// MutationAdoptPR payload. AdoptInput carries the wire-shape
// operator overrides (title / body / project); ResolvedRepo /
// ResolvedBranch / ResolvedPRURL carry the PRResolver's output
// already validated against the project allowlist and the OPEN
// state. CreateReply (shared with MutationCreate) receives the
// assigned key + error.
//
// These three Resolved* fields are deliberately explicit rather than
// positionally reusing IssueKey / ToState / Reason — those names
// belong to MutationTransition and any "smuggled" reuse silently
// corrupts the audit-event renderer + any future shared-dispatcher
// refactor (per SOR-220 cycle-1 review concern #4).
AdoptInput httpapi.IssueAdoptPRInput
ResolvedRepo string // "owner/repo" parsed from the PR URL and validated against cfg.Repos
ResolvedBranch string // head branch name returned by the PRResolver
ResolvedPRURL string // canonical PR URL returned by the PRResolver
// ResolvedFootprint carries the predicted_footprint derived by
// footprintFromPRFiles in ApplyOperatorAdoptPR's pre-mutation phase;
// applyOperatorAdoptPR seats it on the minted issue so the adopted PR
// lands in_review with a non-empty footprint (write-state-footprint-known).
ResolvedFootprint footprint.PredictedFootprint
// Reply, when non-nil, receives one error from the dispatcher
// (nil on success). Buffered by the caller so a slow main loop
// doesn't block the dispatcher's send. Used by transition.
Reply chan<- error
// CommentReply, when non-nil, receives the assigned comment id +
// unix-second timestamp + error from the dispatcher. Used by the
// comment-append path so the wire layer can echo the new id back
// to the operator. Buffered by the caller for the same reason as
// Reply above.
CommentReply chan<- CommentMutationResult
// SnapshotReply, when non-nil, receives the typed dedupe-snapshot
// envelope. Used by MutationDedupeSnapshot so a non-main goroutine
// (the HTTP handler driving the dedupe gate) can read d.state.Issues
// + d.latestPRSnapshots without a data race. Buffered by the caller.
SnapshotReply chan<- DedupeSnapshotResult
// PRSnapshotsReply, when non-nil, receives a copy of
// d.latestPRSnapshots[IssueKey] captured on the main goroutine.
// Used by MutationPRSnapshotsForIssue so the HTTP read path can
// populate IssuePR's CI / merge-state fields without racing the
// supervisor's poller-driven writes. Buffered by the caller so a
// slow handler can't block the main loop.
PRSnapshotsReply chan<- PRSnapshotsForIssueResult
// PRSnapshotsAllReply, when non-nil, receives a copy of every entry
// in d.latestPRSnapshots captured on the main goroutine. Used by
// MutationPRSnapshotsAll so the GET /v1/issues handler can populate
// IssueSummary.PRs across every row in one mutation roundtrip,
// instead of dispatching N MutationPRSnapshotsForIssue calls in a
// loop. Buffered by the caller for the same reason as
// PRSnapshotsReply above.
PRSnapshotsAllReply chan<- PRSnapshotsAllResult
// MutationRoutePrepareMove + MutationFinalizeRouteMove payload (SOR-1278).
// RouteTargetDaemon names the destination peer for the prepare leg;
// RouteDestKey + RouteDestDaemon carry the peer's reply forward to the
// finalize leg. RoutePrepareReply receives the prepared forward body +
// the route_operator_move_initiated audit-id; finalize uses Reply for
// its plain error result.
RouteTargetDaemon string
RouteDestKey string
RouteDestDaemon string
RoutePrepareReply chan<- RoutePrepareMoveResult
// MutationAbortRoleSessions payload (SOR-1364). Role names the
// paused role whose in-flight sessions should walk to aborted;
// Reason is the operator's verbatim --pause-reason text (used as
// the audit-event Reason / Message via TerminationCause).
// AbortRoleSessionsReply receives the count of sessions the
// handler aborted plus any typed error. IssueKey / ToState are
// unused for this kind — the abort targets a role across every
// referencing issue.
Role string
AbortRoleSessionsReply chan<- AbortRoleSessionsResult
// MutationAddPlanChildren payload (SOR-1448 / SOR-1443 Fix 4).
// PlanAddChildrenInput carries the typed PlanAddChildrenInput
// payload (plan_key + the children to add); PlanAddChildrenReply
// receives the assigned keys + warnings + error. IssueKey is left
// empty; the plan key is on the input. The caller buffers the
// reply chan so the dispatcher's send never blocks.
PlanAddChildrenInput httpapi.PlanAddChildrenInput
PlanAddChildrenReply chan<- PlanAddChildrenMutationResult
// MutationRefactorPlan payload (operator-driven replan escape).
// PlanRefactorInput carries the typed PlanRefactorInput payload
// (plan_key + new body + reason); PlanRefactorReply receives the
// typed result + error. The caller buffers the reply chan so the
// dispatcher's send never blocks.
PlanRefactorInput httpapi.PlanRefactorInput
PlanRefactorReply chan<- PlanRefactorMutationResult
// MutationReferBack payload. Concerns carries the operator-supplied
// refer-back concerns; the handler writes the JSON-encoded slice onto
// iss.OperatorReferBackConcerns (overwriting any prior buffer) so the
// next implementer feedback dispatch pipes them into REVIEW_FEEDBACK.
// The caller MUST trim + reject empty arrays at the wire layer; the
// daemon also defends against an empty payload as defense in depth.
Concerns []role.ReviewConcern
// Spec-driven phase-B mutation payload (MutationSpec*). SpecFindingID
// is the spec_findings row id a patch resolves / a suppress targets (0
// for a free-form patch); SpecPatches is the RFC-6902 JSON-Patch
// document for MutationSpecPatch. The narrow-change prose for
// MutationSpecAmend rides on Body; the operator reason for approve /
// suppress rides on Reason. IssueKey carries the planning key for all
// five kinds. Reply receives the typed error or nil.
//
// Scope is the MutationSpecAmend local|structural discriminator (empty =
// full re-author amend). Validated at the wire layer; threaded onto the
// dispatched amend task envelope.
SpecFindingID int64
SpecPatches json.RawMessage
Scope string
// MutationProjectApprovedChildren payload. ProjectApproveProposalID is
// the proposals.id whose approved implementation children need eager
// projection into d.state.Issues. The handler replies on the shared
// Reply chan (nil on success, the lookup/projection error otherwise).
ProjectApproveProposalID int64
// MutationActivationOverride payload (SOR-2504). RequirementID is the
// activation R-ID the operator is overriding to satisfied; Reason
// carries the mandatory operator justification recorded on the
// plan_activating_probe_override audit event. IssueKey carries the
// planning key. Reply receives the typed error or nil.
RequirementID string
// MutationActivationList reply (SOR-2504). ActivationListReply receives
// the race-safe snapshot of plans currently in plan_activating, read on
// the main goroutine; the off-main bridge then assembles the per-probe
// status views from each plan's spec + verdict store. The caller buffers
// this chan so the dispatcher's send never blocks.
ActivationListReply chan<- ActivationListResult
// Steward ledger mutation payload (SOR-2659, spec R5). StewardLedgerRow
// carries the typed row for MutationStewardLedgerCreate (the persisted
// row's AUTOINCREMENT id is populated and returned on StewardLedgerReply)
// and MutationStewardLedgerUpdate (the row is matched by ID and every
// mutable column overwritten; the plain Reply chan carries the error).
// StewardLedgerID names the row for MutationStewardLedgerExpire (a hard
// delete). The caller buffers StewardLedgerReply so the dispatcher's send
// never blocks.
StewardLedgerRow *issuestore.StewardLedgerRow
StewardLedgerID int64
StewardLedgerReply chan<- StewardLedgerMutationResult
// Steward block-intent payload (SPEC-SOR-2652 R20). StewardBlockIntent
// carries the issue key + diagnosis + reason for MutationStewardBlockIntent;
// the main-goroutine handler loads the issue and routes the outcome through
// the escalation chokepoint (escalateSubject), never bare-setting a blocked
// state. The plain Reply chan carries the typed error or nil.
StewardBlockIntent *StewardBlockIntentPayload
// Ack, when non-nil, receives one signal the moment the main-loop
// dispatcher begins applying this mutation (BEFORE the per-kind
// handler runs). It is the "the daemon's main goroutine accepted
// the mutation" gate — distinct from Reply (which lands when the
// handler finishes). The two-phase split lets the CLI surface tell
// "the daemon's main goroutine is wedged / unresponsive" (no Ack
// inside the short submission-ack window) apart from "the handler
// is taking a long time" (Ack fired but Reply hasn't yet —
// legitimate on cascade-heavy mutations like canceling a plan with
// N children, where the handler can take 10–30s of legitimate
// work). The caller MUST buffer this chan so the dispatcher's send
// never blocks: `make(chan struct{}, 1)`.
Ack chan<- struct{}
}
Mutation is the typed payload of EventOperatorIssueMutation. The dispatcher reads Kind first and then the kind-relevant fields. A tagged-union shape keeps the table extensible — adding a new mutation in a follow-up issue means appending a kind constant + new fields and a switch arm in applyOperatorIssueMutation, never a fresh event constant.
type MutationKind ¶
type MutationKind int
MutationKind tags the operator-mutation type carried by a Mutation payload. SOR-952 shipped MutationTransition; SOR-953 adds MutationComment; SOR-954 adds MutationAddBlocker / MutationRemoveBlocker. Subsequent issues add MutationCancel / MutationCreate against the same struct shape.
const ( // MutationUnknown is the zero value; reaching the dispatcher with // it is a programming error. MutationUnknown MutationKind = iota // MutationTransition flips an issue's SM state. IssueKey + ToState + // Reason are populated; Reply receives the typed error or nil. MutationTransition // MutationAddBlocker declares "BlockerKey blocks BlockedKey" — i.e. // appends BlockerKey to the depender's (BlockedKey's) DependsOn // slice. The handler runs sm.WouldCreateCycle and returns // httpapi.ErrCycle if the new edge would close a cycle. Idempotent: // a duplicate add against an existing edge replies nil without // mutating state. MutationAddBlocker // MutationRemoveBlocker is the inverse: drops BlockerKey from // BlockedKey's DependsOn. Idempotent: a remove against a missing // edge replies nil without mutating state. MutationRemoveBlocker // MutationComment appends one operator comment to the issue's // thread. IssueKey + Body are populated; CommentReply receives // the assigned (id, ts, error) tuple. The Reply chan is unused // for this kind — comment dispatch needs the new id back to the // caller, not just a status. MutationComment // MutationCancel transitions the issue to its terminal-cancel state // (abandoned for implementation, plan_abandoned for planning) and // runs the existing supervisor cleanup path (worktree teardown, // best-effort WIP preservation, Linear push to canceled). IssueKey + // Reason are populated; ToState is ignored (kind-derived). MutationCancel // MutationCreate files a fresh issue. CreateInput carries the // typed payload (title / body / kind / repos / depends_on); // CreateReply receives the assigned key + error. IssueKey is not // populated for this kind — the key is the OUTPUT, not the input. MutationCreate // MutationDedupeSnapshot captures a race-safe snapshot of the // in-flight issue digest + open-PR digest on the main goroutine, // for use by the dedupe gate that runs on POST /v1/issues BEFORE // the actual MutationCreate. Read-only; despite the kind name, // no state is mutated. It rides the mutation channel because the // reply-chan + main-loop dispatch is the existing single-writer // convergence point — adding a parallel "query event" would // duplicate the plumbing for the same semantics. MutationDedupeSnapshot // MutationPRSnapshotsForIssue copies d.latestPRSnapshots[IssueKey] // on the main goroutine and replies on PRSnapshotsReply. Read-only; // rides the mutation channel for the same single-writer // race-safety reason as MutationDedupeSnapshot. Used by the // GET /v1/issues/{key} handler so the wire-shape IssuePR rows can // carry the CI / merge-state badge fields without racing the // supervisor's poller-driven writes. MutationPRSnapshotsForIssue // MutationPRSnapshotsAll copies every entry in d.latestPRSnapshots // on the main goroutine and replies on PRSnapshotsAllReply with a // map[issue_key][]PRSnapshot. Read-only; rides the mutation channel // for the same race-safety reason as MutationPRSnapshotsForIssue. // Used by the GET /v1/issues handler so the wire-shape IssueSummary // rows can carry the CI / merge-state snapshot in one roundtrip, // avoiding an N+1 fetch when the operator UI's graph renders // per-issue merge-blocked rollup badges across every visible node. MutationPRSnapshotsAll // MutationAmend updates an issue's description in place and writes // an audited issue_amendments row capturing the pre-amend body so // the prior content is recoverable from the audit trail. IssueKey + // Body (the new description) + Reason are populated; Reply receives // the typed error or nil. The amend mutation touches description // only — issue state, state_kind, and dependencies rows are NEVER // modified by this mutation. MutationAmend // MutationAdoptPR creates a fresh implementation Issue around an // existing PR that was opened out-of-band (the SOR-220 Layer 4 // `sorcerer issue adopt-pr` workflow). AdoptInput carries the // resolved PR shape (URL + repo + branch + title + body); // CreateReply receives the assigned key + error. The new issue // lands directly at IssueStateInReview — bypassing the standard // initial→waiting→ready→implementing chain because the PR is // already pushed and the reviewer is the next responsible actor. // The transition table records this as the `(initial)→in_review` // move; the history row's actor is "operator" and reason is // `issue_adopted_pr: <pr-url>`. MutationAdoptPR // MutationRekey renames an existing issue's key to a fresh // canonical `<teamPrefix>-<N>` minted from project.last_issue_seq. // Surgical fix for the SOR-222 production case: an orphan-PR // adoption synthesized issue `TREAT-500` (a non-canonical key that // breaks the operator UI's issue list); the rekey path renames it // to `SOR-N` while preserving the row's full audit trail across // every referencing table. IssueKey is the OLD key being renamed; // CreateReply receives the assigned NEW key + error. MutationRekey // MutationSetPriority updates an issue's Linear priority in place // and writes an audited history row capturing the prior + new // value. IssueKey + Priority (the new 0..4 value) + Reason are // populated; Reply receives the typed error or nil. Like // MutationAmend it touches one field only — issue state, // state_kind, and dependencies rows are NEVER modified. MutationSetPriority // MutationRoutePrepareMove is the first of the two SOR-1278 // cross-daemon operator-move main-loop touches. The handler // validates the source issue (session_id empty + SM admits // abandonment), emits route_operator_move_initiated capturing the // audit-id, and replies with the prepared forward body + audit-id // on RoutePrepareReply. The operator goroutine consumes the reply, // drives the HTTP forward, and submits MutationFinalizeRouteMove // with the destination's reply. MutationRoutePrepareMove // MutationFinalizeRouteMove is the second of the two SOR-1278 // touches. The handler runs the source-side cancel // (state=abandoned, reason naming the destination), emits // route_operator_move_completed, and sweeps every sibling whose // DependsOn contains the source key — removing the dep + emitting // one route_operator_move_dep_rewritten per affected sibling. // Reply receives nil or the typed error. MutationFinalizeRouteMove // MutationAbortRoleSessions terminates every in-flight session // (state ∈ {starting, running}) whose role equals Role via the // terminateSession chokepoint with an operator-pause TerminationCause. // Dispatched by the cmdadapter SetRolePause path AFTER the // role_health_pauses row UPSERT + role_health_pause_set_by_operator // audit event have committed: the pause row already gates future // dispatches, and this mutation gates the in-flight ones (SOR-1364). // The handler reports the count of sessions it aborted back through // AbortRoleSessionsReply so the cmdadapter can thread the count // onto the wire-shape RolePauseSetResponse.AbortedInFlightSessions // the CLI surfaces. MutationAbortRoleSessions // MutationAddPlanChildren attaches N new implementation children to // a plan_branch planning issue's MaterializedChildren and // transitions the plan to plan_auto_fixing. SOR-1448 / SOR-1443 Fix // 4: the add-children primitive both the operator CLI `sorcerer // plan add-children` and the assembly_fixer role's // file_implementation_issue action use. Additive — does NOT trigger // cancel-cascade; existing parked children stay parked. Reads // PlanAddChildrenInput; replies on PlanAddChildrenReply with the // minted keys + any warnings. MutationAddPlanChildren // MutationRefactorPlan is the operator-driven replan escape hatch // for POST /v1/plan/{key}/refactor. The handler snapshots the // planning issue's children + emits a plan_refactor_dispatched // event, cascade-cancels every non-merged child through the // per-issue cancel core, amends the planning body, and // force-transitions the plan to plan_revising. Reads // PlanRefactorInput; replies on PlanRefactorReply with the typed // result + error. Idempotent: re-running converges to // (plan_revising, new body, zero in-flight children). MutationRefactorPlan // MutationReferBack injects operator-supplied refer-back concerns // onto an in_review / merging issue and walks it to feedback so the // next implementer feedback cycle's REVIEW_FEEDBACK carries them. // The handler clears the live session, sets // OperatorReferBackConcerns to the supplied array (overwriting any // prior buffer), and routes through the standard // applyIssueTransition so downstream side-effects fire identically // to a reviewer-driven refer-back. IssueKey + Reason + Concerns are // populated; Reply receives the typed error or nil. MutationReferBack // MutationSpecVerify re-runs the verifier pipeline against the // planning issue's current spec version, replacing its persisted // findings and emitting spec_verified. MutationSpecVerify // MutationSpecPatch applies an RFC-6902 JSON-Patch (SpecPatches) to // the current version via the patcher's atomic apply, optionally // resolving SpecFindingID, then re-enters the verify lifecycle. MutationSpecPatch // MutationSpecAmend dispatches a TASK: amend on the spec_drafter with // the operator's narrow change (Body) and emits spec_amend_dispatched. MutationSpecAmend // MutationSpecApprove transitions the planning issue spec_review → // spec_approved (precondition: no open error findings) and emits // spec_approved. Reason carries the operator approval reason. MutationSpecApprove // MutationSpecSuppressFinding flips one finding (SpecFindingID) open → // suppressed with the operator's Reason and emits // spec_finding_suppressed. MutationSpecSuppressFinding // MutationClearDedupePending flips iss.DedupePending=false on the // single-writer main goroutine and re-projects the issuestore row. // Submitted by the deferred dedupe sweep (clearDedupePendingMarker), // which runs on a caller-controlled (off-main) goroutine: routing the // in-memory write through the mutation channel keeps the field touch // serialized with every other main-loop reader (e.g. the footprint // reconciler), so the single-writer invariant holds and the marker // clear cannot race the scheduler pass. IssueKey + Reason are // populated; Reply receives the typed error or nil. MutationClearDedupePending // MutationProjectApprovedChildren eagerly projects an approved // proposal's implementation children into d.state.Issues on the // single-writer main goroutine, closing the stranded-live-orphan // window that otherwise exists between the off-main // Store.ReviewProposal approve-commit (in the handlePlanReview HTTP // handler) and the next verdict-harvest or recovery sweep. Dispatched // by ApplyProjectApprovedChildren from handlePlanReview immediately // after a successful approve commit. ProjectApproveProposalID carries // the proposals.id whose approved children need projection; Reply // receives the typed error or nil. Idempotent with the deferred // verdict-harvest projection (a second applyPlanReviewerApprove // re-overwrites the same child map slots and the redundant plan // transition is rejected by the SM). MutationProjectApprovedChildren // MutationActivationOverride marks a named activation probe satisfied // with the operator's mandatory reason on a plan in plan_activating // (SOR-2504, spec R6 sole exception). IssueKey carries the planning // key, RequirementID the activation R-ID to override, Reason the // operator's mandatory justification. The handler emits a durable // plan_activating_probe_override sentinel (the audit record + the // R6 exception), records the satisfied verdict, and triggers an // immediate activation re-poll so the plan reaches plan_completed // rather than re-evaluating the probe live. Reply receives the typed // error or nil (wrapping httpapi.ErrSpecNotFound / ErrSpecStateConflict). MutationActivationOverride // MutationActivationList captures a race-safe snapshot of the plans // in plan_activating off d.state.Issues for the read-only `sorcerer // activation list` verb (SOR-2504). Read-only despite the kind name // (mirrors MutationPRSnapshotsAll): it rides the mutation channel so // the candidate snapshot is read on the single-writer main goroutine, // then the off-main bridge reads each plan's spec + verdict store to // assemble the probe-status views. ActivationListReply receives the // candidate snapshot. MutationActivationList // MutationStewardLedgerCreate INSERTs one steward_ledger row through the // single-writer main loop (SOR-2659, spec R5). StewardLedgerRow carries // the typed row to persist; StewardLedgerReply receives the created row // (with its AUTOINCREMENT id populated) + error. The handler commits the // SaveStewardLedgerRowTx inside a Store.WithTx and emits the // steward_ledger_write audit event after the commit. MutationStewardLedgerCreate // MutationStewardLedgerUpdate UPDATEs every mutable column of the // steward_ledger row identified by StewardLedgerRow.ID (SOR-2659). Reply // receives the typed error or nil (ErrNotFound wrapped when the row is // absent). Emits steward_ledger_write with operation=update. MutationStewardLedgerUpdate // MutationStewardLedgerExpire hard-DELETEs the steward_ledger row // identified by StewardLedgerID (SOR-2659; the table has no expired_at // column, so "expire" maps to DeleteStewardLedgerRowTx). The handler // reads the row's type BEFORE the delete so the audit event carries the // row_type. Reply receives the typed error or nil. Emits // steward_ledger_write with operation=expire. MutationStewardLedgerExpire // MutationStewardBlockIntent routes a steward block-intent action through // the escalation chokepoint (escalateSubject) on the main goroutine // (SPEC-SOR-2652 R20). The steward action executor runs OFF the main // goroutine (the wake goroutine); a block-intent must reach the // single-writer chokepoint, so the executor's surface submits this typed // mutation and awaits the reply, mirroring the triager's // EventTriagerActionApply → escalateSubject bridge. StewardBlockIntent // carries the typed payload; Reply receives the typed error or nil. MutationStewardBlockIntent // MutationRetitle corrects an issue's title in place (SPEC-SOR-2786-v1 // R7/R8). IssueKey + Body (the new title, re-using the shared Body // field) + Reason are populated; Reply receives the typed error or nil. // Like MutationSetPriority / MutationAmend it touches one field only — // issue state, state_kind, session_id, and dependency edges are NEVER // modified — so the issue's key, in-flight session, and edges are // preserved and the change rides an audited history row. Appended at // the END of the iota (preserving every existing ordinal); do not // insert mid-list. MutationRetitle // MutationReplace atomically replaces the issue at IssueKey with a // freshly-minted live replacement (SPEC-SOR-2826): mint the replacement // from CreateInput, copy the old issue's retained declared forward edges, // repoint every declared reverse dependent off the old key onto the // replacement, and terminally cancel the old issue — all in one issuestore // transaction so no scheduler pass observes a dependent gated only on a // terminal issue (R10 no-release). IssueKey is the OLD key being replaced; // CreateInput carries the replacement's content; CreateReply (shared with // MutationCreate) receives the minted replacement key + error. Appended at // the END of the iota (preserving every existing ordinal); do not insert // mid-list. MutationReplace )
func (MutationKind) String ¶
func (k MutationKind) String() string
String returns a stable kind name used in events.log records.
type OrphanAdoptionPRFetcher ¶
type OrphanAdoptionPRFetcher func(ctx context.Context, prURL string) (title, body string, err error)
OrphanAdoptionPRFetcher is the callback shape BackfillOrphanAdoptionMetadata uses to resolve a PR URL into its current title and body. Concretely backed by gh.Client.ViewPR in production; tests inject a fake. Returning an error skips the issue silently (e.g. 404 because the PR was deleted) — the dead metadata stays but isn't crashable.
type OrphanPRRef ¶
type OrphanPRRef struct {
Repo string
URL string
Branch string
HeadSHA string
Title string
Body string
IssueKey string // best-effort; "" when not extractable
// ExtractedVia names which surface produced IssueKey: "branch",
// "title", "body", or "" (no extraction). SOR-1320 — the supervisor
// uses this to dispatch the correct audit event and apply the
// non-terminal guard the title/body path requires.
ExtractedVia string
}
OrphanPRRef is the typed shape OrphanPRDiscoverer returns; the daemon synthesizes one Issue per ref. Mirrors recovery.OrphanPR without importing the recovery package directly so daemon.Config stays narrow.
type Outcome ¶
type Outcome struct {
// Reason is the canonical token from internal/escalation's Reason
// constants (or a novel free-form token — the chokepoint defaults a novel
// token to the autonomous route). It is the key the classifier scans and
// the suffix of the chokepoint's audit-event kind.
Reason string
// Diagnosis is the operator-facing diagnostic. It becomes the transition
// Reason (which the SM auto-derives into BlockedReason on a human-block
// target) and the message of the chokepoint's audit event.
Diagnosis string
// EventKind / EventMessage are forwarded verbatim to the underlying
// applyIssueTransition / applyPlanTransition effect so a caller that wants
// a typed transition-event (e.g. the same-state-cycle backstop's
// issue_same_state_cycle_blocked) still lands it, in ADDITION to the
// chokepoint's generic escalation audit row. Empty leaves the transition's
// own event un-emitted (the chokepoint's generic row still lands).
EventKind string
EventMessage string
// Comment / PreserveWIP / BlockedReason are the human-block-only effect
// slots a converting caller threads through when its bespoke block
// transition carried them. They are forwarded ONLY by escalateHumanBlock
// (the R2/R3 arm) — NEVER by escalateAutonomous — because they describe an
// issue that is actually being parked: a "🧙 sorcerer: paused" Comment, a
// WIP-preserve tag, and an explicit BlockedReason override must not fire
// when the chokepoint resolves the outcome autonomously (the subject is
// not blocked). Empty values are inert: an empty Comment posts nothing, an
// empty PreserveWIP preserves nothing, and an empty BlockedReason leaves
// the SM's auto-derived BlockedReason (from the transition Reason) to
// stand. See IssueTransitionEffects for each field's apply-side semantics.
Comment string
PreserveWIP string
BlockedReason string
// TriagerBlockedAt / TriagerBlockedReason stamp the durable triager-blocked
// marker (SOR-142) that gates autonomous re-dispatch of a triager-blocked
// issue. Like Comment / PreserveWIP / BlockedReason these are human-block-
// only effect slots: escalateHumanBlock forwards them, escalateAutonomous
// never does (an autonomously-resolved subject is not triager-blocked). A
// zero time.Time / empty string leaves the fields unchanged — the planner-
// result sites do not set them; only the triager sites do.
TriagerBlockedAt time.Time
TriagerBlockedReason string
// Escalation is the operator-escalation row the underlying transition writes
// atomically with the human-block state change. The converting planner-
// result and triager sites thread the bespoke store.Escalation they
// previously passed straight to the Effects struct. Forwarded ONLY by
// escalateHumanBlock; nil writes no escalation row.
Escalation *store.Escalation
// CapabilityGapIssueKey / CapabilitySignature are the AUTONOMOUS-route-only
// effect slots for ReasonAssemblyFixerCapabilityGapInFlight (SPEC-SOR-2923-v1
// R2, SOR-3083) — the gap issue key and signature parkPlanOnCapabilityGap
// requires to park the plan in plan_capability_gap_parked. They parallel the
// human-block-only slots above but are forwarded ONLY by escalateAutonomous's
// capability-gap arm; escalateHumanBlock never reads them. Both must be
// non-empty for the arm to fire (parkPlanOnCapabilityGap errors on an empty
// gapIssueKey / capabilitySig, which propagates back as a dispatch error).
CapabilityGapIssueKey string
CapabilitySignature string
}
Outcome carries a "can't-proceed" role-logic outcome to the escalation chokepoint (escalateSubject). It is the parallel of the infra-error path's classifier input: a reason token plus the human-readable diagnostic, and the optional typed transition-event slots the underlying SM helper forwards.
type PRResolver ¶
type PRResolver func(ctx context.Context, prURL string) (PRResolverResult, error)
PRResolver resolves a PR URL into the daemon-friendly shape the adopt-pr mutation needs. Production wires this to a closure over gh.Client.ViewPR (start.go); tests inject a fake. The repo field MUST be the canonical "owner/repo" string parsed from the PR URL.
type PRResolverResult ¶
type PRResolverResult struct {
Repo string // "owner/repo"
URL string // canonical PR URL
State string // "OPEN" | "CLOSED" | "MERGED"
HeadRef string // head branch name
Title string
Body string
}
PRResolverResult is the per-PR shape PRResolver returns. Only the fields the adopt-pr handler reads are exposed — the resolver is internal/github-agnostic so internal/daemon stays out of the gh dependency tree (matches the CommitVerifier pattern).
type PRSnapshot ¶
type PRSnapshot struct {
IssueKey string // "SOR-123"
Repo string // "owner/repo"
PRURL string
State string // "OPEN" | "CLOSED" | "MERGED"
Mergeable string // "MERGEABLE" | "CONFLICTING" | "UNKNOWN"
MergeStateStatus string // "CLEAN" | "BEHIND" | "DIRTY" | "UNSTABLE" | etc.
ChecksGreen bool
// ChecksTerminal is true when every check rollup entry has reported a
// conclusion (success, failure, canceled, skipped, neutral, action-
// required, timed-out) — i.e. no entry is still queued or in-progress.
// An empty rollup is NOT terminal: allChecksTerminal consults
// gh.ExpectedChecksPresent so a freshly-pushed head SHA whose Actions
// runs have not registered yet stays pending (the daemon must not
// treat "no checks observed" as "checks done"). Distinct from
// ChecksGreen: a failing PR is terminal but not green. The dispatch
// gate in
// dispatchFollowOns reads this to defer reviewer dispatch while CI is
// still running.
ChecksTerminal bool
// FailedCheckNames lists the rollup entries whose Check.IsFailing()
// returns true (CONCLUSION ∈ {FAILURE, TIMED_OUT, STARTUP_FAILURE,
// ACTION_REQUIRED} or legacy STATE ∈ {FAILURE, ERROR}). Sorted by
// the poller for deterministic event-message ordering. Read by the
// SOR-1068 reviewer-dispatch readiness evaluator so the `ci_failed`
// event carries the failing check names verbatim instead of just
// `<repo>:<pr>`. Empty when the rollup is green or partially
// pending — pending entries are NOT failing.
FailedCheckNames []string
IsDraft bool
// HeadSHA is the PR head's commit oid (gh's `headRefOid`) at the
// moment of the poll. Populated from github.PR.HeadRefOid by the
// github-prs poller (internal/runtime/pollers.go). Consumed by the
// EventGitHubPRsPolled handler's blocked_user fast-path: a HEAD
// that differs from the prior poll's snapshot for the same
// (issue, repo) is the trigger for an event-driven single-issue
// recovery sweep, dropping unblock latency from one
// Recovery.ActiveSec tick (≤300s) to one github-prs poll cadence
// (≤30s). Empty when gh did not surface the head SHA — the
// fast-path treats empty as "no advance" so transient gh failures
// cannot synthesize a phantom recovery.
HeadSHA string
// LatestRunStartedAt is the start time of the most-recent CI run on
// this snapshot's head SHA — the maximum github.Check.StartedAt over
// the PR's StatusCheckRollup, the zero time.Time when the rollup is
// empty or no entry carries a startedAt. The SOR-3200 bounded-wait
// anchor (maybeRouteActionsWaitTimeout) reads it so a feedback-cycle
// CI restart re-arms the wait budget against the latest run's own
// start rather than the original buffer time. In-memory only:
// PRSnapshot is the d.latestPRSnapshots poll view, never persisted to
// the issuestore, so no migration is added.
LatestRunStartedAt time.Time
}
PRSnapshot is the daemon's view of one GitHub PR after a poll. The poller constructs this from github.PR; the reconcile path reasons only about state and mergeability.
type PRSnapshotsAllResult ¶
type PRSnapshotsAllResult struct {
ByKey map[string][]PRSnapshot
Err error
}
PRSnapshotsAllResult is the reply payload from MutationPRSnapshotsAll. ByKey is a deep-copy snapshot of d.latestPRSnapshots keyed by issue key; Err carries any error the main-loop handler raised. ByKey is nil when the supervisor's snapshot map is empty (no polls have run yet); callers treat nil and zero-length map identically.
type PRSnapshotsForIssueResult ¶
type PRSnapshotsForIssueResult struct {
Snapshots []PRSnapshot
Err error
}
PRSnapshotsForIssueResult is the reply payload from MutationPRSnapshotsForIssue. Snapshots is a copy of d.latestPRSnapshots[IssueKey] (nil when the supervisor hasn't seen a poll for the issue yet); Err carries any error the main-loop handler raised. Currently no error mode is reachable — preserved so future failure modes can surface to the caller without changing the reply-chan shape.
type PendingTransition ¶
type PendingTransition struct {
IssueKey string
// Exactly one of Effects / PlanEffects is set, matching the issue's
// Kind. The daemon's apply walk reads whichever is non-nil and
// dispatches to applyTransitionEffects / applyPlanTransitionEffects.
Effects *IssueTransitionEffects
PlanEffects *PlanTransitionEffects
Applied bool
}
PendingTransition is one (issue, transition-effects) pair the reconcile path wants the daemon to apply via the typed effects helpers. Every production-path SM mutation must flow through this slice — direct iss.ForceConverge / iss.Transition calls bypass the canonical Linear-target / cleanup / escalation plumbing.
`Applied` is set to true when the caller has already mutated state in-place (so chained drift checks within the same pass see the new state). The daemon's walk then runs only the side-effects (event, comment, cleanup, escalation).
func ApplyGitHubPoll ¶
func ApplyGitHubPoll(state *store.State, snapshots []PRSnapshot, now time.Time) ([]store.Event, []store.Escalation, []PendingTransition)
ApplyGitHubPoll applies fresh GitHub PR state to the in-memory SM. Called on the main goroutine after a GitHub PR poll completes.
This pass handles two PR-driven transitions:
All PRs for an issue in merging state are MERGED → transition to merged and run cleanup. This covers the case where the daemon submitted merge requests and the merges have now completed.
Any PR for an issue in in_review/rebasing state is CONFLICTING → transition to rebasing so the implementer can rebase.
Returns events, escalations, and pending transitions for the daemon to apply via applyIssueTransition. Direct iss.{Transition, ForceConverge} calls would bypass the Linear-sync invariant.
type PickResultEvent ¶
type PickResultEvent struct {
Keys []string
HoldExcluded []string
InFlight int
Ghost int
BlockedAll bool
Err error
At time.Time
}
PickResultEvent is the EventPickResult payload. The off-main worker (spawnOrSkipSchedulerPick) captures a deep copy of d.state.Issues on the main loop, runs schedule.Pick on a session goroutine, and submits this struct for applyPickResult to apply on the main loop. The worker writes neither d.state nor the issuestore — it only Submit()s this result, which carries only plain values (no *sm.Issue pointers into live state, no *Daemon reference) so the off-main/on-main boundary stays clean.
Keys is the ordered list of issue keys schedule.Pick selected for dispatch (the snapshot's PickResult.ToDispatch keys, in priority order). applyPickResult re-reads each key against LIVE d.state and re-validates dispatchability before acting, so a key whose issue transitioned between the snapshot and the apply is skipped — the decision stays identical to the inline path over the data it acts on (R1/R3/R5). BlockedAll is the snapshot's PickResult.BlockedAll, threaded so applyProjectTransitions makes the same project-SM decision the inline path made. Err is non-nil only when schedule.Build found a dependency cycle (the inline path's error return); applyPickResult emits the same cycle escalation and dispatches nothing. At is the on-main capture time threaded onto the result.
HoldExcluded / InFlight / Ghost carry the SOR-2572 zero_dispatch_pass inputs across the off-main boundary (re-integrated onto the off-main path by SOR-2598). HoldExcluded is the ordered list of issue keys the on-main-built holdPredicate diverted from the dispatch candidate set (the snapshot's PickResult.HoldExcluded keys, priority-sorted); applyPickResult re-derives the live *sm.Issue pointers by key and, when this pass dispatched nothing while free capacity AND held eligible-looking work both remain, emits the deduped-per-episode zero_dispatch_pass audit event. InFlight / Ghost are the on-main implementer-slot counts threaded in so applyPickResult recomputes the same freeSlots figure the inline path reported.
type PlanAddChildrenMutationResult ¶
PlanAddChildrenMutationResult is the reply payload from the MutationAddPlanChildren handler. AddedKeys + Warnings parallel the HTTP wire shape; Err carries any typed error.
type PlanBranchCreateRepoResult ¶
PlanBranchCreateRepoResult is one repo's outcome from dispatchPlanBranchCreate (CIPB Phase B). Err is nil on success; the per-repo plan-branch ref is recorded on the planning issue's PlanCommitSHAs[Repo] sentinel by applyPlanBranchCreateResult.
type PlanBranchCreateResult ¶
type PlanBranchCreateResult struct {
PlanBranch string
Repos []PlanBranchCreateRepoResult
// ResetPerformed records that a re-decomposition clean-base reset ran
// before the EnsurePlanBranch loop (SPEC-SOR-3368-v1 R3): the plan's
// RedecompBranchResetPendingAt marker was set, so dispatchPlanBranchCreate
// reset each affected repo's plan branch to current main and closed any
// prior plan PR. applyPlanBranchCreateResult clears the marker on the first
// such result (set on attempt, not on per-repo success — a failed per-repo
// reset is logged but the marker still clears so reset is not re-triggered
// indefinitely; EnsurePlanBranch recreates the branch regardless).
ResetPerformed bool
}
PlanBranchCreateResult is the EventPlanBranchCreateResult payload — the aggregate per-repo outcome of one dispatchPlanBranchCreate invocation. Partial failures are tolerated: a failed repo emits a plan_branch_creation_failed audit event and is retried by the next sweepPlanBranchCreate pass; succeeded repos are recorded.
type PlanBranchRecreateRepoResult ¶
type PlanBranchRecreateRepoResult struct {
Repo string
Outcome planBranchRecreateOutcome
Reason string
}
PlanBranchRecreateRepoResult is one repo's outcome from the recovery-walk self-heal dispatch (maybeDispatchRecreatePlanBranch). Reason carries the push-failure detail when Outcome is planBranchRecreatePushFailed.
type PlanExternalMergeRecovery ¶
type PlanExternalMergeRecovery struct {
PlanKey string
FromState sm.PlanState
// PRURLs is the resolved (repo → PR URL) map for the plan PRs the
// recovery observed. Always populated — the branch-name-fallback
// path writes the discovered URL here so the comment + audit event
// can cite the merged PR(s) regardless of whether iss.PRURLs was
// originally set.
PRURLs map[string]string
Trigger string
}
PlanExternalMergeRecovery records one planning issue that the SOR-1375 auto-recovery path detected as externally-merged on GitHub while the local Plan SM was still stuck in a post-integrating non- terminal state (plan_blocked / plan_awaiting_pr_merge / plan_pr_merge_pending_ci). The runtime poller path and the boot consistency sweep share this struct: detection happens off the main goroutine (in the github-prs poller or as inline gh calls during boot), and the main-goroutine handler applies the parent + children transitions atomically via applyIssueTransitionsBatch.
Trigger is a free-text tag baked into the audit event's reason ("poller_detected_merge" / "boot_consistency_sweep") so the `plan_pr_merged_out_of_band` row is grep-able by call site.
func DetectPlanExternalMerges ¶
func DetectPlanExternalMerges(state *store.State, snapshots []PRSnapshot, trigger string) []PlanExternalMergeRecovery
DetectPlanExternalMerges walks the in-memory issue map looking for planning issues stuck in a post-integrating non-terminal state whose plan PR(s) are now MERGED on GitHub per the supplied snapshots.
Pure function — does NOT mutate state. The caller (daemon.go's EventGitHubPRsPolled handler) applies each recovery via applyPlanExternalMergeRecovery.
snapshots are the same []PRSnapshot the issue arm consumes; the plan path matches by IssueKey (planning issues' Key lands in the PRSnapshot.IssueKey field exactly the same way implementation issues do, so the github-prs poller can emit both shapes through one channel).
A plan qualifies when:
- iss.Kind == IssueKindPlanning
- iss.PlanState is one of plan_blocked / plan_awaiting_pr_merge / plan_pr_merge_pending_ci
- the snapshot set keyed by iss.Key is non-empty AND every entry has State == "MERGED"
The function returns one PlanExternalMergeRecovery per qualifying plan, sorted by PlanKey for deterministic event ordering.
type PlanMainMergeFailureKind ¶
type PlanMainMergeFailureKind int
PlanMainMergeFailureKind classifies a per-repo main-merge failure for applyPlanMainMergeResult's routing decision. Mirrors CPBSquashFailureKind's shape so the daemon-side classifier can dispatch on the same enum-style switch the per-child squash uses.
const ( // PlanMainMergeFailureNone is the success sentinel (clean merge or // already-up-to-date no-op). PlanMainMergeFailureNone PlanMainMergeFailureKind = iota // PlanMainMergeFailureConflict — merge_resolver exhausted attempting // to resolve a `git merge origin/<default>` conflict. The daemon's // applyPlanMainMergeResult routes this through the assembly_fixer // chain (Fix 3+4 style) before escalating to the M=3 plan_blocked // counter. PlanMainMergeFailureConflict // PlanMainMergeFailureGate — the per-push gate exited non-zero on // the merge commit. Same routing as Conflict (through // assembly_fixer). PlanMainMergeFailureGate // PlanMainMergeFailureGateTransient — the per-push gate exited // non-zero AND every attempt's stderr matched a curated host- // environmental transient signature (parallel golangci-lint, port- // bind contention, cargo file-lock); the github-side retry budget // was exhausted. Maps from *github.MainMergeGateTransientFailure. // Routes the plan directly to plan_blocked WITHOUT dispatching // assembly_fixer — a fixer agent has no action shape that resolves // a host-environmental flake. The operator inspects the matched // signature in the audit trail and re-arms via // `sorcerer issue transition <plan-key> plan_awaiting_children`. PlanMainMergeFailureGateTransient // PlanMainMergeFailureNonFastForward — the regular `git push` was // rejected because a concurrent sibling squash landed first. This // is TRANSIENT — the next sweep cycle re-attempts cleanly. Not // counted toward the M=3 plan_blocked threshold; emits // `plan_main_merge_push_non_ff` for observability. PlanMainMergeFailureNonFastForward // PlanMainMergeFailureAuth — the regular `git push` was rejected // with an HTTP 401/403 (expired/insufficient token). Maps from // *github.AssemblePushAuthFailure (SOR-1461). The daemon force- // refreshes the token; the next sweep retries with valid // credentials. Counts toward the M=3 threshold so a permanently- // broken token still escalates after M cycles, but NEVER routes to // assembly_fixer (a fixer agent cannot mint a token). PlanMainMergeFailureAuth // PlanMainMergeFailureTransient — the push failed with a recognized // network-class error. Maps from *github.AssemblePushTransientFailure // (SOR-1461). The next sweep retries; counts toward the M=3 threshold // so a sustained outage still escalates. Never routes to // assembly_fixer. PlanMainMergeFailureTransient // PlanMainMergeFailureGeneric — plumbing failure (bare clone // missing, fetch error, etc.). Counts toward the M=3 threshold; // the operator escalation includes the captured reason. PlanMainMergeFailureGeneric // PlanMainMergeFailureBranchDeleted — the plan branch was absent on // origin (deleted out-of-band) AND the autonomous recreate push of the // recorded plan-branch tip failed after the withPushRetry budget // exhausted. Maps from a planBranchRecreatePushFailed outcome in // runPlanMainMergeOffMain. Routes the plan DIRECTLY to plan_blocked // with the typed plan_branch_externally_deleted event (NOT through // assembly_fixer — a fixer agent cannot reach a push that the recorded // SHA / token permanently rejects) and does NOT count toward the M=3 // counter (the plan is already blocked). A recreate that SUCCEEDS never // reaches this kind: the off-main loop re-attempts the merge and the // re-attempt's outcome is classified normally. PlanMainMergeFailureBranchDeleted // PlanMainMergeFailureResolverInfraTransient — an infrastructure- // transient merge_resolver exhaustion (the resolver subprocess was // SIGKILLed, never spawned, or hit a spawn/context deadline), NOT a // genuine "this conflict is unresolvable" outcome. Maps from a // *github.MainMergeConflict whose Cause satisfies // isMergeResolverInfraTransient. applyPlanMainMergeResult re-queues the // main-merge (a fresh, session-load-aware dispatch) WITHOUT routing the // failure through dispatchPlanMainMergeFailureToAssemblyFixer (whose // code-defect-only vocabulary cannot resolve a main-advance-vs-sibling- // squash conflict with no parked owner) and WITHOUT incrementing // MainMergeConsecutiveFailures (the M=3 circuit-breaker). A conflict with // a nil or non-infra Cause stays PlanMainMergeFailureConflict and routes // to the assembly_fixer exactly as before. PlanMainMergeFailureResolverInfraTransient // PlanMainMergeFailureResolverNoProvider — the merge_resolver dispatch // surfaced a no_provider error (PoolExhaustedError / spawn_picker_unavailable) // mid-retry: the OAuth pool exhausted before a session could spawn, NOT an // infra kill of a spawned session (SPEC-SOR-3197-v1 R4/R11). Maps from a // *github.MainMergeConflict whose Cause satisfies // ClassifyDispatchOutcome == DispatchOutcomeNoProvider — checked BEFORE // isMergeResolverInfraTransient at the runPlanMainMergeOffMain conflict arm, // since SpawnPickerUnavailable satisfies both. applyPlanMainMergeResult HOLDS // it: emit the reused pause event + re-queue the main-merge (which the // proactive gate holds again while the pool is unavailable) WITHOUT // incrementing MainMergeConsecutiveFailures / the infra-requeue counter and // WITHOUT routing through escalateSubject — the SOR-2991 / SOR-3155 // false-plan_blocked carve-out. PlanMainMergeFailureResolverNoProvider // PlanMainMergeFailureGateDeadlineKill — the per-push gate subprocess // was killed by its OWN phase-scoped deadline (mainMergePrePushGateTimeout) // rather than by an external OOM/host signal (SOR-2400). Maps from // *github.MainMergeGateDeadlineKill. Routes the plan DIRECTLY to // plan_blocked with the distinct gate_deadline_kill_detected event — NOT // through dispatchPlanMainMergeFailureToAssemblyFixer (a self-inflicted // timeout is not a code defect) and NOT through the env-transient class. // Does NOT count toward the M=3 counter (the plan is routed straight to // plan_blocked). PlanMainMergeFailureGateDeadlineKill )
type PlanMainMergeRepoResult ¶
type PlanMainMergeRepoResult struct {
Repo string
OldSHA string
NewSHA string
AlreadyUpToDate bool
FailureKind PlanMainMergeFailureKind
FailureReason string
ConflictingPaths []string
GateStdout []byte
GateStderr []byte
// GateExitCode is the failing gate subprocess's exit code, copied
// from MainMergeGateFailure.ExitCode. Populated only on a
// PlanMainMergeFailureGate result; threaded into
// ClassifyGateFailureEnvironment so a signal-terminated gate process
// (ExitCode() == -1) classifies as env-class rather than reaching
// assembly_fixer.
GateExitCode int
ResolverAttempts int
// DefaultBranch is populated only on conflict failures so the
// audit event can record the branch the conflict arose against.
DefaultBranch string
// RetryCount and TransientSignature are populated only on a
// PlanMainMergeFailureGateTransient classification. RetryCount
// carries the number of in-place gate retries the github-side
// retry loop attempted before exhausting the transient-class
// budget. TransientSignature is the matched substring from
// transientGateFailureSignatures so the audit event can name the
// failure class.
RetryCount int
TransientSignature string
// GateDeadlineKill is true when the main-merge gate was killed by its
// own phase-scoped deadline (mainMergePrePushGateTimeout) rather than
// by an external signal (SOR-2400). Maps from
// *github.MainMergeGateDeadlineKill; drives the
// PlanMainMergeFailureGateDeadlineKill routing in
// applyPlanMainMergeResult (→ plan_blocked, gate_deadline_kill_detected,
// never the assembly_fixer chain).
GateDeadlineKill bool
// FullResolverVerdict carries the FULL untruncated merge-resolver
// give-up reason threaded out of buildPlanMainMergeResolverClosure (the
// best available: the resolver's verbatim FailureReason prose on a
// genuine give-up, or the trimmed infra error on the infra-transient
// arm). applyPlanMainMergeResult persists it via
// captureAndPersistResolverDiagnosis on BOTH the genuine-conflict and
// infra-transient arms — the latter is the exact arm the SOR-2479 /
// SOR-2440 incident lost. Populated only on a conflict / infra-transient
// result (both set in the same errors.As conflict case); zero-valued
// elsewhere.
FullResolverVerdict string
}
PlanMainMergeRepoResult is one repo's outcome from a periodic main-merge episode (CIPB Phase C). Populated by runPlanMainMergeOffMain per repo and consumed by applyPlanMainMergeResult. Field naming follows the PlanCommitSHAs / LastMainMergeTimes convention — Times for timestamps, SHAs for commit hashes, etc. — so a future maintainer can't repurpose a SHAs field for branch names.
type PlanMainMergeResultEvent ¶
type PlanMainMergeResultEvent struct {
PlanKey string
PlanBranch string
RepoResults []PlanMainMergeRepoResult
// SourcePlanState is the plan state the dispatch was issued FROM.
// applyPlanMainMergeResult routes on it: the legacy periodic
// sweep dispatches from plan_awaiting_children (the default / zero-value
// path), the merge-time BEHIND/DIRTY self-heal dispatches from
// plan_pr_merge_pending_ci, and the mid-lifecycle drift-prevention sweep
// dispatches from a stuck state (plan_blocked / plan_auto_fixing). The
// dispatcher's state guard also checks the live plan state against this
// field so a dispatch issued from one state is dropped if an operator
// moved the plan before the session goroutine spawned.
SourcePlanState sm.PlanState
}
PlanMainMergeResultEvent is the EventPlanMainMergeResult payload — the typed per-repo aggregate of one dispatchPlanMainMerge invocation across every repo of a continuous_plan_branch planning issue. PlanKey identifies the plan; PlanBranch is the canonical plan- branch name the sweep targeted; RepoResults is the per-repo outcome slice in dispatch order.
type PlanMainMergeTipProbeResultEvent ¶
type PlanMainMergeTipProbeResultEvent struct {
PerRepoTips map[string]string // repoSlug → probed origin/<default> tip SHA
ProbeErrors map[string]string // repoSlug → probe error message (observability)
}
PlanMainMergeTipProbeResultEvent is the EventPlanMainMergeTipProbeResult payload (CIPB Phase C, SOR-2187). The off-main probe spawned by sweepPlanMainMerge fills PerRepoTips with each in-flight repo's freshly- probed origin/<default-branch> tip SHA (absent on a probe error) and ProbeErrors with the per-repo error string for observability. The main-loop handler applyPlanMainMergeTipProbeResult compares PerRepoTips against d.state.LastObservedMainTips and dispatches a main-merge only for plans on repos whose tip advanced.
type PlanMainMerger ¶
type PlanMainMerger func(ctx context.Context, repoSlug, planBranch string, resolver gh.ConflictResolver, clearGate gh.PreventiveRerereClearGate, builtins []gh.GeneratedArtifactGroup) (*gh.PlanMainMergeResult, error)
PlanMainMerger is the daemon's per-repo main-merge callback shape. Production wiring uses (*github.Client).MergeDefaultIntoPlanBranch via PlanMainMergerFnFromClient; tests substitute a fake. The resolver parameter is wired by runPlanMainMergeOffMain through buildCPBMergeResolverClosure, so the same merge_resolver retry machinery the per-child squash uses applies here byte-for-byte. The builtins parameter carries the daemon's built-in declared generated-artifact groups (currently just the traceability matrix) the merge seam prepends to the config-declared registry, so the matrix flows through the same generalized declared-artifact path as any config-declared artifact (SOR-3017, spec R7).
type PlanPROpenResult ¶
PlanPROpenResult is one repo's outcome from the plan-PR-open session (SOR-1139). URL is the gh-emitted plan PR URL on success; Err is non-nil when `gh pr create` failed for that repo.
type PlanPRPendingCIObservation ¶
type PlanPRPendingCIObservation struct {
Class pendingCIClassification
Reason string
Now time.Time
Timeout time.Duration
FailedChecks []FailedPlanPRCheck
StallChecks []FailedPlanPRCheck
}
PlanPRPendingCIObservation is the EventPlanPRPendingCIObserved payload. Class is the aggregate classification across the plan's PR set; Reason is the verbose per-PR diagnostic the audit-event message carries; Now is the poll instant (used by the budget gate); Timeout is the per-plan budget the poller carried into this tick; FailedChecks is the per-failure detail populated only when Class is pendingCIClassFailedCheck (SOR-1366) — the apply handler feeds it to synthesizePlanPRCIFailureVerdict so the autonomous fix loop can consume the failure as a refer-back. StallChecks is the (repo, head-SHA) detail the SOR-3152 stalled-required-check recovery ladder re-dispatches over; populated only when Class is pendingCIClassStillPending. Each entry reuses FailedPlanPRCheck (Repo + HeadSHA carry the ActionsRerunner targets); empty/nil for every other class.
type PlanRefactorMutationResult ¶
type PlanRefactorMutationResult struct {
Result httpapi.PlanRefactorResult
Err error
}
PlanRefactorMutationResult is the reply payload from the MutationRefactorPlan handler. Result carries the typed wire reply; Err carries any typed error.
type PlanReviewerDispatcher ¶
type PlanReviewerDispatcher func(ctx context.Context, task role.PlanReviewerTask) (role.PlanReviewerVerdict, error)
PlanReviewerDispatcher runs one plan-review task and returns the reviewer's verdict. Used by dispatchPlanner between planner success and Linear writes.
type PlanTransitionEffects ¶
type PlanTransitionEffects struct {
To sm.PlanState
Actor string
Reason string
Force bool
ClearSession bool
EventKind string
EventMessage string
Comment string
Escalation *store.Escalation
// EventPayload is the typed Planning-SM event a caller built via a
// generated store.New<Kind> constructor (events.gen.go); same
// supersedes-legacy contract as IssueTransitionEffects.EventPayload.
EventPayload store.Event
// BlockedReason, TriagerBlockedAt, TriagerBlockedReason carry the same
// post-transition metadata as IssueTransitionEffects' equivalents (see
// that struct's docstring for the semantics). The chokepoint writes
// them onto *iss after the PlanState mutation so a non-empty value
// wins over the SM's auto-derived BlockedReason on plan_blocked
// targets; zero-value leaves the field unchanged. The cluster-1
// rollback covers them via transitionFieldSnapshot.
BlockedReason string
TriagerBlockedAt time.Time
TriagerBlockedReason string
// From is the PlanState the issue was in before queuePlanTransition
// performed the in-place SM mutation. Same semantic as
// IssueTransitionEffects.From for the Issue-SM path.
From sm.PlanState
// SetPlanImplementationCompleteAt seeds the CIPB implementation-complete
// marker (PlanImplementationCompleteAt) in the same atomic transition the
// caller drives. Used by the SOR-2214 cascade-missed backstop in
// dispatchFollowOnPlanning, which seeds the marker AND transitions a
// durable-complete CIPB plan plan_awaiting_children → plan_integrating
// outside the cascade's seed+transition batch — the chokepoint writes it
// AFTER the rollback snapshot so a failed persist restores the pre-transition
// value (0). Zero leaves the column untouched (the common case for every
// other edge). The cascade's own normal-path seed is a direct write captured
// by transitionFieldSnapshot.planImplementationCompleteAt, not this field.
SetPlanImplementationCompleteAt int64
// CommentRow is an optional issuestore.Comment that callers want
// committed inside the same transaction as the issue-row mirror +
// history row. Same semantic as IssueTransitionEffects.CommentRow.
CommentRow *issuestore.Comment
}
PlanTransitionEffects mirrors IssueTransitionEffects for the Planning Issue SM (state-machines.md §3). Same composition rules — preserve WIP is never set (Planning Issues have no worktree); CleanWorktree likewise. The other fields (comment / event / escalation / session_id clear) work the same way.
type PlannerDispatcher ¶
type PlannerDispatcher func(ctx context.Context, task role.PlannerTask) (role.PlannerResult, error)
PlannerDispatcher runs one planner task and returns the parsed PlannerResult.
type PlannerInputs ¶
PlannerInputs is the project-level context the supervisor passes to PlannerTask on every dispatch. Lives on Config; the supervisor snapshots into the task on the main goroutine.
type Poller ¶
type Poller struct {
// contains filtered or unexported fields
}
Poller drives one external-state poll on a `time.Ticker`. The producer emits an Event onto a shared channel after each successful run.
Pollers don't take SM locks; they push events and let the main loop apply changes. This keeps poller goroutines decoupled from the rest of the daemon — they only need a channel and a function to call.
func NewPoller ¶
func NewPoller(cfg PollerConfig) *Poller
NewPoller constructs a Poller (not started). Call Start to launch the goroutine.
func (*Poller) ApplyProjectState ¶
func (p *Poller) ApplyProjectState(state sm.ProjectState)
ApplyProjectState lets the daemon flip the poller's Active flag in response to a Project SM transition. If the poller was constructed with PollerConfig.ActiveWhen, that policy decides whether the poller is active for the new state; otherwise the call is a no-op (pollers that always run at a fixed cadence — e.g. github-prs — leave ActiveWhen unset).
func (*Poller) Cadence ¶
Cadence returns the currently-configured cadence pair. Used by poller-construction tests to verify the configured ActiveCadence / IdleCadence reached the poller's internal state without exposing the state struct itself.
func (*Poller) LastError ¶
LastError returns the most recent error from pollFn, or nil if the last run succeeded.
func (*Poller) RunCount ¶
RunCount returns the total number of pollFn invocations since Start. Used by tests; in production a periodic events.log emission carries the same metric.
func (*Poller) SetActive ¶
SetActive toggles the active flag. true → next fire uses the active cadence and the goroutine wakes immediately (so a paused poller activated mid-wait doesn't have to ride out the old delay); false → idle cadence (or pause if idle == 0) on the next firing.
func (*Poller) SetCadence ¶
SetCadence updates both intervals. Active fires that haven't happened yet pick up the new value automatically; in-flight pollFn calls run to completion.
func (*Poller) SetPanicSink ¶
SetPanicSink installs the callback invoked with (name, cause) on every supervised restart. Call before Start. nil-safe. The daemon wires this on every poller it adopts so a panicked poller emits a poller_restarted audit row (SOR-2630).
func (*Poller) SetRestartBackoff ¶
SetRestartBackoff overrides the initial post-panic backoff. Call before Start. Tests set a small value to keep restart-loop runtime down; production leaves the supervisorInitialBackoff default.
func (*Poller) Start ¶
Start launches the poller goroutine. The goroutine runs until Stop is called or ctx is canceled.
Implementation: a single time.Timer that's reset after each iteration. We use Timer rather than Ticker so cadence changes (SetActive transitions) take effect on the next firing without losing alignment.
func (*Poller) Stats ¶
func (p *Poller) Stats() PollerStats
Stats returns a runtime snapshot of the poller. Safe to call from any goroutine.
func (*Poller) Stop ¶
func (p *Poller) Stop()
Stop signals the goroutine to exit and waits for it. Idempotent.
func (*Poller) Trigger ¶
func (p *Poller) Trigger()
Trigger wakes the poller goroutine immediately so the next firing happens within one event-loop hop rather than waiting out the configured Active/Idle interval. Used as a public seam for external goroutines that need to flag a fast-track tick — e.g. the daemon's triager executor calls Trigger on the recognizer poller when it emits an `escalate_to_recognizer` action so the recognizer reacts to the wedge in seconds rather than waiting for the next hourly firing (SOR-147).
Semantics:
- If the poller is currently active (`shouldFire()` returns true), the kick causes an immediate `pollFn` invocation.
- If the poller is paused (idle-cadence == 0 and active == false), the kick is dropped — the predicate that made the poller paused is unchanged and the next firing still waits for SetActive(true).
- Multiple Triggers within one event-loop hop collapse to one wake (the kick channel has buffer 1).
Trigger is safe to call from any goroutine.
type PollerConfig ¶
type PollerConfig struct {
// Name is operator-readable, used in events.log records.
Name string
// Cadence is the active and idle interval. Idle==0 → paused when not active.
Cadence Cadence
// Out is the daemon's event channel; the poller emits SuccessEvent() here on every successful poll.
Out chan<- Event
// PollFn does the actual work (HTTP / GraphQL call). Called with the
// daemon's context; should respect cancellation. Errors are stored on
// the poller for inspection but DO NOT halt the poller — the daemon
// keeps polling on the next firing.
PollFn func(ctx context.Context) error
// SuccessEvent constructs the event to emit after a successful poll.
SuccessEvent func() Event
// InitialActive sets the active flag at construction.
InitialActive bool
// ActiveWhen, when set, makes the poller's active flag track the
// Project SM. The daemon calls ApplyProjectState once at boot and
// after every Project SM transition with the new state; the poller
// invokes ActiveWhen(state) and forwards the result through
// SetActive. Wire this for pollers whose cadence should flip with
// project activity (e.g. the recovery-sweep poller). nil leaves
// the poller's active flag pinned to InitialActive.
ActiveWhen func(sm.ProjectState) bool
// NextActiveOverride, when set, runs on every nextDelay() and may
// return a one-shot replacement for the configured Active
// cadence. The closure runs outside the poller's state mutex so
// it may do bounded I/O (e.g. a SQLite count query); returning
// (d, true) makes the next firing use d, returning (_, false)
// leaves the configured Active cadence in force. Wire this for
// pollers whose active-state cadence depends on out-of-band
// signal — currently used by the recognizer poller to drop to
// the escalation floor while un-actioned triager escalations
// exist (SOR-147).
NextActiveOverride func() (time.Duration, bool)
}
PollerConfig collects everything needed to construct a Poller.
type PollerStats ¶
type PollerStats struct {
Name string
Active bool
ActiveS float64 // cadence.Active in seconds
IdleS float64 // cadence.Idle in seconds
LastRun time.Time
LastError string // empty when last run succeeded
RunCount uint64
}
Stats is a snapshot of one poller's runtime state for the health endpoint. All fields are cheap to read; the caller serializes into JSON.
type PrePushGateVerifier ¶
type PrePushGateVerifier struct {
Timeout time.Duration
// GoBuildCacheMaxBytes is the host go-build cache size cap this
// verifier enforces around its gate subprocess via
// gh.PrepareGoBuildCache (SOR-2537). Zero disables the prune (the
// zero-value / test default). Production wires it from
// cleanup.go_build_cache_max_gb via the constructor.
GoBuildCacheMaxBytes int64
// EventSink, when non-nil, receives the go_build_cache_pruned audit
// event when the bound prunes the cache. Production plumbs it to the
// issuestore (the same SaveEvent closure the github.Client uses); a
// nil sink (the test default) silently drops the event.
EventSink func(store.Event)
// PreConfiguredGate is the managed project's config-sourced pre-push gate
// command (config.ProjectPrePushGate(cfg)). It is threaded into
// gh.DiscoverComposedGate: a non-empty value runs as the single gate (with
// cwd = the worktree); empty falls back to the per-language composed gate or
// the language-native auto-detected default. The zero value (test default)
// keeps the verifier on auto-detect.
PreConfiguredGate string
// PerLanguageGates is the resolved per-declared-language gate set
// (config.ResolveLanguageGates, converted), threaded into
// gh.DiscoverComposedGate so a multi-language project's verify runs only the
// gates owning a file the child's diff touched (SPEC-SOR-3055-v1 R5-R8). nil
// (the zero / test default) keeps the verifier byte-identical to the
// single-command / auto-detect behavior; PreConfiguredGate, when set, still
// wins. Production wires it from cmd/sorcererd/start.go via
// WithPerLanguageGates (the constructor signature is unchanged so the existing
// test callsites compile untouched).
PerLanguageGates []gh.LanguageGateEntry
}
PrePushGateVerifier is the first concrete Verifier: runs the same pre-push gate discovery shape internal/github/plan_assemble.go's discoverPrePushGate exposes (now re-exported as gh.DiscoverPrePushGate) against the ephemeral verifier worktree the chain runner already carved. A non-zero exit yields VerifierResult.Passed=false with the captured stdout/stderr; a clean exit (or a missing gate — the worktree has nothing to gate against) yields Passed=true.
Per-verifier wall-clock timeout is configurable; the zero-value / constructor default is DefaultPrePushGateTimeout. A timeout fires `context.DeadlineExceeded` from cmd.Run; the verifier treats that as Passed=false with the cause string projected onto Stderr (same shape a non-zero exit would produce so the synthetic concern that rides into the next feedback envelope is uniform).
func NewPrePushGateVerifier ¶
func NewPrePushGateVerifier(goBuildCacheMaxBytes int64, sink func(store.Event), preConfiguredGate string) *PrePushGateVerifier
NewPrePushGateVerifier constructs the verifier with the default timeout. The constructor exists so cmd/sorcererd/start.go's wiring uses the same shape every other Config.* hook follows (a typed constructor rather than a struct literal). goBuildCacheMaxBytes + sink wire the SOR-2537 host go-build-cache bound around the gate subprocess; pass (0, nil) to leave the prune disabled. preConfiguredGate is the project's config-sourced pre_push_gate command (config.ProjectPrePushGate); pass "" to leave the verifier on the language-native auto-detected default.
func (*PrePushGateVerifier) Name ¶
func (v *PrePushGateVerifier) Name() string
Name returns the stable identifier the chain runner emits on per-verifier audit events and weaves into the synthetic concern surface.
func (*PrePushGateVerifier) Run ¶
func (v *PrePushGateVerifier) Run(ctx context.Context, worktreePath string, env VerifierEnv) (VerifierResult, error)
Run discovers the worktree's pre-push gate, invokes it as a single subprocess with stdin sealed and stdout/stderr captured, and maps the exit code onto VerifierResult.Passed. A worktree with no discoverable gate (no `.git/hooks/pre-push`, no `scripts/pre-push-gates.sh`) is treated as Passed=true — there is no gate to enforce, so there is no gate to fail. This matches the existing behavior in cpb's per-child squash gate (plan_branch_squash_child.go's cpbRunGateAndPush only runs the gate when discoverPrePushGate's `found` is true).
func (*PrePushGateVerifier) WithPerLanguageGates ¶
func (v *PrePushGateVerifier) WithPerLanguageGates(gates []gh.LanguageGateEntry) *PrePushGateVerifier
WithPerLanguageGates returns a copy of v whose Run resolves the composed, changed-files-scoped gate (gh.DiscoverComposedGate) over gates — the resolved per-declared-language gate set (SPEC-SOR-3055-v1 R5-R8). The constructor (NewPrePushGateVerifier) signature is intentionally unchanged so the large existing test corpus compiles untouched; production chains this builder in cmd/sorcererd/start.go. The slice is copied so the builder composes safely.
type PredictionCheckAnchorClass ¶
type PredictionCheckAnchorClass string
PredictionCheckAnchorClass labels one anchor's classification when the predicted anchor list is compared against the actual diff's anchor heuristic. anchor_match means the predicted anchor resolves in the post-diff file content; anchor_drift means the anchor was declared but the diff hit a different anchor of the same file. Phase A surfaces both counts in the prediction_check message; the drift detection is best-effort (the per-language heuristic may miss).
const ( // PredictionAnchorMatch — the predicted anchor resolves in the // file's post-diff content. The planner's region prediction held. PredictionAnchorMatch PredictionCheckAnchorClass = "anchor_match" // PredictionAnchorDrift — the predicted anchor does NOT resolve // in the file's post-diff content (the diff probably moved or // renamed the region). The implementer might still have done the // right work in a different region; soft-signal only. PredictionAnchorDrift PredictionCheckAnchorClass = "anchor_drift" )
type PredictionCheckFileClass ¶
type PredictionCheckFileClass string
PredictionCheckFileClass labels one file's classification when the actual implementer diff is compared against the planner's predicted_footprint. Phase A of CIPB (docs/proposals/continuous-integration-architecture.md) records the counts in the prediction_check event message so Phase D's learning loop has the data when the recognizer learns to nudge planner drift.
const ( // PredictionFileMatch — the file appears in BOTH the prediction // and the actual diff. The planner called it right. PredictionFileMatch PredictionCheckFileClass = "match" // PredictionFileUnderPredicted — the file appears in the actual // diff but NOT in the prediction. The planner missed a touch. PredictionFileUnderPredicted PredictionCheckFileClass = "under_predicted" // PredictionFileOverPredicted — the file appears in the // prediction but NOT in the actual diff. The planner over- // declared (touched_files or created_files entry the implementer // did not need). PredictionFileOverPredicted PredictionCheckFileClass = "over_predicted" )
type PredictionCheckPerAnchor ¶
type PredictionCheckPerAnchor struct {
Path string `json:"path"`
Anchor string `json:"anchor"`
Class PredictionCheckAnchorClass `json:"class"`
Reason string `json:"reason,omitempty"`
}
PredictionCheckPerAnchor is one row in the per-anchor details slice.
type PredictionCheckPerFile ¶
type PredictionCheckPerFile struct {
Path string `json:"path"`
Class PredictionCheckFileClass `json:"class"`
}
PredictionCheckPerFile is one row in the per-file details slice.
type PredictionCheckResult ¶
type PredictionCheckResult struct {
ChildKey string `json:"child_key"`
FileMatch int `json:"file_match"`
FileUnder int `json:"file_under_predicted"`
FileOver int `json:"file_over_predicted"`
AnchorMatch int `json:"anchor_match"`
AnchorDrift int `json:"anchor_drift"`
PerFile []PredictionCheckPerFile `json:"per_file"`
PerAnchor []PredictionCheckPerAnchor `json:"per_anchor,omitempty"`
SkippedReason string `json:"skipped_reason,omitempty"`
Repos []string `json:"repos,omitempty"`
GeneratedAtSec int64 `json:"generated_at_sec,omitempty"`
// contains filtered or unexported fields
}
PredictionCheckResult is the per-child classification computed by classifyPredictionCheck. The daemon emits one event from the result (the message body carries the JSON-encoded summary so operators can grep by counts; the structured details ride in the message body's `details=` suffix as JSON for downstream consumers).
func ClassifyPredictionCheck ¶
func ClassifyPredictionCheck(child *sm.Issue, diffFiles []string, fileContent map[string][]byte) PredictionCheckResult
ClassifyPredictionCheck is the pure-function classifier EmitPredictionCheck calls. Exported so tests can exercise the classification logic without a Daemon. Returns a result with SkippedReason non-empty when the prediction is empty (the planner didn't declare any footprint — legacy pre-Phase-A plan or a validator-let-through edge case).
type ProbeContext ¶
type ProbeContext struct {
WorktreePath string
RepoSlug string
MergeSHA string
DispatchRef string
DispatchedAt time.Time
SpecID string
RequirementID string
CIChecker CIRunChecker
LLMRunner LLMVerifierRunner
}
ProbeContext carries the per-evaluation context an activation probe evaluates against: the post-merge checkout the deterministic probes read, the repo the ci_run probe keys on, and the optional CI / LLM handles. For a dispatch_workflow ci_run probe, DispatchRef + DispatchedAt identify the daemon-dispatched run the probe binds to (SPEC-SOR-3247); MergeSHA is retained for logging/evidence only and no longer gates ci_run selection. The SpecID / RequirementID identify the requirement for an llm_judged probe's oracle call. A nil CIChecker / LLMRunner disables only that probe path (ci_run → pending; llm_judged → keep the deterministic result).
type PropTestStubVerifier ¶
type PropTestStubVerifier struct{}
PropTestStubVerifier is the deterministic daemon-side gate that enforces SPEC-RIDTRACE R3: a spec-driven implementation covering a `prop_test`-mode requirement must NOT pass review unless that requirement's `Property<RID>` predicate helper is implemented in the per-spec `gen` package and compiles.
Under SOR-2742 the per-spec property-test stub (proptest_stubs_test.go) is no longer committed by the implementer — it is a daemon-generated out-of-footprint artifact produced as the UNION of every sibling child's R-IDs at the plan-branch mutation seam (generatePropTestStubUnion). The implementer instead shards its Property<RID> helpers one file per requirement (stubname.HelperFilePath). So this gate no longer searches for a committed stub in the worktree; it compiles a transient throwaway probe per dispatched R-ID that references `gen.Property<RID>` and runs `go vet` against it. A probe that fails to compile — the gen package is absent, the Property<RID> helper is missing or renamed, or the gen package does not build — is the offense.
It is the reviewer-side complement to the runtime invariant — the gate blocks at the review seam, the invariant detects a regression in steady state.
The verifier reads the issue's bound spec + verifies list the same way resolveGeneratedTestStubs does (the chain runner pre-resolves the spec into env.Verifies + env.SpecBodyYAML before the per-repo loop). A non-spec-driven impl, an empty verifies list, or one whose cited R-IDs carry no `prop_test` mode all yield Passed=true with no filesystem inspection, so legacy and manual-mode work is unaffected.
A missing or non-compiling helper returns Passed=false with the per-R-ID diagnostic on Stderr; the chain runner short-circuits and the supervisor routes the issue back through the existing refer-back machinery (applyImplementerVerifyChainResult → synthetic concern → feedback) before it reaches the reviewer.
func NewPropTestStubVerifier ¶
func NewPropTestStubVerifier() *PropTestStubVerifier
NewPropTestStubVerifier constructs the verifier. The constructor exists so cmd/sorcererd/start.go's chain wiring uses the same typed-constructor shape NewPrePushGateVerifier follows.
func (*PropTestStubVerifier) Name ¶
func (v *PropTestStubVerifier) Name() string
Name returns the stable identifier the chain runner emits on per-verifier audit events and weaves into the synthetic concern surface.
func (*PropTestStubVerifier) Run ¶
func (v *PropTestStubVerifier) Run(ctx context.Context, worktreePath string, env VerifierEnv) (VerifierResult, error)
Run gates the worktree for the prop_test helper contract. It no-ops (Passed=true) for any non-spec-driven impl or one whose cited R-IDs carry no `prop_test` mode; otherwise it checks, per prop_test R-ID, that the Property<RID> helper is implemented in the per-spec gen package and compiles.
The property-test backend is resolved PER REQUIREMENT from that requirement's own traced target language (resolveRIDInterp → ResolveRequirementBackend), not from a single project-wide env.ProjectLanguage: langprofile.ForFileExtension on a traced target file gives the language whose .ID keys proptestbackend.ForLanguage. This realizes SPEC-SOR-3054-v1 R6 (propTestBackendLang(r) = requirementLang(r)) — a requirement traced to a .go file is gated by the Go compile probe even in a Rust project, and a requirement traced to a .rs file by the Rust native suite. Requirements are grouped by the backend that resolved: those carrying the Go go_vet compile probe run through the per-R-ID probe loop, every other backend runs its native verify suite once in the worktree root. A requirement with no traced target falls back to env.ProjectLanguage (zero value → "go") for back-compat with un-traced / legacy rows.
type RecoverEvent ¶
type RecoverEvent struct {
Kinds []string
Reply chan<- httpapi.RecoverResponse
}
RecoverEvent is the typed payload of EventRecoverySweep. The HTTP handler and the periodic recovery-sweep poller both push events of this shape onto the daemon's main loop. Kinds limits which sweeps run (empty = all); Reply (when non-nil) receives the response envelope so the HTTP handler can serialize it. The poller submits a fire-and-forget event with Reply == nil — the heartbeat row is recorded by the handler regardless.
type RequirementExecutor ¶
type RequirementExecutor interface {
RunForRequirement(ctx context.Context, worktreePath string, env VerifierEnv, rid string) (VerifierResult, error)
}
RequirementExecutor runs the executable verification for a single spec requirement against the implementer's pushed worktree. One executor is registered per executable verification_mode (prop_test / structural / type / benchmark / llm_test); the mode→executor routing is owned by the dispatch registry, so an executor's RunForRequirement is invoked only for an R-ID whose mode resolved to it.
An executor returns Passed=false (with its diagnostic on Stderr) for a requirement its check rejects, and a non-nil error only for an unrecoverable plumbing failure — the same VerifierResult contract the chain runner applies to a top-level Verifier.
func RequirementExecutorFor ¶
func RequirementExecutorFor(mode string) (RequirementExecutor, bool)
RequirementExecutorFor returns the executor registered for mode and whether one exists. manual / the empty string / any executable mode with no registered executor (a reserved-but-not-yet-implemented mode) all return (nil, false) — i.e. not-live, no executor routed.
type ReviewerDispatcher ¶
type ReviewerDispatcher func(ctx context.Context, task role.ReviewerTask) (role.ReviewerVerdict, error)
ReviewerDispatcher runs one reviewer task and returns a typed verdict.
type RouteAction ¶
type RouteAction string
RouteAction is the action a CIPB integration-dispatch seam takes for one dispatch situation (SPEC-SOR-3197-v1). It is the policy output of the single total decision function RouteDispatchSituation: exactly one of Hold (no budget increment, no escalation), CountRetry (consume one bounded infra-transient budget unit), or Escalate (route the plan to a human-block state through escalateHumanBlock).
This RouteAction is a DISTINCT domain from the untyped string constants RouteActionLocal / RouteActionForward / RouteActionReject in route.go (those are the cross-daemon request-routing decision); the names share a "RouteAction" prefix but never collide — this is a named type, those are package-level untyped string constants, and the two consequent sets are disjoint.
const ( // RouteActionHold neither increments any retry / liveness budget nor // escalates: the dispatch is simply suppressed and re-attempted later // when the holding condition clears. It is the ONLY action with that // property, and is the action for both the gate-held and no_provider // situations — the SOR-2991 / SOR-3155 false-plan_blocked carve-out. RouteActionHold RouteAction = "hold" // RouteActionCountRetry consumes one unit of a bounded infra-transient // retry / liveness budget (the assembly-fixer infra budget, the // main-merge / per-child merge-resolver infra budget, etc.) and // re-attempts. It is the non-exhausted arm of a genuine // infrastructure_transient situation. RouteActionCountRetry RouteAction = "count_retry" // RouteActionEscalate routes the plan to a human-block state through the // escalateHumanBlock chokepoint. It fires only for a genuine outcome or // for an infrastructure_transient situation that has exhausted its // bounded budget — preserving the SPEC-SOR-2782 / // pre_completion_main_merge_liveness_bounded bound for genuine infra // trouble. It NEVER fires for gate-held or no_provider. RouteActionEscalate RouteAction = "escalate" )
func RouteDispatchSituation ¶
func RouteDispatchSituation(sit DispatchSituation, budgetExhausted bool) RouteAction
RouteDispatchSituation is the single, total SPEC-SOR-3197-v1 routing policy: it maps every (DispatchSituation, budgetExhausted) pair to exactly one RouteAction. It is pure and additive — it wires no seam and consults no pool or classifier state; the mapping from a real dispatch attempt to a DispatchSituation value is each seam's responsibility (a later SOR-3197 child). Centralizing the decision here means the budget / escalation policy lives in exactly one place rather than being re-decided inline at each of the three CIPB post-completion integration seams.
The truth table:
GateHeld, any -> Hold NoProvider, any -> Hold InfraTransient, budget left -> CountRetry InfraTransient, budget gone -> Escalate Genuine, any -> Escalate
So Hold is the only action that neither increments a budget nor escalates, and both GateHeld and NoProvider route to Hold (R2 / R3 / R6 / R7); Escalate fires only for InfraTransient-with-budgetExhausted or Genuine, never for GateHeld and never for NoProvider (the R14 Escalate-arm mechanism); InfraTransient is CountRetry until its budget is exhausted and Escalate after (R9 / R10), preserving the bounded SPEC-SOR-2782 liveness escalation.
The default arm catches DispatchSituationGenuine and, deliberately, any unknown situation value: an unrecognized situation escalates (safe-fail) rather than silently falling into Hold or CountRetry.
Exported so the SPEC-SOR-3197-v1 prop-test helpers (the external gen package) can anchor on the production decision function directly — the same export-for-prop-test pattern as ClassifyDispatchOutcome and IntegrationDispatchGateHeld.
type RouteDecision ¶
type RouteDecision struct {
Action string
TargetDaemon string
TargetURL string
Rationale string
OverrideEvidence []string
}
RouteDecision is the resolved routing outcome for one inbound request. The next-phase wire-up consumes it: Local handles the request in-process; Forward proxies to TargetURL with the named TargetDaemon as the audit attribution; Reject returns the Rationale to the caller. Rationale is both human-readable (operator diagnostics) and machine-loggable (verbatim into the route_resolved audit event's Message field, so one short line, no embedded newlines).
OverrideEvidence is non-nil only when the body-content-vs-declared- repos conflict detector (SOR-1218 Phase 3) fired an override against the existing repos-field decision. The slice carries the matched path prefixes (or the ambiguous-case multi-daemon match union) so the calling resolveCreateDestination can emit the route_repos_conflict audit event and surface the evidence in the operator-visible warning.
func ResolveDestination ¶
func ResolveDestination(body string, repos []string, topology *Topology) (RouteDecision, error)
ResolveDestination decides where one inbound request should run. The body parameter feeds the SOR-1218 Phase 3 body-content-vs-declared- repos conflict detector, which runs AFTER the repos-field decision has been picked and may override it on a body-content mismatch.
Algorithm (in evaluation order):
(b) len(repos) == 0 → Local (default rationale)
(s) all repos self-owned (cfg) → Local (SOR-1464 self-knowing path)
(a) topology.IsStale() true → Local (stale-rationale)
(c) primary repos[0] no owner → Reject (no-owner rationale)
(g) multi-repo split owners → Reject (span rationale)
(d) primary owned by self → Local
(e/f) primary owned by other → Forward (single-repo or multi-repo same-owner)
(h) Phase 3 body-vs-repos check → may flip a local/forward decision
to forward/local against the
suggested daemon, or to reject on
an ambiguous match (skipped on
the self/stale/empty/reject branches).
(s) is the SOR-1464 self-knowing short-circuit: a daemon learns its OWN repos from cfg.Repos at construction, so it routes its own work locally WITHOUT consulting the async topology cache. The cache is the source of truth ONLY for cross-daemon (non-self) repos. This runs BEFORE the IsStale check so a self-owned create succeeds even when the cache is empty / never-refreshed (defense-in-depth: the same class of startup race that mis-rejected etherpilot-ai/archers in production). Multi-repo issues that mix a self-owned repo with a peer repo do NOT match (s) — they fall through to the cache-based span check (g).
The error return is reserved for genuinely unexpected conditions (nil topology pointer, etc.) — the three valid Actions (local / forward / reject) are NOT errors. The signature carries error for future extensibility (e.g. when Phase 5's body classifier adds an external- call shape that can fail); for now, every well-defined branch returns nil.
type RoutePrepareMoveResult ¶
RoutePrepareMoveResult is the payload sent back on Mutation.RoutePrepareReply (SOR-1278). On success: AuditID is the route_operator_move_initiated event id (which the operator goroutine reuses on the X-Sorcerer-Route-Audit-ID forward header so the peer's SOR-1216 idempotence channel dedups retries), and ForwardBody is the JSON-marshaled IssueCreateInput the operator goroutine POSTs to the peer. On failure: Err carries the typed rejection (*RouteMoveRefusedError for live-session / non-abandonable, ErrNotFound for missing key) and AuditID / ForwardBody are zero. The handler is the sole site that emits route_operator_move_initiated, so a failed prepare leaves NO audit row for the move (the operator-side rejection is the only signal).
type SchedulerPass ¶
SchedulerPass is the function the daemon runs whenever a relevant event indicates the ready-leaf set may have changed. It does the work described in architecture.md § 9 "What runs in the scheduler pass": reconcile pending state-change events, rebuild the dep graph, pick ready leaves, dispatch implementer/reviewer/merger sessions, run cleanup, persist state.
The function takes a context so cancellation propagates cleanly through any I/O the pass triggers (gh calls, Linear writes).
Return value is informational — the daemon logs failures but does not halt; the next pass will retry whatever broke.
type SessionInitFailureEvent ¶
type SessionInitFailureEvent struct {
SessionID string
IssueKey string
Role string
Sub string
Reason string
APIErrorStatus int
Detail string
}
SessionInitFailureEvent is the EventSessionInitFailed payload: the clauderunner observed claude exit non-zero at session init (SOR-1265). Reason names the structured classification (e.g. `claude_api_error_429`, `claude_init_error`); APIErrorStatus carries the HTTP-style status claude reported in its result frame (0 when absent). Pure telemetry — the typed *agentcli.SessionInitError returned synchronously from runner.Run still drives the supervisor's permanent / transient classification. Defined here rather than reusing agentcli.SessionInitFailure so events.go takes no clauderunner import; the supervisor maps the two at the OnSessionInitFailed hook seam.
type SpawnFailureEvent ¶
type SpawnFailureEvent struct {
SessionID string
IssueKey string
Role string
Class string
Sub string
Error string
}
SpawnFailureEvent is the EventSessionSpawnFailed payload: the clauderunner reported a spawn failure before the `claude` subprocess started. Class is one of the documented clauderunner spawn-failure classes (spawn_fork_exec_failed, spawn_picker_unavailable, etc.). Pure telemetry — the handler appends a session_spawn_failed audit row and does not touch the Session SM (SOR-1154 owns the session_aborted transition). Defined here rather than reusing agentcli.SpawnFailure so events.go takes no clauderunner import; the supervisor maps the two at the OnSpawnFailed hook seam.
type SpecDrafterDispatcher ¶
type SpecDrafterDispatcher func(ctx context.Context, task role.SpecDrafterTask) (role.SpecDrafterResult, error)
SpecDrafterDispatcher runs one spec_drafter task (TASK: draft / amend) and returns the SHAPE-validated draft. The daemon holds the closure role.NewSpecDrafterDispatcher produced; nil disables the dispatch.
type SpecReviewerDispatcher ¶
type SpecReviewerDispatcher func(ctx context.Context, task role.SpecReviewerTask) (role.SpecReviewerVerdict, error)
SpecReviewerDispatcher runs one spec_reviewer coherence-judge task (TASK: spec_review) and returns the typed coherence verdict. The daemon holds the closure role.NewSpecReviewerDispatcher produced; nil disables the dispatch.
type SpecVerificationResultEvent ¶
type SpecVerificationResultEvent struct {
PlanningKey string
SpecID string
Version int64
Result verifier.Result
VerifierErr error
Site specVerifySite
ChangedRequirementIDs []string
}
SpecVerificationResultEvent is the EventSpecVerificationResult payload. The off-main worker (spawnOrSkipSpecVerify) snapshots the verifier inputs on the main loop, runs SpecVerifier on a session goroutine, and submits this struct for applySpecVerificationResult to apply on the main loop. The worker writes neither d.state nor the issuestore — it only Submit()s this result.
PlanningKey / SpecID / Version identify the verified spec version. Result is the verifier's findings + timing; VerifierErr is the verifier's error return (reserved for nil-argument misuse — a non-nil value leaves the issue at spec_verifying for operator inspection). Site selects the lifecycle vs operator-diagnostic routing. ChangedRequirementIDs is non-empty only on a scoped re-verify (the patched-spec path): when set, the lifecycle arm persists fresh findings only for the changed requirements plus any requirement-ID-less finding the carry-forward did NOT already preserve — a requirement-ID-less finding the carry-forward carried forward is skipped so it neither double-counts nor un-suppresses a carried suppression; empty means a full re-verify that persists every finding.
type SpecVerifier ¶
type SpecVerifier func(ctx context.Context, spec *dsl.Spec, finalPreApproval bool, priorSMTLib string, priorBMCBound int) (verifier.Result, error)
SpecVerifier runs the Phase-A SMT verification pipeline over a parsed spec and returns the structured findings. Injected so the draft→verify lifecycle is testable without a live Z3 subprocess; production wires verifier.Verify over a shared smt.Driver.
finalPreApproval marks the verification that gates the spec's advance toward approval (the spec_verifying → spec_review transition: the draft/amend persist-verify, the structural-amend re-verify, and the patched-spec re-verify). The production wiring (start.go) turns the CVC5 differential oracle on ONLY for that position when specs.differential_oracle is set — never on the operator's non-transitioning diagnostic re-verify (`sorcerer spec verify`), which passes false. Test fakes ignore it.
priorSMTLib is the prior spec version's rendered SMT-LIB (resolved via the spec row's previous_version_id) for the incremental verdict cache (SOR-2206): a query unit whose SMT-LIB substring is byte-unchanged from the prior version reuses its (clean) verdict and skips the solver. Empty on a first draft (no prior version) and on the diagnostic re-verify (no reuse). Production wiring threads it onto verifier.Options.PriorSMTLibText; test fakes ignore it.
priorBMCBound is the prior spec version's effective BMC unrolling bound (verifier.EffectiveBMCBound over the prior parsed spec). It rides alongside priorSMTLib because the bound is NOT part of the rendered SMT-LIB — establishBMC applies it procedurally — so a bmc_k-only amendment leaves priorSMTLib byte-identical to the current script and would falsely reuse the prior, shallower-K verdicts unless the bound distinguishes them. Resolved daemon-side via priorVersionReuseInputs; 0 when there is no prior version or the prior spec is a snapshot model. Production wiring threads it onto verifier.Options.PriorBMCBound; test fakes ignore it.
type StalledCheckRecoveryKind ¶
type StalledCheckRecoveryKind int
StalledCheckRecoveryKind enumerates the recovery action the daemon takes for a stalled required check. It is the codomain of DecideStalledCheckRecovery.
const ( // StalledCheckRedispatch is rung 1: the stalled check has not yet been // re-dispatched, so the daemon re-runs it (off-main, via the // ActionsRerunner closure) and keeps the plan in plan_pr_merge_pending_ci // for a bounded chance to take effect. StalledCheckRedispatch StalledCheckRecoveryKind = iota // StalledCheckEscalate is rung 2: a re-dispatch was already attempted and // the check is still stalled, so the daemon escalates the plan through the // standard escalation chokepoint (escalateSubject) — a check that never // terminates even after a re-dispatch needs operator action. StalledCheckEscalate )
func DecideStalledCheckRecovery ¶
func DecideStalledCheckRecovery(redispatchAttempted bool) StalledCheckRecoveryKind
DecideStalledCheckRecovery decides a stalled required check's recovery KIND solely from the language-agnostic boolean discriminant redispatchAttempted (spec SPEC-SOR-3059-v1 R3). It takes NO project-language, build-system, or check-name input by construction, so two stalled required checks with identical such state receive the identical recovery action regardless of project language: a Rust `cargo test` and a Go `go test` that hang identically follow the identical code path (the product-self-boundary / W5 property). When the stalled check has NOT yet been re-dispatched (redispatchAttempted == false) the recovery is rung 1 (re-dispatch); once a re-dispatch was attempted (redispatchAttempted == true) it is rung 2 (escalate). The function is a deterministic pure function of its single boolean argument — it reads no clock, no config, and no global state.
func (StalledCheckRecoveryKind) String ¶
func (k StalledCheckRecoveryKind) String() string
String renders the recovery kind for audit-event messages / test diagnostics.
type StewardActionExecutor ¶
type StewardActionExecutor struct {
// contains filtered or unexported fields
}
StewardActionExecutor applies the steward's typed action vocabulary against an existing daemon mutation surface. It owns the R8 / R18 / R19 / R20 / R21 enforcement logic; the surface owns the I/O.
func (*StewardActionExecutor) Execute ¶
func (e *StewardActionExecutor) Execute(ctx context.Context, act steward.StewardAction, issueKey string, decisionSnap applyguard.Snapshot) (*steward.StewardActionRefusal, error)
Execute applies one steward action against the mutation surface, enforcing every spec concern in order:
R19 — refuse a cross-daemon / deploy-lifecycle action (typed refusal, no
surface call).
R8a — apply-time revalidation: discard the action if the subject moved
since the decision snapshot (skipped when decisionSnap opts out).
R21 — dispatch through an existing surface (ExecCLI) or, for a block-intent,
R20 — the escalation chokepoint (EscalateSubject).
R18 — record the application and, on a repeated same-shape action, run the
class-coverage filing gate.
R8b — verify-after-apply: surface a typed corrective follow-up on a
post-condition mismatch.
Return values:
- (nil, nil) — the action applied cleanly, OR was discarded as stale (R8a).
- (*StewardActionRefusal, nil) — the action was refused (R19) or its post-condition did not hold (R8b corrective follow-up).
- (nil, err) — an infra failure applying the mutation.
decisionSnap is the subject snapshot taken when the steward formed the decision; the zero value opts apply-time revalidation out (the wake loop has no per-action pre-snapshot, so it passes the zero value and relies on verify-after-apply).
type StewardBlockIntentPayload ¶
type StewardBlockIntentPayload struct {
// IssueKey is the subject the steward's block-intent targets.
IssueKey string
// Diagnosis is the operator-facing detail; it becomes the transition reason
// (and the SM-derived BlockedReason on a human-block disposition).
Diagnosis string
// Reason is the escalation reason token the chokepoint classifies. Empty
// defaults to escalation.ReasonStewardBlockIntent (autonomous by default); a
// steward citing a genuinely retry-futile denylisted reason threads it here
// so the chokepoint blocks.
Reason string
}
StewardBlockIntentPayload carries the typed fields of a MutationStewardBlockIntent (SPEC-SOR-2652 R20): the issue the steward wants to park, the operator-facing diagnosis, and the escalation reason token. The main-goroutine handler (applyOperatorStewardBlockIntent) loads the issue and hands these to the escalation chokepoint, so the steward's block-intent resolves through the same default-autonomous classification every other can't-proceed outcome does — never a bare blocked-state set.
type StewardDispatcher ¶
type StewardDispatcher func(ctx context.Context, task role.StewardTask) (role.StewardResult, error)
StewardDispatcher runs one steward task on the long-lived, resumable steward conversation and returns its typed result. nil disables the steward role entirely (the dormant default), mirroring SpecDrafterDispatcher: the field is declared on daemon.Config by the scaffolding child (SOR-2659) and populated in production by the go-live child (role.NewStewardDispatcher); tests inject a stub.
type StewardHandlerFunc ¶
StewardHandlerFunc handles one steward-prefixed event routed to it by StewardSubKind. It receives the daemon so a sub-handler can read state and route mutations through the single-writer surfaces; it runs on the single-writer main goroutine (handleStewardEvent is called from handle()), so it MUST NOT make a blocking subprocess / network call inline — the blessed shape captures the needed Config closure into a local, spawns a goroutine over a state snapshot, and posts results back via d.Submit (no-main-loop-blocking-call). A non-nil error is logged and dropped (default-autonomous).
type StewardLedgerMutationResult ¶
type StewardLedgerMutationResult struct {
Row issuestore.StewardLedgerRow
Err error
}
StewardLedgerMutationResult is the payload sent back on Mutation.StewardLedgerReply (SOR-2659). Used by the create path so the caller gets the persisted row with its AUTOINCREMENT id populated; on error Row is the zero value and Err carries the typed error (issuestore.ErrNotFound wrapped on a miss, validation error otherwise). Update / expire reply on the plain Mutation.Reply error chan instead.
type StewardMutationSurface ¶
type StewardMutationSurface interface {
// ExecCLI applies a non-block mutating action through the operator CLI
// surface (R21) — the same `sorcerer` subcommands the operator runs. args
// is the full argv after the binary (e.g. {"issue","transition",...}).
ExecCLI(ctx context.Context, args ...string) error
// EscalateSubject routes a block-intent through the escalation chokepoint
// (R20) instead of a bare CLI transition. diagnosis is the operator-facing
// detail; reason is the optional escalation reason token.
EscalateSubject(ctx context.Context, issueKey, diagnosis, reason string) error
// ReadIssueSnapshot reads the subject's apply-guard snapshot (state +
// version) for apply-time revalidation and verify-after-apply (R8). A
// not-found / read error is returned to the caller; an empty-state snapshot
// opts the relevant check out (fail-open, per applyguard).
ReadIssueSnapshot(ctx context.Context, issueKey string) (applyguard.Snapshot, error)
// --- R18 self-shrinking seam ------------------------------------------
// PriorAppliedCount returns how many times this (subject, action-kind) pair
// was applied before — the repeated-action signal. RecordApplied records
// one application so future calls see it.
PriorAppliedCount(ctx context.Context, issueKey string, kind steward.StewardActionKind) (int, error)
RecordApplied(ctx context.Context, issueKey string, kind steward.StewardActionKind)
// ClassCoverage consults the class-coverage filing gate for classKey:
// (true, rowID, nil) when the class is already covered (the caller comments
// + increments rather than filing a duplicate), (false, 0, nil) when it is
// uncovered (the caller files + registers). A read error falls open.
ClassCoverage(ctx context.Context, classKey string) (covered bool, rowID int64, err error)
// FileMechanizationIssue files one mechanization planning issue and returns
// its minted key. The production surface files through the existing
// operator-create surface on the daemon that manages the sorcerer source
// repo (R19-clean) and returns empty on any NON-owning daemon, where the
// filing would be the cross-daemon mutation R19 forbids; see
// daemonStewardMutationSurface. Empty is also returned on a dedupe-gate
// no-op. RegisterCoverage maps classKey to the covering key;
// IncrementCoverage bumps an already-covered row's tally.
FileMechanizationIssue(ctx context.Context, classKey, body string) (issueKey string, err error)
RegisterCoverage(ctx context.Context, classKey, coveringIssueKey string) error
IncrementCoverage(ctx context.Context, rowID int64) error
// --- R4 wedge_investigator seam ---------------------------------------
// IsDossierRecognized reports whether issueKey's dossier matches a recognized
// failure-class fingerprint (a class-coverage ledger row names it as
// CoveringIssueKey). The executor checks this daemon-side (R4) before
// dispatching the wedge_investigator: a recognized subject is refused. A read
// error is returned to the caller; a non-*Store wrapper falls open as
// unrecognized (a novel wedge), mirroring ClassCoverage.
IsDossierRecognized(ctx context.Context, issueKey string) (bool, error)
// PersistWedgeDiagnosis durably persists the investigator's typed diagnosis
// for issueKey as a StewardLedgerRow (wedge-diagnosis type) so a downstream
// recovery step (Cap 2) can consume it.
PersistWedgeDiagnosis(ctx context.Context, issueKey string, result role.WedgeInvestigatorResult) error
}
StewardMutationSurface is the testability seam for the steward action executor: every outbound effect the executor needs routes through this interface so the unit tests inject a fake without a daemon or a CLI subprocess. The production implementation (daemonStewardMutationSurface) wires each method to an existing daemon mutation surface (the operator CLI, the escalation chokepoint, the issuestore reads, the steward-ledger mutation surface) — the executor itself never touches the issuestore or d.state, which is the by-construction proof of R21.
type StewardResult ¶
type StewardResult = role.StewardResult
StewardResult is the output envelope of one StewardDispatcher invocation. A Go type alias for role.StewardResult; see StewardTask.
type StewardSweepFunc ¶
StewardSweepFunc runs one per-cycle steward sweep from the EventScheduleEval handler arm. It runs on the single-writer main goroutine under the same no-main-loop-blocking-call constraint as StewardHandlerFunc. A non-nil error is logged; the sweep runner continues to the next sweep (one failing sweep never halts the others).
type StewardTask ¶
type StewardTask = role.StewardTask
StewardTask is the input envelope for one StewardDispatcher invocation. A Go type alias for role.StewardTask (SOR-2661 expands the SOR-2659 placeholder): the typed wake envelope — wake trigger, precomputed brief, durable conversation handle — lives in internal/role/steward.go alongside the dispatcher constructor. The alias keeps the daemon-side spelling stable while the canonical shape is owned by the role package.
type SubscriptionSnapshot ¶
type SubscriptionSnapshot struct {
Name string `json:"name"`
LastUsedAt time.Time `json:"last_used_at,omitempty"`
ThrottledUntil time.Time `json:"throttled_until,omitempty"`
ThrottleCount int `json:"throttle_count"`
}
SubscriptionSnapshot is one entry in the daemon's status report for the OAuth pool. Daemon Config.SubscriptionSnapshotter returns these; the runtime layer adapts agentcli.TokenSnapshot to this shape so the daemon package doesn't import agentcli.
type TerminationCause ¶
type TerminationCause struct {
// TerminalState is the Session SM terminal the walk lands on:
// completed (happy path), crashed (error / runtime), timed_out
// (wall-clock max-age), aborted (operator / triager teardown).
// Required — SessionStatePending is the int zero value so it
// cannot double as a sentinel.
TerminalState sm.SessionState
// Actor / Reason are the Session SM transition fields recorded on
// each step of the walk. Actor defaults to "supervisor" when empty.
Actor string
Reason string
// AuditKind / AuditMessage emit an event row tagged with this
// kind and message after the SM walk. When AuditKind is empty no
// event is emitted; callers that want a cause-specific shape
// (with the session's role / id woven in) leave this empty and
// emit their own event AFTER terminateSession returns.
AuditKind string
AuditMessage string
// ReaperKill, when true, force-reaps the subprocess tree via
// d.cfg.SessionReaper BEFORE the SM transition. The SOR-195
// reaper snapshots /proc ancestry while the process is still
// alive — kill-first is the right order. ReaperGraceful selects
// SIGTERM (true) vs SIGKILL (false). ReaperReason is the message
// threaded to SessionReaper. EmitReapEvent, when true, writes a
// session_force_reaped audit row capturing the (reaped, reason)
// pair.
ReaperKill bool
ReaperGraceful bool
ReaperReason string
EmitReapEvent bool
// RecordFailure, when true, calls recordRoleFailure with
// FailureReason. Operator aborts and boot-recovery do NOT bump
// the failure streak — they aren't role failures.
//
// RecordSuccess, when true, calls recordRoleSuccess (zeroes the
// streak). Exclusive with RecordFailure; setting both is a
// programming error.
RecordFailure bool
RecordSuccess bool
FailureReason string
// Transient, when set alongside RecordFailure, marks the failure
// as transient infrastructure noise (pool-exhausted / token
// throttle / network / transient auth — anything classified by
// isTransientSessionError). recordRoleFailure consults the bool
// and skips the quarantine-streak increment when true, so a
// transient close neither advances toward conversation-recycle
// nor resets an accumulating crash streak.
//
// Set by terminationCauseSessionError from the actual session
// error (not FailureReason — the literal "crashed" string the
// other forced-timeout paths thread carries no classifiable
// text, so the classifier must see the real error). The forced-
// timeout paths (max-age / dead-by-PID-probe) leave this false:
// a max-age timeout is a genuine session failure, not an
// infrastructure hiccup.
Transient bool
}
TerminationCause captures the per-cause shape passed to terminateSession. The fields distinguish how a session walks to terminal — terminal state, SM transition labeling, optional subprocess reaper kill, audit-event payload, and role-failure bookkeeping. Adding a new cause means adding a constructor below; it does NOT mean inventing a new place to flip iss.SessionID.
The chokepoint owns ONLY the synchronization invariant: after terminateSession returns, no issue's SessionID can equal a terminal session's ID. Per-cause issue-side SM transitions (rollBackForcedTimeoutIssue, applyImplementerResult, etc) run AFTER terminateSession returns; they continue to own the cause- specific issue-state walk.
type Topology ¶
type Topology struct {
Self string
// Daemons maps daemon name → DaemonInfo for the most recent
// successful refresh.
Daemons map[string]DaemonInfo
// RepoOwnership maps OWNER/REPO slug → owning daemon name. A repo
// claimed by two or more daemons (a conflict) is absent, so OwnerOf
// returns empty for it until the conflict clears.
RepoOwnership map[string]string
// contains filtered or unexported fields
}
Topology is a daemon-side cache of the registered-peer universe and the repo→owner mapping derived from it (SOR-1191).
Self is this daemon's own registered name, carried so callers can compare an OwnerOf result against self.
Daemons and RepoOwnership are guarded by mu and are only ever mutated by full-map replacement under the write lock in refreshOnce; readers go through OwnerOf / Snapshot, which copy under the read lock. Do not read or mutate the maps directly without holding mu.
func NewTopology ¶
NewTopology constructs a topology cache for the given self-name, UI endpoint, and self-owned repo set (sourced from cfg.Repos, SOR-1464), wiring emit as the audit-event sink. The refresh goroutine is launched separately via Start; the FIRST refresh runs synchronously at boot via BlockingFirstRefresh.
func (*Topology) AuthTokenHashOf ¶
AuthTokenHashOf returns the hex-lowercase SHA-256 of daemonName's bearer (SOR-1214), or the empty string for unknown / stale-cache / older-peer cases. The cross-daemon auth path on POST /v1/issues consults this to verify an inbound `X-Sorcerer-Forwarded-From: <D>` request's bearer against the named source daemon. Empty results reject the request (the auth handler treats them as mismatch).
func (*Topology) BlockingFirstRefresh ¶
BlockingFirstRefresh runs ONE synchronous refresh against the UI registry. The boot sequence calls it BEFORE opening the HTTP listener so no inbound route check ever observes an empty-but-recently-refreshed cache (the SOR-1464 startup race: an empty refresh that just landed looked "fresh" under the old time-only IsStale, so route checks rejected self-owned repos). The caller bounds it with a timeout context; on timeout the underlying fetch error is returned and the cache is left empty — IsStale then reports stale (zero daemons) and route checks fall through to local until the periodic refresh lands.
func (*Topology) DaemonURLByName ¶
DaemonURLByName returns the registered base URL of daemonName, or the empty string when no daemon by that name is in the most recent successful refresh. Used by SOR-1278's `sorcerer issue route --to` path to resolve a target-daemon name to the URL the cross-daemon forwarder POSTs against. An empty return is the operator-visible "unknown daemon" condition; the CLI handler maps it to 400.
func (*Topology) IsStale ¶
IsStale reports whether the cache should NOT be trusted for routing. A never-refreshed cache, an EMPTY cache (zero daemons — SOR-1464), or a cache whose last successful refresh is older than topologyStaleThreshold is stale. The empty-cache rule closes the startup race where a refresh that returned no daemons advanced lastRefresh and so "looked fresh" under the old time-only check: an empty fresh cache is functionally identical to a never-refreshed one, so route checks must fall back to local rather than reject self-owned repos.
func (*Topology) LastRefresh ¶
LastRefresh returns the timestamp of the most recent successful refresh (zero until the first one lands).
func (*Topology) OwnerOf ¶
OwnerOf returns the name of the daemon that owns repo, or the empty string when no daemon owns it OR the repo is in conflict (claimed by two or more daemons).
func (*Topology) Snapshot ¶
func (t *Topology) Snapshot() TopologySnapshot
Snapshot returns a deep copy of the current peer set under the read lock, name-sorted, alongside the last-refresh timestamp and a staleness flag. The copy means callers can serialize the result without holding the cache lock or racing the refresh goroutine.
func (*Topology) Start ¶
Start launches the periodic refresh goroutine. The FIRST refresh is run SYNCHRONOUSLY by the boot sequence via BlockingFirstRefresh BEFORE the HTTP listener opens (SOR-1464), so this goroutine only drives the periodic re-fetch every topologyRefreshInterval until ctx is canceled. Mirrors startRegistrySweep (cmd/sorcerer-ui/registry.go).
type TopologySnapshot ¶
type TopologySnapshot struct {
LastRefresh time.Time
Stale bool
Daemons []DaemonInfo
}
TopologySnapshot is the read-only view of the cache that StatusSnapshot serializes for operators (SOR-1193). Daemons is name-sorted; Stale mirrors IsStale at snapshot time; LastRefresh is zero until the first successful refresh lands.
type TriagerActionApplyPayload ¶
type TriagerActionApplyPayload struct {
Kind TriagerActionKind
// IssueKey is the subject for unblock_via_dep_drop /
// force_converge_to_terminal / archive_session (via session-issue
// lookup) / requeue_issue / mark_blocked_user.
IssueKey string
// DepKey is the dep edge to drop for unblock_via_dep_drop.
DepKey string
// TargetIssueState is the issue-SM terminal target for
// force_converge_to_terminal with target_state=merged.
TargetIssueState sm.IssueState
// TargetPlanState is the plan-SM terminal target for
// force_converge_to_terminal with target_state=plan_completed.
TargetPlanState sm.PlanState
// ProposalID is the proposal row id for
// cascade_abandon_orphan_proposal.
ProposalID int64
// SessionID is the session row id for archive_session.
SessionID string
// UserMessage is the operator-facing rationale for
// mark_blocked_user.
UserMessage string
// AssembledNeeds is the fully pre-assembled operator escalation body
// for escalate_plan_defect: offending issue body + dependency-chain
// neighbor bodies + the implementer's plan_defect report + the
// agent's recommended correction. Built off the main loop by the
// executor (it reads the issuestore) and handed to the main-loop
// handler verbatim so the SM-mutation goroutine does no I/O.
AssembledNeeds string
// SeenIssueVersion is the SOR-1067 stale-payload race shield: the
// issue's row-version observed at triager Tick start (before the
// LLM was called), threaded through the LLM's action onto the
// payload by the triager-executor builder. The main-loop apply
// handlers refuse to fire when the live row's `Version` has moved
// past this stamp — a stale `mark_blocked_user` against an issue
// the operator already transitioned between LLM and apply is
// dropped at the executor seam instead of silently flipping a row
// the LLM never saw. Zero is the "no snapshot threaded" sentinel —
// the gate compares strict equality, so executor sites that
// genuinely cannot supply a snapshot (e.g. archive_session, which
// operates on a session row, not an issue) skip the gate inline
// with a comment.
SeenIssueVersion int64
// LLMRationale is the triager LLM's verbatim rationale for the
// action, threaded onto the payload so the SOR-1067
// `mutation_skipped_stale_version` audit event can record the
// classification that was about to fire. Operator-readable; empty
// when the action carried no rationale.
LLMRationale string
// Confidence is the triager LLM's confidence label for the action
// (`low` / `medium` / `high`), threaded onto the payload so the
// SOR-1333 AC2 escalation-reassembly path can surface the current
// decision's confidence on the `triager_escalation_reassembled`
// audit event (the escalation row itself doesn't carry a confidence
// column; the audit-event message is the canonical surface). Only
// the executor builders for actions whose Applied=false path
// reassembles an operator escalation populate this field
// (mark_blocked_user, escalate_plan_defect); other apply payloads
// leave it empty.
Confidence string
// Reason is the audit-trail reason recorded on the SM transition
// (free-text; the daemon also folds in a triager-attribution
// prefix).
Reason string
// Reply receives the typed outcome from the handler.
Reply chan<- TriagerActionApplyResult
}
TriagerActionApplyPayload is the typed payload of EventTriagerActionApply. The executor (triager_executors.go) builds one per autonomy-safe action and submits it onto the main loop. Reply receives a TriagerActionApplyResult once the handler completes — synchronous from the executor's perspective. Buffered by the caller so a slow main loop doesn't block the dispatcher's send.
type TriagerActionApplyResult ¶
type TriagerActionApplyResult struct {
Applied bool
SkippedIdempotent bool
// Reassembled (SOR-1333) is set on the subset of Applied=false
// returns where the issue was already in the requested target
// state (e.g. mark_blocked_user against a row already in
// blocked_user / plan_blocked, or escalate_plan_defect against a
// row already in blocked_user / plan_blocked) AND the main-loop
// handler refreshed the operator-facing escalation row (keyed on
// (issue_key, rule)) with the current decision's user-message /
// assembled body, emitting a `triager_escalation_reassembled`
// audit event. The executor surface suppresses its default
// `triager_action_applied` emit on this path so the audit trail
// records exactly one row per refresh (the reassembled event)
// rather than one applied=false row per triager tick.
Reassembled bool
// SkippedTerminalState (SOR-1333) is set on the subset of
// Applied=false returns where the dispatch-time terminal-state
// gate short-circuited the action because the target issue is
// already in a terminal state (merged / abandoned / rejected /
// plan_completed / plan_abandoned / merged_to_abandoned_plan).
// The main-loop handler already emitted the
// `triager_skipped_terminal_state` audit event (gated per
// (issue_key, state) edge); the executor surface suppresses the
// default `triager_action_applied` emit AND returns the
// OutcomeSkippedTerminal outcome so the per-tick triager_completed
// event records the skip under actions_skipped rather than
// actions_applied. TerminalState carries the state string the
// gate observed; operator-readable on audit-event message lines.
SkippedTerminalState bool
TerminalState string
Err error
}
TriagerActionApplyResult is the reply payload from EventTriagerActionApply. Applied=true when the SM transition fired; Applied=false + Err==nil is the idempotent no-op fast path (e.g. force-converge to a state the issue is already in, drop a dep that's already gone). Err carries the transition / store failure when the apply hit a real error.
SkippedIdempotent (SOR-1311) is set on the subset of Applied=false returns where the handler already emitted a `triager_action_skipped_idempotent` audit event (gated by the per-state-edge dedupe) AND the executor's normal `triager_action_applied` emit must be suppressed so the audit trail records exactly one row per state-edge. The flag is meaningful only when Applied=false; Applied=true SkippedIdempotent=true is a programming error the executor surfaces by writing the normal applied event.
type TriagerActionKind ¶
type TriagerActionKind int
TriagerActionKind tags the per-action SM mutation a triager executor needs to run on the main goroutine. Each kind maps 1-to-1 onto an action key in internal/role/triager.go's vocabulary; the distinct enum here lets the dispatcher branch without re-parsing the action string.
const ( TriagerActionKindUnknown TriagerActionKind = iota TriagerActionKindUnblockDepDrop TriagerActionKindForceConvergeIssue TriagerActionKindForceConvergePlan TriagerActionKindCascadeOrphanProposal TriagerActionKindArchiveSession TriagerActionKindRequeueIssue TriagerActionKindMarkBlockedUser TriagerActionKindEscalatePlanDefect )
type TriagerDispatcherLogger ¶
TriagerDispatcherLogger is the minimal logging surface the dispatcher uses to emit operator-facing one-liners on each event-driven dispatch. *log.Logger satisfies it; nil silences.
type TriagerEventDispatcher ¶
type TriagerEventDispatcher struct {
// contains filtered or unexported fields
}
TriagerEventDispatcher owns the event-driven dispatch path. One per daemon. It serializes Tick invocations across both event-driven and polled paths via a single runMu, so the AC's "never two triagers active simultaneously" invariant holds regardless of which side initiates the dispatch.
func NewTriagerEventDispatcher ¶
func NewTriagerEventDispatcher(in TriagerEventDispatcherInputs) (*TriagerEventDispatcher, error)
NewTriagerEventDispatcher constructs a dispatcher. Call Run to start the dispatch goroutine; call Stop to shut it down. Tick and Store are required; everything else has sensible defaults.
func (*TriagerEventDispatcher) Notify ¶
func (d *TriagerEventDispatcher) Notify(key string)
Notify is invoked from the supervisor's transition-write path on every transition INTO blocked_user or plan_blocked. Best-effort: if the notification channel is full the call drops the event (the polled triager backstop will still pick the issue up on its next tick). Safe to call from any goroutine.
func (*TriagerEventDispatcher) Run ¶
func (d *TriagerEventDispatcher) Run(ctx context.Context)
Run starts the dispatch goroutine. Blocks until ctx is canceled or Stop is called. Run MUST be called exactly once; calling it twice or after Stop is undefined.
The goroutine collects blockedEvent notifications from Notify into a pending batch. The first notification of a batch arms a debounce timer; subsequent notifications during the window join the batch without re-arming. When the timer fires, a dispatch goroutine takes runMu (blocking if a polled Tick is in flight), writes the triager_dispatched_on_event row + log line, then runs Tick. While the dispatch goroutine is in flight, new notifications buffer into a fresh pending batch; the next debounce window starts only after the in-flight dispatch completes, so AC Layer 2's "queued for the NEXT triager cycle" rule holds for any event arriving mid-cycle. Run is the supervised outer goroutine: it runs the dispatch select loop and, when that loop recovers a panic, backs off and re-enters it rather than letting the event-driven triager dispatcher die silently (SOR-2630). close(d.done) lives here at the outermost level so Stop's `<-d.done` resolves exactly once, only when the goroutine truly exits (ctx canceled / Stop).
func (*TriagerEventDispatcher) RunPolled ¶
func (d *TriagerEventDispatcher) RunPolled(ctx context.Context) error
RunPolled is the entry point the polled triager poller uses to run its Tick through this dispatcher's serializing lock. Acquires runMu (blocking until any in-flight event-driven Tick completes), invokes Tick under that lock, and releases. The dispatcher does NOT emit a triager_dispatched_on_event row on the polled path — that event is reserved for the event-driven fast path; the triager writes its own triager_dispatched row inside Tick on both paths.
Safe to call from any goroutine — runMu serializes contention.
func (*TriagerEventDispatcher) Stop ¶
func (d *TriagerEventDispatcher) Stop()
Stop signals the dispatch goroutine to exit and waits for it. Idempotent. Safe to call before or after Run.
type TriagerEventDispatcherInputs ¶
type TriagerEventDispatcherInputs struct {
// Tick runs one triager pass. Required. Production wires
// triager.Triager.Tick collapsed to error-only.
Tick TriagerTickFn
// Store is the issuestore handle the dispatcher writes the
// triager_dispatched_on_event row to. Required.
Store issuestore.IssueStore
// Logger receives the per-dispatch one-liner. Optional; nil silences.
Logger TriagerDispatcherLogger
// Debounce is the coalescing window. Zero defaults to
// DefaultTriagerEventDebounce.
Debounce time.Duration
// MaxAge is the per-Tick wall-clock budget the dispatcher wraps
// around the ctx handed to Tick. Mirrors TriagerPollerInputs.MaxAge
// so event-driven and polled invocations honor the same cap. Zero
// skips the WithDeadline wrap.
MaxAge time.Duration
// Now overrides the wall-clock source. Tests use it for
// deterministic latency assertions; nil defaults to time.Now.
Now func() time.Time
// TimerFactory overrides the debounce-timer source, returning the
// timer's fire channel and a stop function (mirroring
// time.Timer.C / time.Timer.Stop). Tests inject a controllable fake
// to drive the debounce window deterministically instead of racing a
// real wall-clock timer against goroutine scheduling under load; nil
// defaults to a time.NewTimer-backed wrapper whose behavior is
// byte-for-byte identical to the pre-seam direct time.NewTimer calls.
TimerFactory func(d time.Duration) (<-chan time.Time, func() bool)
// ChannelBuffer sets the size of the in-flight notification channel.
// Zero defaults to 256 — generous enough to absorb a boot-recovery
// storm without dropping notifications.
ChannelBuffer int
// PauseFn closes over the role-pause gate (SOR-1195) so an
// operator-set role_health_pauses row is honored by the
// event-driven dispatch path AND the polled-through-RunPolled
// path identically. nil disables the gate (back-compat for tests
// and pre-SOR-1195 wiring). Production wires
// internal/runtime.IsRolePaused via a closure that fixes the
// role name + the store handle at construction time.
PauseFn TriagerPauseFn
// PanicSink, when set, is invoked with (name, cause) whenever the
// dispatcher's Run loop or a per-batch dispatchOne goroutine recovers
// a panic (SOR-2630). Without it a panic in dispatchOne → Tick would
// crash the entire daemon (an unrecovered panic in a child goroutine
// is fatal), and a panic in the Run select would kill the dispatcher
// silently. Production wires it to the daemon's poller_restarted
// emit; nil disables only the audit emit (recovery + restart still
// happen).
PanicSink func(name, cause string)
}
TriagerEventDispatcherInputs configures NewTriagerEventDispatcher.
type TriagerPauseFn ¶
TriagerPauseFn reports whether the triager role's role_health_pauses row is in force at the call site. Returns (paused=true, reason) when a pause is in force; (paused=false, "") when the role is healthy or the pause has auto-expired; a non-nil error on a transient read failure (callers fail open). Production wires internal/runtime.IsRolePaused; the runtime → daemon import direction means the closure is injected at construction time rather than referenced statically. nil disables the gate (back-compat for tests and call sites that haven't wired it).
SOR-1195: this is the chokepoint that closes the operator-disable hole — before the gate, an event-driven dispatch bypassed the recognizer/triager pause-table check that the timer-tick poller enforced. Wiring this here means timer-tick and event-driven invocations are gated identically.
type TriagerTickFn ¶
TriagerTickFn is the function shape the dispatcher invokes to run one triager pass. Mirrors triager.Triager.Tick's signature collapsed to error-only because the dispatcher does not consume the TickResult — the triager writes its own lifecycle events to the issuestore inside Tick.
type VerdictCacheHandle ¶
type VerdictCacheHandle interface {
GetOrComputeVerdictCache(ctx context.Context, specID, requirementID, codeHash string, nowSeconds int64, computeFn func() (result, rationale string, err error)) (issuestore.VerdictCache, error)
}
VerdictCacheHandle is the narrow verdict-cache accessor executors use for the compute-only-on-miss pattern (spec R5): a cached oracle verdict keyed on (spec, requirement, traced-code-hash) is recomputed only when the key changes. The daemon's issuestore handle (issuestore.IssueStore, assigned via daemon.Config.Issuestore) satisfies it.
type Verifier ¶
type Verifier interface {
// Name returns the stable identifier the chain runner emits on
// per-verifier audit events and weaves into the synthetic concern
// `Comment:` field. Should be stable across daemon restarts so
// operators can grep the events table for one verifier's history.
Name() string
// Run executes the verifier against worktreePath (an ephemeral
// worktree carved off the bare clone pinned to env.Branch's
// freshly-fetched tip; the chain runner owns the worktree's
// lifecycle). Returns a populated VerifierResult plus an error;
// the error return covers unrecoverable plumbing failures, NOT
// a non-zero gate exit (those flow through VerifierResult.Passed
// = false so the chain runner can short-circuit and route the
// issue through refer-back).
Run(ctx context.Context, worktreePath string, env VerifierEnv) (VerifierResult, error)
}
Verifier is one rung of the implementer post-submit verifier chain. Implementations are deterministic gates run by the daemon (NOT the implementer LLM) against the implementer's pushed branch the moment the success marker lands. A verifier returning Passed=false routes the issue back through the existing refer-back machinery as synthetic reviewer concerns; an unrecoverable plumbing error (worktree carve failure, etc.) is surfaced as the error return.
The chain is the durable chokepoint for forward-looking enforcement of `do-not-do-X` rules: once a class of defect is encoded as a Verifier the LLM cannot bypass it on any subsequent cycle.
type VerifierEnv ¶
type VerifierEnv struct {
IssueKey string
RepoSlug string
Branch string
BareCloneDir string
DefaultBranch string
CarvePointBaseRef string
// PlanBranch is the plan-branch name for a CIPB child, threaded so the
// verifier-worktree acquirer carves a SQUASH-PREVIEW off the CURRENT plan
// tip (plan tip + child work) instead of the child's stale branch. Empty
// for per_child / legacy — the acquirer then carves off the child's branch.
PlanBranch string
// Verifies holds the spec R-ID list the issue covers, decoded from
// sm.Issue.Verifies JSON by the chain runner. Empty for a
// non-spec-driven issue. A verifier that gates spec-cited work (the
// prop_test stub gate) reads it to resolve which requirements apply;
// the legacy verifiers ignore it.
Verifies []string
// SpecBodyYAML holds the raw canonical YAML of the issue's bound
// spec, resolved by the chain runner via the issuestore once per
// chain run (before the per-repo loop). Empty when the issue is not
// spec-driven or the spec cannot be loaded — a verifier that needs
// it treats the empty value as a no-op signal (fail open).
SpecBodyYAML string
// RequirementTrace is the implementer's structured requirement→code
// trace, decoded by the chain runner from the result's
// requirement_trace.json (role.ImplementerResult.RequirementTraceJSON).
// Empty/nil for a non-spec-driven impl, an impl that traced nothing, or
// a legacy row predating the trace column. The deterministic structural
// rule reads its target and the llm_test oracle reads its input from
// here; the prop_test executor ignores it.
RequirementTrace spectrace.RequirementTrace
// LLMRunner is the oracle-runner handle the llm_test executor uses to
// emit a structured pass/fail verdict for a requirement's traced code.
// nil (the default + back-compat shape) disables llm_test invocations
// until the oracle executor ships; the deterministic executors ignore
// it. Behind an interface so a sibling executor adds only its own file.
LLMRunner LLMVerifierRunner
// VerdictCache is the narrow verdict-cache accessor the llm_test
// executor uses for the compute-only-on-miss pattern (spec R5). The
// daemon's issuestore handle satisfies it; nil (a test daemon without an
// issuestore) disables caching, and executors guard on the nil. Behind
// an interface so a sibling executor adds only its own file.
VerdictCache VerdictCacheHandle
// GateRuntimeFactory mints a fresh gateruntime.GateRuntime per gate
// subprocess dispatch (the configured runtime chokepoint every gate
// spawn routes through, spec R1). NativeGateRunner is stateful per
// dispatch, so each spawn site mints a fresh instance via the factory.
// nil falls back to &gateruntime.NativeGateRunner{} so the large test
// corpus that constructs a VerifierEnv without wiring a factory is
// unaffected (the native runner reproduces the prior host spawn).
GateRuntimeFactory func() gateruntime.GateRuntime
// ProjectLanguage is the managed project's config-sourced primary
// language, threaded from daemon.Config.ProjectLanguage. The prop_test
// stub verifier resolves the project's property-test interpreter from
// it (proptestbackend.ForLanguage) so verification runs the project's native
// runner (Go→go vet, Rust→cargo, Python→pytest) instead of assuming Go.
// The zero value ("") is treated as "go" by the verifier so unwired test
// daemons keep the existing Go probe path (matching the
// daemon.Config.ProjectLanguage zero-value convention).
ProjectLanguage string
// InFlightOps is the stable in-flight-operations registry pointer
// (store.State.InFlightOps), captured on the main goroutine at chain
// dispatch and threaded onto every per-repo env so a verifier can register
// a live, subject-attributed entry for its duration (SOR-3028 R3). The
// PrePushGateVerifier registers a pre_push_gate entry keyed on IssueKey for
// the gate's runtime. nil (the back-compat / unwired-test default) disables
// the registration — every registry method is nil-safe.
InFlightOps *store.InflightOperationRegistry
// PropTestRunnerFor resolves the project's config-DECLARED prop_test runner
// for a language (config.PerLanguageConfig.PropTestRunner), threaded from
// daemon.Config.PropTestRunnerFor. The prop_test stub verifier's native
// section feeds the result through Interpreter.ResolveRunner so a language
// with runner variants (TypeScript) runs the SELECTED runner's verify command
// (SPEC-SOR-3093-v1 R5) instead of the profile's top-level Vitest command. It
// is nil-safe: a nil field (every unwired test daemon / legacy config) means
// "no declared runner", so selection falls to detection/default and the verify
// command stays byte-identical to today's default-runner output. It mirrors the
// emit-side threading generatePropTestStubUnion already uses.
PropTestRunnerFor func(lang string) string
}
VerifierEnv is the per-run input the chain runner passes to each verifier. Carries enough identification (issue key, repo slug, branch, bare clone, default branch) for any verifier to locate its inputs without smuggling extra state through closures. The carved ephemeral worktree path is passed separately to Run so a verifier that doesn't need the worktree (e.g. a pure policy lint) doesn't have to reach into VerifierEnv for it.
CarvePointBaseRef is the per-(plan, child) carve-point ref name for a child of a continuous-plan-branch (CPB) planning issue — the ref the child's working branch was carved off when the supervisor dispatched the implementer. The supervisor populates it with either the topologically-latest sibling-dep's working branch (when the child stacks on a sibling) or the plan-branch name (when the child has no sibling dep), mirroring the carve-base computation prepareWorktrees uses. The field stays empty under branch_model: per_child, the legacy plan_branch deferred-integration shape, and the empty back-compat sentinel — those shapes have no inherited prior-sibling history to scope around.
Verifiers that enumerate the child's own commits (as opposed to the cumulative plan-branch history that piles up under CPB as siblings squash) should prefer CarvePointBaseRef as the git-log base when non-empty: the daemon-generated sibling-squash commits inherited via the live plan branch are NOT the dispatched child's commits and must not feed into a per-child verdict. When empty, verifiers fall back to their default base (typically origin/DefaultBranch), the legacy per_child shape.
type VerifierExecutorDispatch ¶
type VerifierExecutorDispatch struct {
// Timeout is the phase-scoped wall-clock budget for the executor
// subprocesses (go vet / go build / go test) this dispatch spawns,
// anchored fresh at context.Background() rather than the shared parent
// ctx (SOR-2626) so the pre-push gate's wall-clock cannot drain it. A
// zero value falls back to DefaultExecutorDispatchTimeout via timeout(),
// so a struct-literal VerifierExecutorDispatch{} stays bounded.
Timeout time.Duration
}
VerifierExecutorDispatch is the mode-dispatched rung of the implementer verifier chain. For each cited R-ID whose verification_mode is executable and has a registered executor, it routes to that executor and aggregates the verdicts, short-circuiting on the first failing executor. It is the seam that makes "live iff executable" realizable per mode (spec R2): a manual / empty mode resolves to no executor (not-live, skipped) and an executable mode with no executor yet is skipped (not-yet-implemented = not-live) without failing the chain.
It replaces the direct PropTestStubVerifier entry in cmd/sorcererd/start.go's chain: the prop_test path is preserved by registering the existing PropTestStubVerifier as the prop_test executor (see verifier_prop_test_stub.go's init), so prop_test requirements route through the same gate as before.
func NewVerifierExecutorDispatch ¶
func NewVerifierExecutorDispatch() *VerifierExecutorDispatch
NewVerifierExecutorDispatch constructs the dispatch with the default phase budget. The constructor mirrors NewPrePushGateVerifier / NewPropTestStubVerifier so the cmd/sorcererd/start.go chain wiring uses the same typed-constructor shape; setting the default here means production inherits DefaultExecutorDispatchTimeout with no start.go change.
func (*VerifierExecutorDispatch) Name ¶
func (d *VerifierExecutorDispatch) Name() string
Name returns the stable identifier the chain runner emits on per-verifier audit events and weaves into the synthetic concern surface.
func (*VerifierExecutorDispatch) Run ¶
func (d *VerifierExecutorDispatch) Run(ctx context.Context, worktreePath string, env VerifierEnv) (VerifierResult, error)
Run routes each cited requirement's executable verification_mode to its registered executor. It no-ops (Passed=true) for a non-spec-driven impl (empty Verifies or empty SpecBodyYAML) and fails open (Passed=true) on an unparseable spec — matching PropTestStubVerifier's pattern: blocking on a daemon-side data problem the chain runner already loaded would convert it into an implementer refer-back. A requirement whose mode is manual / empty or has no registered executor is skipped. On the first executor that returns Passed=false or an error, Run short-circuits and returns that executor's result (its per-R-ID diagnostic already names the requirement).
The parent ctx is intentionally NOT used to spawn the executor subprocesses (SOR-2626): it is the implementer session context shared across the whole verifier chain, so the pre-push gate that runs before this dispatch can arrive here with most of its wall-clock already spent. Run anchors a fresh phase-scoped budget from context.Background() (the same decoupled-budget shape PrePushGateVerifier.Run uses at verifier_pre_push_gate.go:166) so a drained parent ctx can no longer kill `go vet`/`go build` before they print a byte. ctx stays in the signature to satisfy the Verifier interface and bound the chain runner's outer orchestration.
type VerifierResult ¶
type VerifierResult struct {
Name string
Passed bool
Stdout []byte
Stderr []byte
DurationMS int64
// EnvironmentDrift carries the diverging dimension(s) named by the
// gate's `gate_environment_drift:` stdout line (empty when the gate
// emitted no drift warning, or for verifiers that don't run the
// gate). Threaded so a gate failure that occurred under known
// environment drift (e.g. local Go toolchain ≠ the version Actions
// installs from go.mod) surfaces the drift in the feedback envelope
// rather than presenting as an unexplained gate failure.
EnvironmentDrift string
// ExitCode is the gate subprocess's exit code on a Passed=false
// result, where the verifier exposes it (PrePushGateVerifier
// populates it from *exec.ExitError.ExitCode()). Zero on the success
// path, on the deadline/cancel path, and for verifiers that run no
// subprocess gate. Threaded so the env-class gate-failure classifier
// (ClassifyGateFailureEnvironment) can reach the OOM / signal-kill
// exit-code class (134/137/139) that carries no stdout/stderr
// substring: scripts/pre-push-gates.sh runs under bash, so a child
// killed by SIGKILL/SIGABRT propagates as the script's own
// 128+signal exit code, which exitErr.ExitCode() reports directly.
ExitCode int
// DeadlineKill is true when the gate subprocess was killed by its OWN
// phase-scoped deadline (the verifier's runCtx, anchored at
// gate-subprocess start from context.Background()) rather than by an
// external OOM/host signal (SOR-2400). The verify-chain apply handler
// reads it BEFORE ClassifyGateFailureEnvironment so a self-inflicted
// timeout is routed to its distinct gate_deadline_kill_detected
// signature, NOT misclassified as the env-transient
// killed-before-success-marker class. Always false on the success path,
// on a genuine non-zero gate exit, and for verifiers that run no
// subprocess gate.
DeadlineKill bool
// GateImageBuildFailed is true when the gate ran NO subprocess because the
// container gate factory could not produce a fresh image: the ensure-fresh
// coordinator settled BuildOutcomeFailedPersistent, so the factory returned a
// preflight-error runtime whose Command yielded a
// *gateruntime.GateImageBuildPersistentFailureError. The gate call site
// (verifier_pre_push_gate.go) sets it via an errors.As probe BEFORE the error
// is string-serialized onto Stderr; the verify-chain apply handler reads it
// BEFORE the deadline-kill / env-class classification and routes the failure
// to the gate-image-build escalation path (spec SPEC-SOR-3166-v2 R6) rather
// than the normal refer-back path — a missing/stale-image build failure is an
// infra fault the implementer cannot fix by re-doing already-correct work.
// Always false on the success path, on a genuine non-zero gate exit, and for
// verifiers that run no subprocess gate.
GateImageBuildFailed bool
// SquashPreviewConflict is true when AcquireVerifierWorktree returned a
// *github.VerifierSquashPreviewConflict — for a CIPB child, `git merge
// --squash`ing it onto the CURRENT plan-branch tip left unmerged paths, so
// the child must be rebased onto the current plan branch tip before it can
// land. The acquire-error arm of runVerifierChainForRepo sets it via an
// errors.As probe BEFORE the typed error is string-serialized onto Stderr
// (mirroring GateImageBuildFailed / DeadlineKill); the verify-chain apply
// handler reads it BEFORE the deadline-kill / env-class classification and
// routes the failure to the rebase-recovery path (IssueStateRebasing when an
// automatic rebase applies) or blocked_user (when no automatic rebase path
// applies) — never a code-feedback cycle, which cannot rebase a branch and
// would re-emit a byte-identical diff that re-conflicts on the next
// squash-preview. Always false on the success path, on a genuine non-zero
// gate exit, and for verifiers that run no subprocess gate.
SquashPreviewConflict bool
}
VerifierResult is the per-verifier outcome the chain runner records and surfaces back to the supervisor. DurationMS is wall-clock milliseconds spent inside Run; Stdout / Stderr carry the captured streams the runner pipes into the synthetic refer-back concerns on failure (truncation is the caller's responsibility — the chain runner preserves whatever the verifier returned).
func RunImplementerVerifierChain ¶
func RunImplementerVerifierChain(ctx context.Context, chain []Verifier, worktreePath string, env VerifierEnv) (bool, []VerifierResult)
RunImplementerVerifierChain runs the ordered chain of verifiers against the supplied worktree, short-circuiting on the first verifier that returns Passed=false (or a non-nil error). Returns the aggregate pass/fail signal plus every VerifierResult that ran (including the failing one if there was one). The caller is responsible for supplying the ephemeral worktree carve + teardown — the chain runner is pure orchestration and never touches the filesystem on its own, so unit tests can drive it with stub verifiers and a tmp worktree.
A nil / empty chain returns (true, nil) — the test-mode / back-compat shape so a daemon without verifiers configured behaves identically to the pre-chain code path.
The runner is intentionally simple. The per-verifier audit events (verifier_started / _succeeded / _failed) and the chain-level envelope events (implementer_verify_chain_started / _succeeded / _failed) are emitted by the supervisor's wrapper around this call; the runner doesn't reach into the daemon's audit surface so a future non-daemon caller (e.g. a sorcerer-side CLI gate that runs the same chain against a local worktree) can reuse the runner without dragging the daemon along.
type WedgeInvestigatorDispatcher ¶
type WedgeInvestigatorDispatcher func(ctx context.Context, task role.WedgeInvestigatorTask) (role.WedgeInvestigatorResult, error)
WedgeInvestigatorDispatcher runs one wedge_investigator dispatch for a novel wedge and returns the typed root diagnosis (SPEC-SOR-2820-v3 R4). nil disables the investigator (the dormant default), mirroring StewardDispatcher; the executor self-gates on nil so a dispatch_wedge_investigator action is a no-op until a dispatcher is wired.
type WorkflowDispatcher ¶
type WorkflowDispatcher interface {
DispatchWorkflow(ctx context.Context, repoSlug, workflowName, ref string) error
}
WorkflowDispatcher dispatches a named GitHub Actions workflow on a ref — the dispatch_workflow primitive the activation driver fires (spec R11). *github.Client satisfies it via DispatchWorkflow, so the driver depends on this one method rather than the full client surface (the narrow-handle pattern CIRunChecker / LLMVerifierRunner use). A nil dispatcher disables only the dispatch_workflow firing path — such a probe defers to pending rather than failing.
Source Files
¶
- abandon_with_cleanup.go
- abort_role_sessions.go
- acceptance_criteria.go
- ack_timeout.go
- actions_flake_retry.go
- activation_operator.go
- activation_probe_eval.go
- amender_exhaustion.go
- autonomous_create.go
- baseline_refresh.go
- benchmark_required_targets.go
- blocked_user_pr_open_offmain.go
- body_sizing_warning.go
- boot_orphan_branch_sweep.go
- capability_gap_deploy.go
- capability_gap_release_sweep.go
- capability_gap_signature.go
- claim_dispatch.go
- completed_plan_precedence.go
- coverage_gap_sweep.go
- coverage_warning.go
- cpb_assembly_fixer_dispatch.go
- cpb_corrective_hold_expiry.go
- cpb_in_flight_stale_clear.go
- cpb_integration_review.go
- cpb_pr_url_backfill.go
- cpb_stranded_finalization.go
- crash_recovery_pr_discovery_offmain.go
- cross_daemon.go
- cross_daemon_forward_events.go
- daemon.go
- dedupe.go
- dedupe_deferred_sweep.go
- defect_domain_default.go
- discovery_telemetry.go
- dispatch_outcome.go
- dispatch_route.go
- ephemeral_worktree_prune_sweep.go
- escalation.go
- events.go
- feedback_envelope.go
- flow_liveness.go
- footprint_admission.go
- footprint_dep_reconciler_sweep.go
- force_converge_dispatch.go
- gate_env_toolchain_diagnosis.go
- gate_image_build_escalation.go
- gate_transient_env.go
- git_network_transient.go
- hermeticity.go
- implementer_cycle_summary.go
- inflight_op_attribution.go
- inputs_closure_provider.go
- integration_seam_hold.go
- invariants_check_sweep.go
- issue_body_features.go
- landing_gate_failure.go
- legacy_archive.go
- live_session_metrics.go
- liveness_session_writeback.go
- loop_test_harness.go
- loop_test_harness_public.go
- merge_resolver_closure.go
- merge_resolver_diagnosis_capture.go
- merge_resolver_infra_classifier.go
- mirror.go
- narration_retry.go
- observe_gate_readset.go
- operator.go
- operator_adopt_pr.go
- operator_amend.go
- operator_blocks.go
- operator_contracts.go
- operator_create.go
- operator_dryrun.go
- operator_mutate.go
- operator_priority.go
- operator_refer_back.go
- operator_rekey.go
- operator_replace.go
- operator_replace_compute.go
- operator_route_move.go
- oversized_reseat.go
- park_capability_gap.go
- pass.go
- per_issue_worktree_gc_sweep.go
- plan_activation_driver.go
- plan_activation_escalation.go
- plan_activation_phase.go
- plan_add_children.go
- plan_admission_stale_rearm.go
- plan_admission_sweep.go
- plan_amend_auto.go
- plan_assemble_artifact.go
- plan_assemble_stale_rerere_sweep.go
- plan_assembly_fixer.go
- plan_branch_create.go
- plan_branch_membership.go
- plan_branch_merge_child.go
- plan_branch_ready_hold.go
- plan_branch_self_heal.go
- plan_branch_tracematrix.go
- plan_external_merge_recovery.go
- plan_main_merge.go
- plan_main_merge_assembly_fixer_dispatch.go
- plan_main_merge_relevance.go
- plan_main_merge_sweep.go
- plan_membership.go
- plan_outcome_reconcile.go
- plan_pr_early_open.go
- plan_pr_merge_ci_failure_verdict.go
- plan_pr_merge_pending_ci.go
- plan_pr_merge_pending_ci_stall.go
- plan_pr_ready_flip.go
- plan_project_approve.go
- plan_proposal_orphan.go
- plan_refactor.go
- plan_teardown.go
- planner_failure_classify.go
- pollers.go
- pr_lifecycle_handlers.go
- predicted_footprint_backfill.go
- predicted_footprint_projection.go
- prediction_check.go
- prediction_check_diff.go
- product_footprint_altitude.go
- progress_gatherer.go
- progress_line.go
- proptest_required_helpers.go
- push_retry.go
- recognizer_legality.go
- reconcile.go
- recover_discovering_no_session.go
- recover_session_required_strand.go
- recover_sweep.go
- redecomposition_route.go
- rehydrate_missing.go
- reviewer_cycle_summary.go
- route.go
- same_state_cycle.go
- scheduler.go
- scheduler_pick_offmain.go
- session_error_permanent.go
- spec_amend_disposition.go
- spec_blocked_migration.go
- spec_drafter_dispatch.go
- spec_excerpt.go
- spec_operator_mutate.go
- spec_orphan_reconcile.go
- spec_predecessor_supersede_reconcile.go
- spec_review_guard_recovery.go
- spec_reviewer_dispatch.go
- spec_revise_lifecycle.go
- spec_verify_offmain.go
- stale_session_reap.go
- steward_executor.go
- steward_handler.go
- steward_ledger_mutate.go
- steward_question_handler.go
- steward_recovery.go
- steward_recovery_escalate.go
- steward_recovery_ledger.go
- steward_recovery_outcome.go
- steward_wake.go
- steward_watch_eval.go
- submit_result_audit.go
- supervisor.go
- supervisor_discovering_footprint_sweep.go
- supervisor_dispatch.go
- supervisor_lifecycle.go
- supervisor_planning.go
- supervisor_result_implementer.go
- supervisor_result_implementer_spec_defect.go
- supervisor_result_reviewer.go
- supervisor_transition.go
- terminate_session.go
- throttle_retry.go
- title_guard.go
- topology.go
- triager_dispatcher.go
- triager_executors.go
- triager_gh_verify.go
- verifier_benchmark_executor.go
- verifier_chain.go
- verifier_chain_stale_sweep.go
- verifier_chain_staleness.go
- verifier_executor_dispatch.go
- verifier_llm_oracle.go
- verifier_pre_push_gate.go
- verifier_prop_test_stub.go
- verifier_structural_executor.go
- verifier_type_executor.go
- wedge_detector.go
Directories
¶
| Path | Synopsis |
|---|---|
|
activationcompletionproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2504 R6 (the activation completion gate) and R7 (the evidence-cited closing event).
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2504 R6 (the activation completion gate) and R7 (the evidence-cited closing event). |
|
activationdriverproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../../../../proptest_stubs_test.go) for SOR-2503 R5 (firing) and R11 (dispatch), extended by SOR-2505 with R8 (escalation on probe failure / deadline expiry) and R9 (autonomous-first corrective issue, plan_blocked only via backstop).
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../../../../proptest_stubs_test.go) for SOR-2503 R5 (firing) and R11 (dispatch), extended by SOR-2505 with R8 (escalation on probe failure / deadline expiry) and R9 (autonomous-first corrective issue, plan_blocked only via backstop). |
|
activationprobeproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../../../../proptest_stubs_test.go) for SOR-2502 R10.
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../../../../proptest_stubs_test.go) for SOR-2502 R10. |
|
admissionclaimproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the admission-claim sole-serializer spec (requirements R4, R5, R6).
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the admission-claim sole-serializer spec (requirements R4, R5, R6). |
|
assemblyfixerstripproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the SOR-2342 assembly_fixer corrective-child self-dependency-strip spec (R1, R2, R3).
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the SOR-2342 assembly_fixer corrective-child self-dependency-strip spec (R1, R2, R3). |
|
autonomousroutingproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (the colocated proptest_stubs_test.go in this package's directory, relocated out of the daemon's per-issue profile-sourced prop-test stub basename at the worktree-root path so a later codegen run at that shared path cannot re-clobber it) for SOR-2409 — the autonomous-filing spec-driven routing derivation at the create chokepoint.
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (the colocated proptest_stubs_test.go in this package's directory, relocated out of the daemon's per-issue profile-sourced prop-test stub basename at the worktree-root path so a later codegen run at that shared path cannot re-clobber it) for SOR-2409 — the autonomous-filing spec-driven routing derivation at the create chokepoint. |
|
Package cmdadapter holds the glue between the daemon's storage layer (issuestore.Store, daemon.Daemon) and the httpapi server package's wire-shape interfaces.
|
Package cmdadapter holds the glue between the daemon's storage layer (issuestore.Store, daemon.Daemon) and the httpapi server package's wire-shape interfaces. |
|
declaredgateproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the declared-only dispatch-gate spec (requirements R1, R2, R3).
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the declared-only dispatch-gate spec (requirements R1, R2, R3). |
|
escalationproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the escalation chokepoint primitive (SOR-2325 R1 / R2 / R3).
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the escalation chokepoint primitive (SOR-2325 R1 / R2 / R3). |
|
Package httpapi serves the daemon's versioned HTTP/JSON control plane.
|
Package httpapi serves the daemon's versioned HTTP/JSON control plane. |
|
implgateproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the SPEC-impl-gate-marker spec (requirements R1, R2, R4).
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the SPEC-impl-gate-marker spec (requirements R1, R2, R4). |
|
mainloopbudgetproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub relocated into this subpackage (internal/daemon/mainloopbudgetproptest/proptest_stubs_test.go) for SOR-2447 R7 — the conventional sibling-subpackage placement the prop_test_stub verifier resolves via its worktree walk, keeping the shared worktree-root stub free for the next spec-driven child.
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub relocated into this subpackage (internal/daemon/mainloopbudgetproptest/proptest_stubs_test.go) for SOR-2447 R7 — the conventional sibling-subpackage placement the prop_test_stub verifier resolves via its worktree walk, keeping the shared worktree-root stub free for the next spec-driven child. |
|
materializationpriorityproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (the worktree-root proptest_stubs_test.go, the daemon's per-issue profile-sourced prop-test stub basename) for SOR-2533 R4 — the materialization priority-inheritance fix in applyPlanReviewerApprove.
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (the worktree-root proptest_stubs_test.go, the daemon's per-issue profile-sourced prop-test stub basename) for SOR-2533 R4 — the materialization priority-inheritance fix in applyPlanReviewerApprove. |
|
offmainproptest
|
|
|
gen
Package gen is the project-local generator-helper package backing the committed prop_test stub at the worktree root (proptest_stubs_test.go, the daemon's profile-sourced prop-test stub basename) for SPEC-SOR-2440's off-main-work requirements R3, R4, R5, and the main-loop responsiveness requirement R11.
|
Package gen is the project-local generator-helper package backing the committed prop_test stub at the worktree root (proptest_stubs_test.go, the daemon's profile-sourced prop-test stub basename) for SPEC-SOR-2440's off-main-work requirements R3, R4, R5, and the main-loop responsiveness requirement R11. |
|
oversizedparityproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the oversized-child estimate-parity + reroute-to-planning requirements (SOR-2326 R5 / R6) of the escalation-routing spec.
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the oversized-child estimate-parity + reroute-to-planning requirements (SOR-2326 R5 / R6) of the escalation-routing spec. |
|
pendingproposalproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the codegen-owned root prop_test stub (../../../../proptest_stubs_test.go) for R4-R6 of the pending-proposal plan-state reconciliation spec (SPEC-SOR-2525).
|
Package gen is the operator-supplied generator-helper package backing the codegen-owned root prop_test stub (../../../../proptest_stubs_test.go) for R4-R6 of the pending-proposal plan-state reconciliation spec (SPEC-SOR-2525). |
|
planoutcomereconcileproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the approved-proposal full-outcome reconcile sweep (SOR-2354 R2 / R3).
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the approved-proposal full-outcome reconcile sweep (SOR-2354 R2 / R3). |
|
planwipcapproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (the worktree-root proptest_stubs_test.go, the daemon's profile-sourced prop-test stub basename) for the plan-level WIP-cap spec (requirement R1).
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (the worktree-root proptest_stubs_test.go, the daemon's profile-sourced prop-test stub basename) for the plan-level WIP-cap spec (requirement R1). |
|
proposaloutcomeproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the SOR-2350 proposal-outcome-reconcile spec's R1 and R4.
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the SOR-2350 proposal-outcome-reconcile spec's R1 and R4. |
|
r1proptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the SOR-2279 plan-membership spec R1.
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the SOR-2279 plan-membership spec R1. |
|
r2proptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the SOR-2279 plan-membership spec R2.
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the SOR-2279 plan-membership spec R2. |
|
r3proptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SPEC-RIDTRACE R3.
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SPEC-RIDTRACE R3. |
|
r4proptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2268 R4.
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2268 R4. |
|
r4r5proptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the SOR-2279 plan-membership spec R4 and R5.
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the SOR-2279 plan-membership spec R4 and R5. |
|
r6proptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2279 R6.
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2279 R6. |
|
r7proptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2327 R7.
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2327 R7. |
|
sessionleakproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (the worktree-root proptest_stubs_test.go, the daemon's per-issue profile-sourced prop-test stub basename) for SOR-2535 — the dispatch-leaked-session termination + discovery double-dispatch guard.
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (the worktree-root proptest_stubs_test.go, the daemon's per-issue profile-sourced prop-test stub basename) for SOR-2535 — the dispatch-leaked-session termination + discovery double-dispatch guard. |
|
snapshotroundtripproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2453 R10.
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2453 R10. |
|
specamendrouteproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the planner spec-amend-required routing requirement (SOR-2328 R9).
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the planner spec-amend-required routing requirement (SOR-2328 R9). |
|
specchipproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2269's spec-driven-indicator spec (requirements R1, R2, R3, R5).
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2269's spec-driven-indicator spec (requirements R1, R2, R3, R5). |
|
specrearmproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2514's spec-lifecycle recovery spec (requirements R17, R19, R20).
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for SOR-2514's spec-lifecycle recovery spec (requirements R17, R19, R20). |
|
specreviewentrygateproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the spec_review entry gate (SPEC-SOR-2387 R4).
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the spec_review entry gate (SPEC-SOR-2387 R4). |
|
specreviewerguardproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub at the worktree root (proptest_stubs_test.go, the daemon's profile-sourced prop-test stub basename) for SPEC-SOR-2387 R1+R2+R3.
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub at the worktree root (proptest_stubs_test.go, the daemon's profile-sourced prop-test stub basename) for SPEC-SOR-2387 R1+R2+R3. |
|
specverifyproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stubs (../proptest_stubs_test.go) for the off-main spec-verification seam (SOR-2272).
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stubs (../proptest_stubs_test.go) for the off-main spec-verification seam (SOR-2272). |
|
specverifyrecoveryproptest
|
|
|
gen
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the boot-recovery re-drive sweep (SOR-2274 R5 / R7).
|
Package gen is the operator-supplied generator-helper package backing the committed prop_test stub (../proptest_stubs_test.go) for the boot-recovery re-drive sweep (SOR-2274 R5 / R7). |