Documentation
¶
Overview ¶
Package httpapi serves the daemon's versioned HTTP/JSON control plane.
Mounted at /v1/, it is the daemon's only operator transport: the status / freeze / unfreeze / stop / replan handlers plus a tailable /v1/events stream that returns audit-trail records (sourced from the SQLite events table) as JSON or SSE.
Authentication is bearer-token only. The token lives in a 0600 file on disk; the daemon reads it at boot, the operator CLI reads it from the same path (or from $SORCERER_TOKEN). There is no TLS — operators front this with a reverse proxy when exposing beyond loopback (per the eliminate-linear plan).
The package depends on a narrow Backend interface implemented by *daemon.Daemon, so the import graph runs daemon → httpapi (parent → child) without httpapi pulling the daemon package back upward.
Index ¶
- Constants
- Variables
- func FormatSubscriptionTime(t time.Time) string
- func RouteHistoryKinds() []string
- type ActivationBackend
- type ActivationListResponse
- type ActivationOverrideRequest
- type ActivationOverrideResponse
- type ActivationPlanView
- type ActivationProbeView
- type ActiveProposal
- type AnswerStewardLedgerQuestionRequest
- type Backend
- type CapabilityGapBlockView
- type CascadeImpactError
- type CommentHit
- type CommentView
- type Config
- type CreateStewardLedgerRowRequest
- type DashboardRow
- type DuplicateEntry
- type DuplicateError
- type EventView
- type FindingDismissResponse
- type FindingListResponse
- type FindingView
- type FindingsBackend
- type FindingsFilter
- type ForwardQueuedError
- type ForwardRequiredError
- type ForwardedCreateError
- type HistoryView
- type IssueAdoptPRInput
- type IssueAgentShellBackend
- type IssueAgentShellConversation
- type IssueAgentShellStartOptions
- type IssueAgentShellTurn
- type IssueCreateInput
- type IssueDetail
- type IssueDryRunResult
- type IssueListFilter
- type IssueMutator
- type IssuePR
- type IssueReader
- type IssueSummary
- type Logger
- type PlanAddChildInput
- type PlanAddChildrenInput
- type PlanAddChildrenResult
- type PlanApproveProjector
- type PlanBranchCommit
- type PlanBranchRepoStatus
- type PlanCitations
- type PlanRefactorInput
- type PlanRefactorResult
- type PlanStore
- type PlanSubmitValidator
- type RecoverBackend
- type RecoverRequest
- type RecoverResponse
- type RecoverTransition
- type RequirementVerdictEntryView
- type ReviewConcern
- type ReviewVerdictView
- type RoleHealthBackend
- type RolePauseClearRequest
- type RolePauseClearResponse
- type RolePauseListResponse
- type RolePauseSetRequest
- type RolePauseSetResponse
- type RolePauseView
- type RouteClassification
- type RouteClassifier
- type RouteMoveForwardError
- type RouteMoveRefusedError
- type SearchHit
- type SearchOptions
- type SearchResults
- type SelfReflectionListResponse
- type SelfReflectionView
- type SelfReflectionsBackend
- type SelfReflectionsFilter
- type Server
- type SetRolePauseInput
- type SpecAmendResponse
- type SpecApproveResponse
- type SpecBackend
- type SpecFindingView
- type SpecFindingsSummary
- type SpecPatchResponse
- type SpecSuppressFindingResponse
- type SpecVerifyResponse
- type SpecVersionMeta
- type SpecVersionView
- type SpecView
- type StateDescriptor
- type StatesResponse
- type StewardLedgerBackend
- type StewardLedgerCreateResponse
- type StewardLedgerListResponse
- type StewardLedgerRowView
- type SubmittedIssue
- type SubscriptionBackend
- type SubscriptionClearResponse
- type SubscriptionListResponse
- type SubscriptionResetRequest
- type SubscriptionResetResponse
- type SubscriptionView
- type TopologyLookup
- type UpdateStewardLedgerRowRequest
Constants ¶
const RolePauseIndefiniteSentinel = apiclient.RolePauseIndefiniteSentinel
RolePauseIndefiniteSentinel mirrors apiclient.RolePauseIndefiniteSentinel for callers inside the httpapi package — see that constant for documentation.
Variables ¶
var ( // ErrSpecNotFound is returned when a planning key has no spec version // (or the requested historical version does not exist). ErrSpecNotFound = errors.New("spec not found") // ErrSpecFindingNotFound is returned when a suppress targets a finding // id that does not belong to the planning issue's current spec. ErrSpecFindingNotFound = errors.New("spec finding not found") // ErrSpecApprovalBlocked is returned when approve is refused because at // least one error-severity finding is still open (docs/spec-driven.md // § 7.5: approval requires every error finding resolved or suppressed). ErrSpecApprovalBlocked = errors.New("spec approval blocked: open error-severity findings") // ErrSpecStateConflict is returned when a mutation is invalid in the // planning issue's current spec plan state (e.g. verify/amend on an // approved spec, patch from a non-drafting state). ErrSpecStateConflict = errors.New("operation not valid in current spec state") // ErrSpecPatchFailed is returned when ApplyPatchAtomic rejected the // patch (parse / apply / validation failure); the atomic apply rolled // back, so the spec version is unchanged. ErrSpecPatchFailed = errors.New("spec patch application failed") )
Typed sentinels a SpecBackend implementation returns; the handlers map each to an HTTP status. ErrSpecNotFound / ErrSpecFindingNotFound → 404; ErrSpecApprovalBlocked / ErrSpecStateConflict → 409; ErrSpecPatchFailed → 422 (the JSON-Patch could not be applied — the atomic apply rolled back).
var ErrBadRequest = errors.New("bad request")
ErrBadRequest is the typed sentinel an IssueMutator implementation returns when an operator-driven create request fails a daemon-side validation (repo not in allowlist, unknown depends_on key, unsupported kind). The POST /v1/issues handler maps it to 400 with the wrapped message verbatim so the operator sees which input tripped the check. Any error wrapping this sentinel satisfies errors.Is(err, ErrBadRequest).
var ErrCycle = errors.New("would create dependency cycle")
ErrCycle is the typed sentinel an IssueMutator implementation returns when an operator-driven AddBlocker would close a cycle in the dep graph. The /v1/issues/{src}/blocks/{dst} POST handler maps it to 409 with the underlying message; the CLI surfaces it with a "would create cycle" line so operators recognize the gate. Any error wrapping this sentinel satisfies errors.Is(err, ErrCycle).
var ErrFindingNotFound = errors.New("finding not found")
ErrFindingNotFound is the typed sentinel a FindingsBackend implementation returns when a finding lookup misses. The /v1/findings/{id} and /v1/findings/{id}/dismiss handlers map it to 404; any other error surfaces as 500.
var ErrInvalidTransition = errors.New("invalid state transition")
ErrInvalidTransition is the typed sentinel an IssueMutator implementation returns when an operator-driven SM move is illegal per the in-band Issue / Plan transition tables. The /v1/issues/{key}/transition handler maps it to 409 with the underlying message verbatim. Any error wrapping this sentinel (e.g. the daemon wrapping `sm.ErrInvalidTransition`) satisfies errors.Is(err, ErrInvalidTransition).
var ErrLinearAPI = errors.New("linear API failure")
ErrLinearAPI is the typed sentinel an IssueMutator.CreateIssue implementation returns when the configured LinearIssueCreator callback fails. The POST /v1/issues handler maps it to 502 Bad Gateway with the wrapped message verbatim so the operator can distinguish a Linear-side failure (their job to retry or escalate) from a local-side failure (500, the daemon's job). Any error wrapping this sentinel satisfies errors.Is(err, ErrLinearAPI).
var ErrNotFound = errors.New("httpapi: issue not found")
ErrNotFound is the typed sentinel an IssueReader implementation returns when a key-shaped lookup misses. The /v1/issues/{key} handler maps it to 404; any other error maps to 500.
var ErrProposalConflict = errors.New("proposal already reviewed")
ErrProposalConflict is the typed sentinel a PlanStore implementation returns when a review against an already-decided proposal is rejected. The review handler maps it to 409.
var ErrRolePauseNotFound = errors.New("role pause not found")
ErrRolePauseNotFound is the typed sentinel a RoleHealthBackend implementation returns when ClearRolePause is called against a role that has no row. The /v1/role-pauses/clear handler maps it to 200 with `cleared: false` — clearing an unpaused role is the idempotent re-clear case and operators see it as a successful no-op, NOT a 404.
var ErrSubscriptionNotFound = errors.New("subscription not found")
ErrSubscriptionNotFound is the typed sentinel a SubscriptionBackend implementation returns when ClearSubscription is called against a name that doesn't exist in the OAuth pool. The /v1/subscriptions/{name}/clear handler maps it to 404 with the offending name in the message — distinct from the "already healthy" idempotent path, which returns 200 with `cleared:false`.
var ErrValidationRejected = errors.New("validation rejected")
ErrValidationRejected is the typed sentinel a PlanStore implementation returns when the in-transaction validator callback supplied by the submit handler returned a non-empty slice. The handler maps this (via errors.Is) to 422 Unprocessable Entity with body `{"error":"validation_failed","reason":"<wrapped message>"}`.
Functions ¶
func FormatSubscriptionTime ¶
FormatSubscriptionTime renders a time.Time for the `/v1/subscriptions` wire shape. Zero values become "" so the SubscriptionView struct's `,omitempty` tags drop the field entirely — matching the `/v1/status` `subscriptions:` block, which omits zero time fields via its map[string]any construction. Used by the cmdadapter; exported so the test fixtures share one formatter with production code.
func RouteHistoryKinds ¶
func RouteHistoryKinds() []string
RouteHistoryKinds returns a fresh copy of the canonical kind set the SOR-1279 `--show` inspection surfaces. Exposed so daemon-side adapter implementations can drive their events-table query off the same source-of-truth slice the handler renders — a drift between the adapter's filter and the handler's expected kind set would silently hide one row class.
Types ¶
type ActivationBackend ¶
type ActivationBackend interface {
// ListActivating returns every plan in plan_activating with each owned
// activation criterion's current probe status + evidence pointer.
ListActivating(ctx context.Context) (ActivationListResponse, error)
// OverrideProbe marks the named activation probe satisfied on the planning
// issue with the operator's mandatory reason and returns the plan's state at
// commit. ErrNotFound (unknown/non-planning key), ErrSpecStateConflict (plan
// not in plan_activating), ErrBadRequest (empty/unknown rid or empty reason).
OverrideProbe(ctx context.Context, planningKey, requirementID, reason string) (ActivationOverrideResponse, error)
}
ActivationBackend is the narrow daemon-side hook the /v1/activation handlers consume (implemented by cmdadapter.ActivationBackend). ListActivating is a per-request sync read of the live plan_activating set; OverrideProbe routes through the daemon's single-writer main loop, which emits the audited plan_activating_probe_override event and triggers the activation re-poll. The backend returns errors wrapping the httpapi.Err* sentinels so the handler maps them to the right status without a translation layer.
type ActivationListResponse ¶
type ActivationListResponse = apiclient.ActivationListResponse
Activation wire shapes are defined once in internal/apiclient. These aliases keep the handler signatures readable while preserving the single definition; the ActivationBackend implementation (cmdadapter.ActivationBackend) and the operator CLI verbs read the same apiclient types.
type ActivationOverrideRequest ¶
type ActivationOverrideRequest = apiclient.ActivationOverrideRequest
Activation wire shapes are defined once in internal/apiclient. These aliases keep the handler signatures readable while preserving the single definition; the ActivationBackend implementation (cmdadapter.ActivationBackend) and the operator CLI verbs read the same apiclient types.
type ActivationOverrideResponse ¶
type ActivationOverrideResponse = apiclient.ActivationOverrideResponse
Activation wire shapes are defined once in internal/apiclient. These aliases keep the handler signatures readable while preserving the single definition; the ActivationBackend implementation (cmdadapter.ActivationBackend) and the operator CLI verbs read the same apiclient types.
type ActivationPlanView ¶
type ActivationPlanView = apiclient.ActivationPlanView
Activation wire shapes are defined once in internal/apiclient. These aliases keep the handler signatures readable while preserving the single definition; the ActivationBackend implementation (cmdadapter.ActivationBackend) and the operator CLI verbs read the same apiclient types.
type ActivationProbeView ¶
type ActivationProbeView = apiclient.ActivationProbeView
Activation wire shapes are defined once in internal/apiclient. These aliases keep the handler signatures readable while preserving the single definition; the ActivationBackend implementation (cmdadapter.ActivationBackend) and the operator CLI verbs read the same apiclient types.
type ActiveProposal ¶
type ActiveProposal struct {
ID int64
Outcome string // 'pending' or 'approved'
OriginIssue string
PlannerSession string
}
ActiveProposal is the narrow view of a proposals row the submit handler's duplicate guard consults. Mirrors the subset of issuestore.Proposal that the guard reads (id, outcome, origin_issue, planner_session) without leaking the storage-layer struct into the httpapi package.
type AnswerStewardLedgerQuestionRequest ¶
type AnswerStewardLedgerQuestionRequest = apiclient.AnswerStewardLedgerQuestionRequest
Steward ledger wire shapes are defined once in internal/apiclient. These aliases keep the handler signatures readable while preserving the single definition; the StewardLedgerBackend implementation and the operator CLI read the same apiclient types.
type Backend ¶
type Backend interface {
// SubmitOperatorCommand pushes one operator command onto the
// daemon's event channel and waits up to ~5s for an ack. The
// command string carries any payload (replan uses
// "replan:<issue>:<reason>"). Returns nil on accept, the
// applied error on rejection, or an "ack timeout" error if the
// main loop is wedged.
SubmitOperatorCommand(ctx context.Context, command string) error
// StatusSnapshot returns the operator-facing status map served
// at GET /v1/status.
StatusSnapshot() (map[string]any, error)
}
Backend is the narrow daemon-facing interface the HTTP handlers consume. Defined here so the httpapi package doesn't have to import internal/daemon. *daemon.Daemon implements it.
type CapabilityGapBlockView ¶
type CapabilityGapBlockView = apiclient.CapabilityGapBlockView
CapabilityGapBlockView is the operator-visible capability-gap park signal on an issue detail (SPEC-SOR-2923-v1 R10, SOR-3085). See apiclient.CapabilityGapBlockView.
type CascadeImpactError ¶
type CascadeImpactError struct {
ParentPlan string
SiblingsOrphaned []string
SiblingsWithLostSquashes []string
Message string
Hint string
}
CascadeImpactError is the typed error an IssueMutator.CancelIssue implementation returns when canceling a `plan_branch` implementation child would cascade-abandon its parent Planning Issue and orphan its siblings (and, for siblings whose squash already landed on the plan branch, drop those commits when the plan branch is torn down). The POST /v1/issues/{key}/cancel handler maps it to 409 Conflict with the documented body shape:
{
"error": "cascade_impact",
"message": "<one-line summary>",
"parent_plan": "SOR-N",
"siblings_orphaned": ["SOR-X", ...],
"siblings_with_lost_squashes": ["SOR-Y", ...],
"hint": "<operator next-step>"
}
The operator opts in to the cascade by passing `cascade_plan_abandon: true` in the cancel body (CLI flag `--cascade-plan-abandon`). The daemon refuses the cancel when this error fires and the flag is absent; passing the flag bypasses the gate and the cascade proceeds.
errors.As unwraps to *CascadeImpactError so the handler can render the structured fields without losing the typed payload.
func (*CascadeImpactError) Error ¶
func (e *CascadeImpactError) Error() string
Error renders a compact summary so log lines remain readable.
type CommentView ¶
type CommentView = apiclient.CommentView
CommentView mirrors issuestore.Comment on the wire. See apiclient.CommentView.
type Config ¶
type Config struct {
// Bind is the listener address (host:port). 127.0.0.1:7777 is the
// recommended default; operators front this with a reverse proxy
// when exposing beyond loopback.
Bind string
// Token is the bearer token clients must present. Empty rejected.
Token string
// Backend is the daemon hook. Required.
Backend Backend
// IssueReader serves the /v1/issues* read endpoints. Required.
IssueReader IssueReader
// IssueMutator serves the /v1/issues/{key}/* write endpoints.
// Required — the server refuses to start without one even if no
// mutation traffic is expected, so a misconfiguration is caught
// at boot rather than on the first transition request.
IssueMutator IssueMutator
// PlanStore is the proposals lifecycle's persistence surface,
// consumed by /v1/plan/submit, /v1/plan/{id}/review, and
// /v1/plan/{id}/dump. Optional: when nil, every /v1/plan/*
// endpoint returns 500 "plan store not configured" so the route
// table doesn't need a conditional registration. The post-cutover
// daemon wires this; the existing planner pipeline doesn't depend
// on it, so the endpoints can ship unwired without breaking
// anything.
PlanStore PlanStore
// PlanApproveProjector, when non-nil, is called by handlePlanReview
// immediately after a successful Store.ReviewProposal(approve) commit
// to project the proposal's children into the daemon's d.state.Issues
// on the single-writer main goroutine (SOR-2323). Nil disables the
// eager projection — the recovery sweep then remains the sole
// projection path (the pre-SOR-2323 behavior), which is the right
// default for test servers that don't run a live event loop.
PlanApproveProjector PlanApproveProjector
// PlanAllowedRepos is the project's strict push allowlist passed
// through to the plan validator (rule 4). Empty disables the
// rule, which is fine in tests but rejected at runtime by the
// validator itself.
PlanAllowedRepos []string
// PlanCitations is the file-existence checker passed through to
// the plan validator (rule 1). Nil disables citation checking.
PlanCitations PlanCitations
// PlanDeclaredArtifactPaths is the project's flattened set of declared
// generated-artifact output paths, passed through to the plan validator's
// rule 14b (validateFootprintDeclaredArtifactPaths) so a proposal child
// whose predicted_footprint declares a daemon-generated artifact path (or
// the built-in docs/traceability/ matrix prefix) is hard-rejected at submit
// time. Production wiring (cmd/sorcererd/start.go) passes the same flattened
// value the daemon threads into seatPredictedFootprint. Empty/nil degrades
// the rule to the matrix-only behavior, so legacy test wiring keeps passing.
PlanDeclaredArtifactPaths []string
// PlanSelfHostingScaffolding is the project's config-declared
// self-hosting-scaffolding path prefix set (config.SelfHostingScaffolding),
// passed through to the plan validator's R7 advisory product-altitude check
// (validateFootprintProductAltitude) so a flagged product-domain child's
// no-product-source footprint paths can be characterized as recognized
// self-hosting scaffolding via footprint.IsSelfHostingScaffolding. Production
// wiring (cmd/sorcererd/start.go) passes config.SelfHostingScaffolding(cfg),
// the same value the seat-time altitude gate consumes. Empty/nil classifies
// every path non-scaffolding (no config = no scaffolding recognized), which
// leaves the flag itself intact — flagging keys on the product-engine-source
// predicate, not this set.
PlanSelfHostingScaffolding []string
// PlanOpenPRCounter returns the number of non-terminal issues in
// the daemon's state whose `latestPRSnapshots` entry contains at
// least one element with `State == "OPEN"` and a non-empty
// `PRURL`. Used by handlePlanSubmit to populate
// ValidationContext.OpenPRCount (which gates the plan validator's
// rule 8 `overlap_considered` check). Optional: when nil the
// handler defaults OpenPRCount to 0 so existing test wiring that
// doesn't supply this counter continues to pass — at the cost of
// disabling rule 8 in those tests, which the test fixtures don't
// exercise. Production wiring (cmd/sorcererd/start.go) supplies a
// counter that reads d.latestPRSnapshots on the supervisor main
// goroutine.
PlanOpenPRCounter func(ctx context.Context) int
// PlanImplementerMaxAgeFloorSec is the lower-bound the plan
// validator's rule 9 enforces on every per-issue `max_age_seconds`
// override. Production wiring passes
// `int(cfg.SessionMaxAges()["implementer"].Seconds())` so the
// planner can't undercut the project-wide default. A zero value
// disables the floor check (zero accepted, ceiling still 86400) so
// legacy test wiring that doesn't supply the field keeps passing.
PlanImplementerMaxAgeFloorSec int
// OversizedCeiling is the per-project dispatch-escalate ceiling (in
// predicted cache tokens) threaded onto every ValidateProposal call in the
// submit-proposal handler (lintProposalFromResult). DetectOversizedChild
// upgrades an oversized_child finding to gating SeverityCritical only when a
// child's estimate exceeds it — the same boundary the dispatch gate
// escalates on (SOR-2708). Production wiring passes
// cfg.OversizedDispatchEscalateCeiling(); zero in tests that don't configure
// it leaves the ceiling gate inactive (findings advisory only).
OversizedCeiling int64
// IntegrationSizeCap is the per-project proposal-time submit-gate cap on a
// CIPB plan's cumulative integration FILE footprint (the de-duplicated distinct-
// file count), threaded onto every ValidateProposal call in the submit-proposal
// handler (lintProposalFromResult) as Proposal.IntegrationSizeCap.
// DetectOversizedPlan rejects an over-cap proposal as gating oversized_plan,
// directing the planner to decompose into a depends_on-chained set of smaller
// sub-plans. Production wiring passes cfg.IntegrationSizeCap(); zero in tests
// that don't configure it leaves the file axis of the oversized_plan gate
// inactive.
IntegrationSizeCap int64
// IntegrationSizeCapChildren is the child-count companion to IntegrationSizeCap,
// threaded onto Proposal.IntegrationSizeCapChildren at the same submit gate so
// DetectOversizedPlan rejects a proposal whose child count exceeds it. Production
// wiring passes cfg.IntegrationSizeCapChildren(); zero in tests that don't
// configure it leaves the child axis inactive (when BOTH are zero the lint is a
// no-op).
IntegrationSizeCapChildren int64
// RecoverBackend serves the POST /v1/recover endpoint. Optional:
// when nil, the handler returns 500 "recover backend not configured"
// so the route table doesn't need a conditional registration. The
// production daemon wires this; tests that exercise other
// endpoints can omit it.
RecoverBackend RecoverBackend
// RoleHealthBackend serves GET /v1/role-pauses and POST
// /v1/role-pauses/clear. Optional: when nil, both endpoints
// return 500 "role health backend not configured" so the route
// table doesn't need a conditional registration. The production
// daemon wires this with a thin issuestore adapter.
RoleHealthBackend RoleHealthBackend
// SubscriptionBackend serves GET /v1/subscriptions and POST
// /v1/subscriptions/{name}/clear. Optional: when nil, both
// endpoints return 500 "subscription backend not configured" so
// the route table doesn't need a conditional registration. The
// production daemon wires this with a thin clauderunner adapter
// (SOR-1080) so the auth-flagged subscription recovery path
// doesn't require a daemon restart.
SubscriptionBackend SubscriptionBackend
// FindingsBackend serves GET /v1/findings, GET /v1/findings/{id},
// and POST /v1/findings/{id}/dismiss. Optional: when nil, every
// findings endpoint returns 500 "findings backend not configured"
// so the route table doesn't need a conditional registration. The
// production daemon wires this with a thin issuestore adapter.
FindingsBackend FindingsBackend
// SpecBackend serves the seven /v1/specs/* endpoints (spec-driven
// phase B): the two GET read paths plus the verify / patch / amend /
// approve / suppress-finding mutations. Optional: when nil, every
// spec endpoint returns 500 "specs backend not configured" so the
// route table doesn't need a conditional registration. The production
// daemon wires this with cmdadapter.SpecBackend (reads via the
// issuestore, mutations through the daemon's single-writer main loop).
SpecBackend SpecBackend
// SelfReflectionsBackend serves GET /v1/self-reflections. Optional:
// when nil, the endpoint returns 500 "self-reflections backend not
// configured" so the route table doesn't need a conditional
// registration. The production daemon wires this with a thin
// issuestore adapter; rows are append-only audit (no write/dismiss
// surface — the operator's intervention point is the daemon-auto-
// filed meta-issue from the hourly blocker-rollup sweep).
SelfReflectionsBackend SelfReflectionsBackend
// ActivationBackend serves the two /v1/activation endpoints (SOR-2504):
// GET /v1/activation lists plans in the post-merge plan_activating phase
// with each probe's status + evidence pointer, and POST
// /v1/activation/{key}/override marks one activation probe satisfied with
// a mandatory operator reason (the spec R6 sole exception). Optional: when
// nil, both endpoints return 500 "activation backend not configured" so
// the route table doesn't need a conditional registration. The production
// daemon wires this with cmdadapter.ActivationBackend (the override routes
// through the daemon's single-writer main loop; the list is a per-request
// sync read of the live plan set, no time-based cache).
ActivationBackend ActivationBackend
// StewardLedgerBackend serves the four /v1/steward/ledger endpoints
// (SOR-2659): GET lists rows (optional ?row_type= filter), POST creates a
// row, POST .../{id} updates a row, POST .../{id}/expire hard-deletes one.
// Optional: when nil, every endpoint returns 500 "steward ledger backend
// not configured" so the route table doesn't need a conditional
// registration. The production daemon wires cmdadapter.StewardLedgerBackend
// (reads direct to the issuestore; writes route through the daemon's
// single-writer main loop, which emits the audited steward_ledger_write
// event — spec R5).
StewardLedgerBackend StewardLedgerBackend
// IssueAgentShellBackend spawns the issue_agent subprocess backing the
// /v1/issue-agent-shell WebSocket endpoint. Optional: when nil, a connect
// (after the bearer check passes) is rejected with 503 before the
// WebSocket upgrade so the route table stays unconditional. The
// production daemon wires a cmdadapter that holds the resolved
// issue_agent effort so the httpapi package stays off the
// clauderunner import.
IssueAgentShellBackend IssueAgentShellBackend
// IssueAgentShellIdleTimeout bounds an idle /v1/issue-agent-shell session: when no
// operator-side message arrives for this long the handler closes
// the socket with the `session timed out` reason and reaps the
// subprocess. Zero falls back to issueAgentShellDefaultIdleTimeout so a
// misconfigured wiring still bounds the subprocess. Production
// passes time.Duration(cfg.Limits.IssueAgentShellIdleTimeoutSeconds)*time.Second.
IssueAgentShellIdleTimeout time.Duration
// Topology is the daemon's peer-cache lookup for the SOR-1214
// cross-daemon auth path on POST /v1/issues. Optional: when nil,
// the dual-auth branch is disabled and requests carrying
// `X-Sorcerer-Forwarded-From` 401 with the same envelope as a
// local-bearer mismatch. Production wiring passes the
// (*daemon.Topology) constructed in cmd/sorcererd/start.go.
Topology TopologyLookup
// RouteClassifier serves the SOR-1279 dry-run POST
// /v1/issues/route-classify endpoint. Optional: when nil, the
// handler returns 503 Service Unavailable with the documented
// `cross-daemon routing disabled (no topology cache)` body — the
// only correct answer when the daemon was started without a UI
// endpoint (no peers to forward to, so routing classification has
// no input). Production wiring passes a cmdadapter.RouteClassifier
// that calls daemon.ResolveDestination against the live topology
// snapshot.
RouteClassifier RouteClassifier
// Logger is optional.
Logger Logger
}
Config holds everything Server.New needs. All fields except Logger are required.
type CreateStewardLedgerRowRequest ¶
type CreateStewardLedgerRowRequest = apiclient.CreateStewardLedgerRowRequest
Steward ledger wire shapes are defined once in internal/apiclient. These aliases keep the handler signatures readable while preserving the single definition; the StewardLedgerBackend implementation and the operator CLI read the same apiclient types.
type DashboardRow ¶
type DashboardRow = apiclient.DashboardRow
DashboardRow is one row of GET /v1/dashboard. See apiclient.DashboardRow.
type DuplicateEntry ¶
DuplicateEntry is one row of DuplicateError.Duplicates. Mirrors the SOR-57 wire shape: `{"key":"SOR-N","overlap":"<one-line>"}`.
type DuplicateError ¶
type DuplicateError struct {
Duplicates []DuplicateEntry
}
DuplicateError is the typed error an IssueMutator.CreateIssue implementation returns when the SOR-57 dedupe gate refuses a create. The POST /v1/issues handler maps it to 409 Conflict with the documented body shape:
{"error":"duplicate","duplicates":[{"key":"SOR-N","overlap":"..."}]}
errors.As unwraps to *DuplicateError so the handler can render every overlapping entry without losing the typed payload.
func (*DuplicateError) Error ¶
func (e *DuplicateError) Error() string
Error renders a compact summary so log lines remain readable.
type EventView ¶
EventView is one row from the daemon's events table on the wire. See apiclient.EventView for the canonical declaration.
type FindingDismissResponse ¶
type FindingDismissResponse = apiclient.FindingDismissResponse
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type FindingListResponse ¶
type FindingListResponse = apiclient.FindingListResponse
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type FindingView ¶
type FindingView = apiclient.FindingView
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type FindingsBackend ¶
type FindingsBackend interface {
// ListFindings returns FindingView rows matching the filter,
// newest-first. An empty result is a non-nil empty slice (the JSON
// envelope is `{"findings":[]}`).
ListFindings(ctx context.Context, filter FindingsFilter) ([]FindingView, error)
// GetFinding returns the FindingView for id. Returns
// ErrFindingNotFound when no row with that id exists.
GetFinding(ctx context.Context, id int64) (FindingView, error)
// DismissFinding flips the finding's outcome to `dismissed`.
// Returns dismissed=true when it flipped a non-dismissed finding;
// dismissed=false on an idempotent re-dismiss of an already-
// dismissed finding (NOT an error — the handler maps it to 200).
// Returns ErrFindingNotFound when no row with that id exists.
DismissFinding(ctx context.Context, id int64) (dismissed bool, err error)
}
FindingsBackend is the narrow daemon-side hook the /v1/findings* handlers consume. Implemented by a thin adapter over the issuestore (cmdadapter.FindingsBackend). Listing and detail are read-only; DismissFinding flips a finding's outcome to `dismissed`.
type FindingsFilter ¶
type FindingsFilter struct {
SinceTS int64 // unix seconds; rows with ts >= SinceTS only when > 0
Limit int // 0 → unlimited
Outcome string // exact match on outcome
Signature string // exact match on pattern_signature
}
FindingsFilter narrows a ListFindings query. Built from the optional query params on GET /v1/findings. Zero-valued fields impose no constraint.
type ForwardQueuedError ¶
type ForwardQueuedError struct {
PendingID int64
}
ForwardQueuedError is the typed error an IssueMutator.CreateIssue implementation returns when the SOR-1216 cross-daemon forwarder's delivery attempt failed transiently and the create body was appended to pending_cross_daemon_forwards for the retry sweep to drain. The POST /v1/issues handler maps it to 202 Accepted with the body shape:
{"queued":true,"pending_id":<id>}
errors.As unwraps to *ForwardQueuedError so the handler can render the queued-row id without losing the typed payload.
func (*ForwardQueuedError) Error ¶
func (e *ForwardQueuedError) Error() string
Error renders a compact summary so log lines remain readable.
type ForwardRequiredError ¶
ForwardRequiredError is the typed error an IssueMutator.CreateIssue implementation returns when the SOR-1184 cross-daemon resolver decides a prospective issue belongs to a peer daemon (its named repos are owned by another daemon in the topology). The POST /v1/issues handler maps it to 409 Conflict with the documented body shape:
{"action":"forward_required","target_daemon":"<name>",
"target_url":"<url>","rationale":"<text>"}
The field names are the wire contract Phase 2's forward mechanism (SOR-1185) consumes — preserved verbatim so the downstream phase doesn't need to renegotiate. errors.As unwraps to *ForwardRequiredError so the handler can render the target without losing the typed payload. Phase 1 ships the contract only; no daemon-to-daemon forward is implemented yet.
func (*ForwardRequiredError) Error ¶
func (e *ForwardRequiredError) Error() string
Error renders a compact summary so log lines remain readable.
type ForwardedCreateError ¶
ForwardedCreateError is the typed result an IssueMutator.CreateIssue implementation returns when the SOR-1216 cross-daemon forwarder successfully proxied the create to a peer daemon. It is a "typed error" only in the carrier sense — the create succeeded, just on a remote daemon — matching the *ForwardRequiredError precedent of surfacing a non-local routing outcome as a typed error the handler errors.As-unwraps. The POST /v1/issues handler maps it to 201 Created with the body shape:
{"key":"<destKey>","daemon":"<destDaemon>","forwarded":true}
func (*ForwardedCreateError) Error ¶
func (e *ForwardedCreateError) Error() string
Error renders a compact summary so log lines remain readable.
type HistoryView ¶
type HistoryView = apiclient.HistoryView
HistoryView mirrors issuestore.HistoryEntry on the wire. See apiclient.HistoryView.
type IssueAdoptPRInput ¶
type IssueAdoptPRInput = apiclient.IssueAdoptPRInput
IssueAdoptPRInput is the POST /v1/issues/adopt-pr payload. See apiclient.IssueAdoptPRInput.
type IssueAgentShellBackend ¶
type IssueAgentShellBackend interface {
// StartConversation spawns the long-lived issue_agent
// subprocess for ONE WebSocket connection (Shape A) or for ONE
// per-convID registry entry (Shape B). Canceling ctx (operator
// disconnect, idle reap, concurrent-connect eviction) reaps the
// whole subprocess tree. A token-pool exhaustion (or any spawn
// failure) returns a non-nil error and no conversation.
//
// SOR-333: opts.ResumeConvID, when non-empty, threads `--resume
// <id>` onto the claude argv so the spawned subprocess resumes the
// named on-disk conversation. The Shape B registry passes a non-
// empty value only on a reconnect after a between-connections reap
// — fresh-spawn paths (Shape A, and the first spawn for a new
// convID) supply an empty ResumeConvID and get a fresh subprocess.
//
// Per-conversation worktree prep / cleanup, OAuth token pick, and
// live-registry enrolment all happen here (per-conversation
// lifecycle, NOT per-turn — SOR-331). The conversation has no
// user message yet; the first turn is sent via SendTurn whose
// payload should carry the rendered issue_agent prompt
// template + system-context envelope.
StartConversation(ctx context.Context, opts IssueAgentShellStartOptions) (IssueAgentShellConversation, error)
// BuildFirstTurnPayload returns the text the first turn's
// stream-json user message should carry: the rendered
// issue_agent prompt template + system-context envelope with
// the operator's first message substituted into MESSAGE. Called
// exactly once per subprocess — every subsequent turn writes
// the raw operator message text without any envelope, because
// claude retains conversation state internally. SOR-333: under
// Shape B the handler caches the envelope-rendered state on the
// registry entry so a reconnect that reuses the existing
// subprocess skips the envelope (claude already has it in
// context); only a fresh spawn (including the --resume reconnect
// path, which starts a brand-new subprocess whose context comes
// from claude's on-disk store) renders the envelope.
BuildFirstTurnPayload(message string) (string, error)
}
IssueAgentShellBackend spawns the long-lived issue_agent subprocess that backs one /v1/issue-agent-shell WebSocket connection. SOR-331 replaced the pre-existing one-shot-per-turn shape with a single subprocess per WS connection that survives across many operator turns; SOR-333 extended the lifecycle further so a stable conversation ID supplied by the SPA keys a daemon-side subprocess registry whose entries survive WS drops within an idle TTL.
It is declared here (not in internal/agentcli) so the httpapi package stays off the clauderunner import; the production daemon wires a thin cmdadapter that holds the resolved issue_agent `--effort` plus the static daemon-side envelope fields (DAEMON / PROJECT_DIR / TEAM_KEY / REPOS / EXPLORABLE_REPOS) and delegates to agentcli.Runner.StartIssueAgentShell.
type IssueAgentShellConversation ¶
type IssueAgentShellConversation interface {
// SendTurn writes one operator-side user message (text) to the
// subprocess's stdin as a stream-json frame and returns a
// IssueAgentShellTurn whose Stdout yields the assistant's text content
// line-by-line until the per-turn `result` terminator (io.EOF).
SendTurn(messageText string) (IssueAgentShellTurn, error)
// Reap tears the subprocess tree down. Idempotent — the eviction,
// idle-timeout, and connection-closed paths can all call it.
Reap(reason string)
// Wait blocks until the subprocess exits. Returns the classified
// exit error (e.g. *agentcli.NarrationError on a transient
// upstream API failure) for diagnostics; the operator-visible
// signal is per-turn stdout EOF on the in-flight IssueAgentShellTurn, not
// Wait's return value.
Wait() error
}
IssueAgentShellConversation is the long-lived issue_agent subprocess owning one WebSocket connection (SOR-331). SendTurn writes one operator-side message to the subprocess's stdin and returns a IssueAgentShellTurn for that turn's stdout. Reap tears down on WS close / idle / eviction; the conversation can be reaped from any of those paths concurrently and the implementation must be idempotent. Wait blocks until the subprocess exits.
type IssueAgentShellStartOptions ¶
type IssueAgentShellStartOptions struct {
// ResumeConvID, when non-empty, tells the backend to spawn the
// subprocess via `claude --resume <ResumeConvID>` so it picks up
// the named on-disk conversation. The Shape B registry sets this
// only on the between-connections-reap path; fresh spawns leave
// it empty.
ResumeConvID string
}
IssueAgentShellStartOptions carries the per-spawn inputs StartConversation needs to decide whether the new subprocess is a fresh start or a `--resume <id>` reconnect (SOR-333). The struct exists so the interface signature can be extended without churning every implementation each time a new spawn-time concern is added.
type IssueAgentShellTurn ¶
IssueAgentShellTurn is one in-flight turn's reader. Stdout yields the assistant's text output as line-delimited chunks; io.EOF marks the end of the turn. Close releases the reader-side pipe fd so the long- lived conversation doesn't leak file descriptors across many turns.
type IssueCreateInput ¶
type IssueCreateInput = apiclient.IssueCreateInput
IssueCreateInput is the POST /v1/issues payload. See apiclient.IssueCreateInput.
type IssueDetail ¶
type IssueDetail = apiclient.IssueDetail
IssueDetail is the operator-show view of an issue. See apiclient.IssueDetail for the canonical declaration.
type IssueDryRunResult ¶
type IssueDryRunResult = apiclient.IssueDryRunResult
IssueDryRunResult is the POST /v1/issues/dry-run report. See apiclient.IssueDryRunResult.
type IssueListFilter ¶
type IssueListFilter = apiclient.IssueListFilter
IssueListFilter is the wire-shape filter for GET /v1/issues. See apiclient.IssueListFilter for the canonical declaration.
type IssueMutator ¶
type IssueMutator interface {
// ApplyTransition advances the issue at key to toState, recording
// reason in the audit trail. The actor is "operator" by
// convention. Returns ErrNotFound, ErrInvalidTransition, or
// nil.
ApplyTransition(ctx context.Context, key, toState, reason string) error
// AddBlocker declares "blockerKey blocks blockedKey" — i.e.
// appends blockerKey to blockedKey's DependsOn slice and pushes
// the matching Linear `blocks` relation. Idempotent (a duplicate
// add is a silent success). Returns ErrNotFound (either side
// missing), ErrCycle (would close a cycle), or nil.
AddBlocker(ctx context.Context, blockerKey, blockedKey string) error
// RemoveBlocker is the inverse of AddBlocker: drops blockerKey
// from blockedKey's DependsOn and removes the Linear relation.
// Idempotent (a remove against a missing edge is a silent
// success). Returns ErrNotFound (either side missing) or nil.
RemoveBlocker(ctx context.Context, blockerKey, blockedKey string) error
// AppendComment inserts one operator-authored comment on the
// issue at key with the given body. The body is taken verbatim;
// callers MUST trim and reject empties at the wire layer (the
// daemon never inserts an empty-body row). Returns the assigned
// comment id and unix-seconds timestamp on success, or
// ErrNotFound when the key is absent. Other errors surface to
// the handler as 500.
AppendComment(ctx context.Context, key, body string) (commentID int64, ts int64, err error)
// CancelIssue transitions the issue at key to its terminal-cancel
// state (abandoned for implementation, plan_abandoned for planning),
// runs the cleanup path the supervisor uses today on involuntary
// abandonment (worktree teardown, optional WIP preservation, Linear
// push to canceled), and inserts an audit history row plus an
// optional derived comment row carrying the cancel reason.
//
// When the issue 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 daemon refuses the cancel with CascadeImpactError
// in that situation unless cascadePlanAbandon is true; the operator
// passes true via the `--cascade-plan-abandon` CLI flag (wire field
// `cascade_plan_abandon`) once they have inspected the impact.
//
// Returns ErrNotFound, ErrInvalidTransition, *CascadeImpactError, or
// nil.
CancelIssue(ctx context.Context, key, reason string, cascadePlanAbandon bool) error
// CreateIssue files one fresh issue. Title / BodyMarkdown / Kind
// are required; Repos / DependsOn are optional. The daemon
// validates Repos against the project's allowlist and DependsOn
// against state.Issues; rejections wrap ErrBadRequest with the
// offending input in the message. The Linear-side create runs
// inside the daemon's main-loop handler; a Linear-side failure
// wraps ErrLinearAPI so the handler can map it to 502 (distinct
// from a 500 local failure). Returns the assigned Linear key
// (e.g. "SOR-N") plus an optional operator-facing warning string
// on success. The warning is non-empty only when the SOR-1218
// body-vs-repos detector overrode the prior repos-field routing
// decision so the create proceeded locally despite the operator's
// declared `--repo` flag pointing elsewhere; the handler folds
// it into the 201 reply as the optional `warning` field.
//
// The third return value, specDriven, reports whether the create was
// routed to the spec-driven lifecycle (minted in spec_drafting) — true
// exactly when createroute.Decide returns RouteSpecDrafting for this
// request (SPEC-SOR-2828-v1 R7), false on every error path and on the
// cross-daemon forward path. The handler folds it into the 201 reply as
// the optional `spec_driven` field (omitted when false).
CreateIssue(ctx context.Context, in IssueCreateInput) (key string, warning string, specDriven bool, err 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 — WITHOUT minting a key,
// persisting a row, or invoking the dedupe LLM. It is the cheap,
// deterministic pre-commit check a drafting tool iterates against
// before the mutating real create. The returned error is non-nil only
// on a genuine daemon-side failure (e.g. a state-snapshot error); a
// draft that would be rejected by a real create is reported via
// IssueDryRunResult.Valid=false + ValidationErrors, NOT via this error.
DryRunCreateIssue(ctx context.Context, in IssueCreateInput) (*IssueDryRunResult, error)
// AmendIssue updates the issue at key's description in place and
// writes an audited, recoverable amendment row capturing actor,
// reason, timestamp, and the pre-amend body. The amend touches
// description only — issue state, state_kind, and dependencies
// rows are NEVER modified by this method. The caller is
// responsible for trimming and rejecting empties at the wire
// layer; the daemon also rejects empties as defense in depth.
// Returns ErrNotFound when key is absent, ErrInvalidTransition
// when the issue is in a terminal state, or nil on success. On
// success it also returns the operator-facing body-sizing warning
// for the NEW body (empty when the post-amend body is in the "safe"
// risk bucket or forceLargeScope is true); the handler folds it into
// the 200 reply as the optional `warning` field.
AmendIssue(ctx context.Context, key, newDescription, reason string, forceLargeScope bool) (warning string, err error)
// AdoptPR creates a fresh implementation Issue around an existing
// out-of-band PR (SOR-220 Layer 4 `sorcerer issue adopt-pr`).
// The daemon resolves the PR via GitHub, validates that the PR's
// repo is in the project allowlist and that the PR is open, then
// mints a fresh SOR-N key and inserts the issue directly at
// IssueStateInReview with PRURLs+Branch pre-populated. Returns
// (key, nil) on success. The 400-mapped failures wrap
// ErrBadRequest with the offending input in the message
// ("repo not in allowlist", "PR is closed, not open"). PR
// resolution failures surface as ErrLinearAPI (502 by analogy
// with the existing create path's Linear-side failures).
AdoptPR(ctx context.Context, in IssueAdoptPRInput) (key string, err error)
// SetPriority updates the issue at key's Linear priority in place
// and writes an audited history row capturing actor, reason,
// timestamp, and the prior + new value. Like AmendIssue it touches
// one field only — issue state, state_kind, and dependencies rows
// are NEVER modified by this method. newPriority MUST be in
// [0, 4]; the caller is responsible for rejecting out-of-range
// values and empty reasons at the wire layer (the daemon also
// rejects them as defense in depth). Returns ErrNotFound when key
// is absent, ErrInvalidTransition when the issue is in a terminal
// state (merged / abandoned / rejected / plan_completed /
// plan_abandoned), or nil on success.
SetPriority(ctx context.Context, key string, newPriority int, reason string) error
// Retitle corrects the issue at key's title in place and writes an
// audited history row capturing actor, reason, timestamp, and the
// prior + new title (SPEC-SOR-2786-v1 R7/R8). Like SetPriority it
// touches one field only — issue state, state_kind, session_id, and
// dependencies rows are NEVER modified — so the issue's key, in-flight
// session, and dependency edges are preserved. newTitle MUST be
// non-empty; the caller rejects blank titles and empty reasons at the
// wire layer (the daemon also rejects them as defense in depth).
// Returns ErrNotFound when key is absent, ErrInvalidTransition when
// the issue is in a terminal state (merged / abandoned / rejected /
// plan_completed / plan_abandoned), or nil on success.
Retitle(ctx context.Context, key, newTitle, reason string) error
// RekeyIssue renames the issue at oldKey to a fresh canonical
// `<teamPrefix>-<N>` minted from project.last_issue_seq. SOR-222
// surgical fix for orphan-PR adoptions that synthesized non-
// canonical keys (e.g. `TREAT-500`) which the operator UI's
// issue list can't render. The rewrite touches every referencing
// table (history, comments, dependencies, etc.) atomically and
// inserts a comment on the renamed row recording the rename.
// Returns the assigned new key on success, ErrNotFound when
// oldKey is absent, ErrBadRequest when oldKey is malformed.
RekeyIssue(ctx context.Context, oldKey string) (newKey string, err error)
// RouteMove executes the SOR-1278 `sorcerer issue route --to` cross-
// daemon move: the source daemon emits route_operator_move_initiated,
// forwards a fresh create to targetDaemon with the source key on the
// X-Sorcerer-Source-Key header, cancels the source issue as
// abandoned with a reason naming the destination, emits
// route_operator_move_completed, and rewrites any sibling deps that
// pointed at sourceKey (removing the dep + emitting one
// route_operator_move_dep_rewritten per affected sibling). Returns
// (destKey, destDaemon, nil) on success.
//
// Failure modes:
// - ErrNotFound when sourceKey is absent from state.Issues.
// - *RouteMoveRefusedError when the source has a live session
// (cancel the session first) or the SM does not admit
// abandonment (already merged / abandoned / rejected). Handler
// maps to 409 with {error,hint}.
// - *RouteMoveForwardError when the destination's POST /v1/issues
// returned non-201 or the request never reached it. Handler
// maps to 502 with {error,status,body}.
// - err wrapping ErrBadRequest for operator-side validation
// (unknown / stale target daemon, moving to self, empty
// targetDaemon / reason). Handler maps to 400.
RouteMove(ctx context.Context, sourceKey, targetDaemon, reason string) (destKey, destDaemon string, err error)
// AddPlanChildren attaches N new implementation children to the
// named planning issue's MaterializedChildren and transitions the
// plan to plan_auto_fixing (SOR-1448 / SOR-1443 Fix 4). Additive —
// existing parked children stay parked; does NOT trigger
// cancel-cascade. Each minted child carries
// BranchModel=plan_branch so the dispatcher carves a worktree
// from the cumulative plan-state for its implementer session.
// Returns the assigned keys (parallel to in.Children) + any
// operator-facing warnings on success.
//
// Failure modes:
// - ErrNotFound when in.PlanKey is absent from state.Issues.
// - err wrapping ErrBadRequest for validation rejections (plan
// state outside the accepted set, kind not planning, repo not
// on the project allowlist, missing required field). The
// handler maps to 400. State-outside-set rejections fold onto
// the same wrapper but the prose names the accepted set.
AddPlanChildren(ctx context.Context, in PlanAddChildrenInput) (keys []string, warnings []string, err error)
// RefactorPlan is the operator-driven replan escape hatch for POST
// /v1/plan/{key}/refactor. On the daemon's main goroutine it
// atomically (single-writer-serialized, not single-transaction)
// snapshots the planning issue's children + emits a
// plan_refactor_dispatched audit event, cascade-cancels every
// non-merged child through the existing per-issue cancel core,
// amends the planning body, and force-transitions the plan to
// plan_revising. Idempotent: re-running converges to
// (plan_revising, new body, zero in-flight children).
//
// Failure modes:
// - ErrNotFound when in.PlanKey is absent from state.Issues.
// - err wrapping ErrBadRequest for validation rejections (empty
// key / body / reason, target not a planning issue). The
// handler maps to 400.
// - any other error (a child that could not be canceled, the
// amend, or the transition) maps to 500; the operator re-runs.
RefactorPlan(ctx context.Context, in PlanRefactorInput) (PlanRefactorResult, error)
// ApplyReferBack 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 slice (overwriting any prior buffer), and routes
// through applyIssueTransition with EventKind=operator_refer_back
// so the audit trail and downstream side-effects match a
// reviewer-driven refer-back.
//
// Returns ErrNotFound when key is absent, an error wrapping
// ErrInvalidTransition when the issue is not in in_review or
// merging, or nil on success.
ApplyReferBack(ctx context.Context, key, reason string, concerns []ReviewConcern) error
// ReplaceIssue atomically replaces the issue at oldKey with a freshly-
// minted live replacement (SPEC-SOR-2826): it mints the replacement from
// in, copies the old issue's retained declared forward edges onto it,
// repoints every declared reverse dependent off oldKey onto the
// replacement, and terminally cancels the old issue — all in one
// issuestore transaction, so no scheduler pass observes a dependent gated
// only on a terminal issue (the R10 no-release property). Returns the
// minted replacement key on success.
//
// Returns ErrNotFound when oldKey is absent, an error wrapping
// ErrBadRequest when the old issue has materialized children (R9), is
// already terminal, or a depends_on entry names the old key, an error
// wrapping ErrCycle when a depends_on entry would close a cycle with the
// replacement, or any other error for a local failure.
ReplaceIssue(ctx context.Context, in IssueCreateInput, oldKey string) (newKey string, err error)
}
IssueMutator is the wire-facing write surface backing the /v1/issues/{key}/* mutation endpoints. The daemon implements it by pushing typed mutation events onto its main loop; callers see the same ack-or-error contract as the existing operator commands but with structured payloads instead of stringified prefix forms.
Methods return ErrNotFound when the issue key is absent and ErrInvalidTransition when an SM move is illegal. Other errors surface to the handler as 500.
SOR-952 shipped ApplyTransition; subsequent issues in the operator-mutation PLAN extend this interface (AppendComment, AddBlocker, RemoveBlocker, CancelIssue, CreateIssue) without changing its existing methods.
type IssueReader ¶
type IssueReader interface {
// ListIssues returns IssueSummary rows matching f. Zero-valued
// filter fields impose no constraint; an empty result is a
// non-nil empty slice (the JSON envelope is `{"issues":[]}`).
ListIssues(ctx context.Context, f IssueListFilter) ([]IssueSummary, error)
// GetIssue returns the full IssueDetail for key. Returns
// ErrNotFound when the key does not exist.
GetIssue(ctx context.Context, key string) (*IssueDetail, error)
// ListComments returns every comment on the issue at key,
// oldest first. An unknown key surfaces as ErrNotFound (the
// daemon-side adapter checks the issue exists before
// querying); a known key with no comments is a non-nil empty
// slice.
ListComments(ctx context.Context, key string) ([]CommentView, error)
// ListHistory returns every history row on the issue at key,
// oldest first. Same not-found / empty-slice contract as
// ListComments.
ListHistory(ctx context.Context, key string) ([]HistoryView, error)
// SearchIssues runs an FTS5 MATCH query against the issuestore and
// returns ranked title + snippet hits, plus optional comment hits
// when SearchOptions.IncludeComments is set. Empty queries are the
// caller's responsibility — the HTTP handler rejects them at 400.
// FTS5 syntax errors come back wrapped in issuestore.ErrInvalidQuery
// so the handler can surface 400 instead of 500.
SearchIssues(ctx context.Context, query string, opts SearchOptions) (*SearchResults, error)
// CommentsAfter returns every comment on the issue at key whose
// id > sinceID, oldest first. Used by the /v1/issues/{key}/stream
// SSE handler to deliver backlog and poll for new rows. sinceID==0
// is "every comment". Same not-found / empty-slice contract as
// ListComments.
CommentsAfter(ctx context.Context, key string, sinceID int64) ([]CommentView, error)
// HistoryAfter returns every history row on the issue at key whose
// id > sinceID, oldest first. Used by the /v1/issues/{key}/stream
// SSE handler to deliver backlog and poll for new rows. sinceID==0
// is "every history row". Same not-found / empty-slice contract as
// ListHistory.
HistoryAfter(ctx context.Context, key string, sinceID int64) ([]HistoryView, error)
// EventsAfter returns rows from the daemon's events table whose
// id > sinceID, oldest first. sinceID <= 0 means "every event".
// limit > 0 bounds the result; limit <= 0 is no limit. Used by the
// /v1/events JSON-list and SSE handlers to serve the audit-trail
// stream that previously read from events.log.
EventsAfter(ctx context.Context, sinceID int64, limit int) ([]EventView, error)
// RouteHistoryEvents returns the chronological routing-history audit
// rows for the issue at key — the post-mint `route_resolved_for_key`
// row (SOR-1277) plus the four `route_operator_move_*` rows
// (SOR-1278). Filter shape: `subject = key AND kind IN
// (route_resolved_for_key, route_operator_move_initiated,
// route_operator_move_completed, route_operator_move_received,
// route_operator_move_dep_rewritten)`, sorted ts ASC. Empty result is
// a non-nil empty slice (the caller renders {events:[]}). A missing
// issue key is NOT a not-found — an issue with no routing history is
// a normal case (any issue minted before the topology cache was wired
// has zero rows). Used by the SOR-1279 GET
// /v1/issues/{key}/route-show inspection endpoint.
RouteHistoryEvents(ctx context.Context, key string) ([]EventView, error)
// ListDashboardRows returns one aggregated row per issue for the
// `sorcererd dashboard` view: key + kind + state + state_kind +
// last_change (unix seconds of the most-recent history row, or the
// issue's updated_at when no history row exists). When
// includeTerminal is false (the default), terminal issue / plan
// states are filtered out so the dashboard's default "what's
// in-flight?" view stays focused.
ListDashboardRows(ctx context.Context, includeTerminal bool) ([]DashboardRow, error)
}
IssueReader is the wire-facing read surface backing GET /v1/issues (and, in subsequent issues, /v1/issues/{key}, /v1/search, /v1/issues/{key}/events). Each method maps to one read endpoint; the daemon implements it by delegating to its *issuestore.Store.
Defined here — alongside Backend — so handlers consume a narrow HTTP-shaped contract rather than reaching into the storage layer.
Methods that look up by key return ErrNotFound (this package's sentinel) when the row is absent; callers map that to 404. Any other error surfaces as 500.
type IssueSummary ¶
type IssueSummary = apiclient.IssueSummary
IssueSummary is the operator-list view of an issue. See apiclient.IssueSummary for the canonical declaration.
type Logger ¶
Logger is the minimal logging surface the server uses for startup/shutdown messages and accept errors. Optional; nil silences.
type PlanAddChildInput ¶
type PlanAddChildInput = apiclient.PlanAddChildInput
PlanAddChildInput is one entry in PlanAddChildrenInput.Children. See apiclient.PlanAddChildInput.
type PlanAddChildrenInput ¶
type PlanAddChildrenInput = apiclient.PlanAddChildrenInput
PlanAddChildrenInput is the POST /v1/plan/{key}/add-children payload. See apiclient.PlanAddChildrenInput. SOR-1448 / SOR-1443 Fix 4.
type PlanAddChildrenResult ¶
type PlanAddChildrenResult = apiclient.PlanAddChildrenResult
PlanAddChildrenResult is the wire-shape reply from POST /v1/plan/{key}/add-children. See apiclient.PlanAddChildrenResult.
type PlanApproveProjector ¶
type PlanApproveProjector interface {
ProjectApprovedChildren(ctx context.Context, proposalID int64) error
}
PlanApproveProjector drives the eager in-memory projection of an approved proposal's implementation children into the daemon's d.state.Issues, immediately after handlePlanReview's off-main Store.ReviewProposal approve-commit succeeds. Closing this window — between the issuestore approve-commit and the in-memory projection — demotes the recovery sweep from the steady-state projection trigger to a pure crash-recovery backstop (SOR-2323).
Implemented by the cmdadapter wrapping *daemon.Daemon, which routes the projection through the single-writer main loop (the same coordination ApplyOperatorCreateIssue uses for an off-main operator create). Optional: nil in test servers that don't need the projection — handlePlanReview's approve branch then leaves the recovery sweep as the sole projection path (the pre-SOR-2323 behavior).
type PlanBranchCommit ¶
type PlanBranchCommit = apiclient.PlanBranchCommit
PlanBranchCommit is one materialized child's prospective squash commit on the plan branch. See apiclient.PlanBranchCommit.
type PlanBranchRepoStatus ¶
type PlanBranchRepoStatus = apiclient.PlanBranchRepoStatus
PlanBranchRepoStatus is one repo's slice of the plan-branch projection. See apiclient.PlanBranchRepoStatus.
type PlanCitations ¶
type PlanCitations = plan.CitationChecker
PlanCitations is the citation-existence surface the submit handler passes through to the plan validator (rule 1). Mirrors plan.CitationChecker so the daemon's existing checker satisfies it without an extra adapter.
type PlanRefactorInput ¶
type PlanRefactorInput = apiclient.PlanRefactorInput
PlanRefactorInput is the POST /v1/plan/{key}/refactor payload — the operator-driven replan escape hatch. See apiclient.PlanRefactorInput.
type PlanRefactorResult ¶
type PlanRefactorResult = apiclient.PlanRefactorResult
PlanRefactorResult is the wire-shape reply from POST /v1/plan/{key}/refactor. See apiclient.PlanRefactorResult.
type PlanStore ¶
type PlanStore interface {
// SubmitProposal persists one planner submission inside a single
// BEGIN IMMEDIATE transaction. The handler-built `validate` callback
// is invoked inside the transaction after all INSERTs but before
// COMMIT; a non-empty return slice rolls back every write and
// returns an error matching errors.Is(err, ErrValidationRejected)
// with the joined slice as the wrapped message.
//
// envelope is the raw HTTP request body — stored byte-for-byte so
// the dump endpoint can return it without re-marshaling.
//
// Returns (proposalID, mintedKeys, nil) on success.
SubmitProposal(
ctx context.Context,
envelope []byte,
plannerSession, originIssue string,
now int64,
issues []SubmittedIssue,
validate PlanSubmitValidator,
) (int64, []string, error)
// ReviewProposal flips a proposal's outcome and every
// proposal_id=<id> issue's state (approve → 'waiting' children;
// reject → 'rejected' children) inside one transaction. When
// planningKey is non-empty AND decision == "approve",
// issue_materialized_children is populated.
//
// Returns ErrNotFound (unknown id), ErrProposalConflict (already
// reviewed), or nil.
ReviewProposal(
ctx context.Context,
proposalID int64,
decision, summary, concerns, planningKey string,
decidedAt int64,
) error
// GetProposalEnvelope returns the bytes from proposals.envelope
// for the row at proposalID. Returns ErrNotFound when the id is
// unknown.
GetProposalEnvelope(ctx context.Context, proposalID int64) ([]byte, error)
// ExistingIssueRefs returns one IssueRef per issuestore row,
// keyed by SOR-N. Used by the submit handler to feed the
// plan validator's cross-batch dep / canceled-dep checks.
// StateType is the Linear-style classifier ("canceled" /
// "completed" / "started") derived from the issue's state column.
// Priority is the issue's stored Linear priority (0..4); rule 10
// (ApplyChildPriority + the downgrade hard reject in
// ValidatePlannerResult) reads it off the parent planning issue's
// entry.
ExistingIssueRefs(ctx context.Context) (map[string]plan.IssueRef, error)
// NonTerminalImplCohort returns a snapshot of the non-terminal
// implementation issues (state ∈ implementing | in_review |
// awaiting_integration) that carry a materialized footprint, sorted by
// key, for the proposal-time cross-impl footprint-overlap validator rule
// (CIPB Phase E). Empty-footprint impls are excluded (no overlap is
// computable). Returns nil without error when no in-flight impls exist.
// The submit handler treats an error as fail-open (no cohort, the
// cross-impl rule no-ops) so a flaky probe never blocks a proposal.
NonTerminalImplCohort(ctx context.Context) ([]planvalidate.InFlightImpl, error)
// PlanningKeyForPlannerSession resolves the planner session id to
// the Linear key of the planning issue it was opened against. Used
// by the submit handler to populate
// ValidationContext.ParentIssueKey so rule 10 can inherit the
// parent's priority onto omitted children and reject unjustified
// downgrades. Returns ("", nil) when the session is unknown (e.g.
// tests that don't seed sessions, or a session row that hasn't
// been mirrored yet) — rule 10 then silently skips, which is the
// right outcome for the no-parent case.
PlanningKeyForPlannerSession(ctx context.Context, plannerSession string) (string, error)
// SpecRequirementIDs returns the full R-ID set of the approved spec
// backing the named planning issue (resolved via SpecAwarePlanning →
// GetSpec → the spec DSL's Requirements[].ID), or (nil, nil) when the
// planning issue is spec-unaware. The submit handler threads the result
// into ValidationContext.RequirementIDs so the spec-driven verifies
// rules fire only on spec-driven dispatches. Fail-open: the handler
// treats any error as "no spec" (legacy path) so a flaky spec probe
// never blocks a proposal, matching the NonTerminalImplCohort /
// PlanOpenPRCounter convention.
SpecRequirementIDs(ctx context.Context, planningKey string) ([]string, error)
// SpecRequirements returns the full approved-spec requirement set backing
// the named planning issue (the same SpecAwarePlanning → GetSpec → spec
// DSL chain as SpecRequirementIDs, but carrying each Requirement's
// closer / terminal / after placement metadata), or (nil, nil) when the
// planning issue is spec-unaware. The submit handler threads the result
// into plan.CheckSpecPlacement so the deterministic R-ID coverage +
// placement checks fire only on spec-driven dispatches. Fail-open with
// the same convention as SpecRequirementIDs.
SpecRequirements(ctx context.Context, planningKey string) ([]dsl.Requirement, error)
// PlanningBody returns the stored body (Description) of the named planning
// issue — the authoritative acceptance-criteria text the AC-coverage gate
// checks the planner's children against. The submit handler threads the
// result into lintProposalFromResult → Proposal.ParentBody so
// DetectACCoverageGap reads the parent's ACs from the issuestore, never
// from the planner's (untrusted) submission. Fail-open with the same
// convention as SpecRequirements: returns ("", nil) on an empty key or an
// unknown issue, and the caller treats any error as "no parent body" (the
// gate short-circuits on an empty ParentBody) so a flaky probe never blocks
// a proposal.
PlanningBody(ctx context.Context, planningKey string) (string, error)
// FindActiveProposalForSession returns the oldest non-rejected
// proposal for plannerSession plus its envelope bytes verbatim.
// "Active" means outcome IN ('pending', 'approved'); rejected
// and abandoned rows are excluded. Returns (nil, nil, nil) when
// no active row exists. Backs the SOR-93 duplicate-proposal
// guard in handlePlanSubmit.
FindActiveProposalForSession(ctx context.Context, plannerSession string) (*ActiveProposal, []byte, error)
// AppendEvent appends one row to the issuestore's events table.
// Used by handlePlanSubmit to audit the duplicate-proposal guard
// (proposal_submit_idempotent on the 200 byte-equal retry path,
// proposal_submit_refused on the 409 conflict path). Errors are
// logged by the adapter and swallowed at the caller — a failed
// audit row must NOT take down the user-facing response.
AppendEvent(ctx context.Context, kind, subject, message string)
}
PlanStore is the narrow persistence surface backing /v1/plan/*. The daemon implements it by wrapping its *issuestore.Store handle; tests supply an in-memory adapter against a per-test sqlite file. Methods return ErrNotFound when the proposal id is unknown, ErrProposalConflict when the proposal's outcome is no longer 'pending', ErrValidationRejected when the in-transaction validator callback returned a non-empty slice, and any other error surfaces as 500 from the handler.
type PlanSubmitValidator ¶
type PlanSubmitValidator func(envelope []byte, children []SubmittedIssue) []string
PlanSubmitValidator is the synchronous callback shape the submit handler builds and the PlanStore implementation invokes inside its transaction. A non-empty return slice causes SubmitProposal to roll back every INSERT (proposal + children + repos + dependencies) and return an error matching errors.Is(err, ErrValidationRejected).
type RecoverBackend ¶
type RecoverBackend interface {
ApplyRecoverSweep(ctx context.Context, kinds []string) (RecoverResponse, error)
}
RecoverBackend is the narrow daemon-side hook the /v1/recover handler consumes. Implemented by *daemon.Daemon (via ApplyRecoverSweep). The daemon's implementation submits a typed event to the main goroutine and waits up to ~10s for an ack — the planning sweep can recurse through nested chains so the longer-than-5s budget vs operator mutations is intentional.
type RecoverRequest ¶
type RecoverRequest = apiclient.RecoverRequest
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type RecoverResponse ¶
type RecoverResponse = apiclient.RecoverResponse
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type RecoverTransition ¶
type RecoverTransition = apiclient.RecoverTransition
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type RequirementVerdictEntryView ¶
type RequirementVerdictEntryView = apiclient.RequirementVerdictEntryView
RequirementVerdictEntryView is one entry of a ReviewVerdictView's per-R-ID map. See apiclient.RequirementVerdictEntryView.
type ReviewConcern ¶
type ReviewConcern struct {
PR string `json:"pr,omitempty"`
File string `json:"file,omitempty"`
Line int `json:"line,omitempty"`
Severity string `json:"severity"`
Comment string `json:"comment"`
}
ReviewConcern is the wire shape of one operator-supplied refer-back concern. Field tags match the internal role.ReviewConcern shape (SOR-1149) so the JSON body the CLI POSTs is round-trippable through either type. The httpapi package defines its own copy so the wire surface stays off the role package's import.
type ReviewVerdictView ¶
type ReviewVerdictView = apiclient.ReviewVerdictView
ReviewVerdictView is the latest per-R-ID reviewer verdict for an issue. See apiclient.ReviewVerdictView.
type RoleHealthBackend ¶
type RoleHealthBackend interface {
// ListRolePauses returns every row of role_health_pauses, ordered
// by role for stable output. An empty result is a non-nil empty
// slice (the JSON envelope is `{"pauses":[]}`).
ListRolePauses(ctx context.Context) ([]RolePauseView, error)
// ClearRolePause removes one row. Returns ErrRolePauseNotFound
// when the role is not paused; the handler maps that to a
// `cleared:false` body (still 200 — the absence of a pause
// satisfies the request's intent). Any other error surfaces as
// 500.
ClearRolePause(ctx context.Context, role string) error
// SetRolePause UPSERTs the role_health_pauses row from the given
// operator-supplied input and emits the audit event. SOR-1364:
// after the row + audit-event commit succeeds, the implementation
// dispatches a daemon-side abort across every in-flight session of
// the paused role; the second return is the count of sessions
// walked to aborted (zero in the steady-state "no in-flight
// session" path), surfaced through the wire-shape
// RolePauseSetResponse.AbortedInFlightSessions field. Returns the
// resulting RolePauseView on success; the caller renders it back
// to the operator without a follow-up GET.
SetRolePause(ctx context.Context, in SetRolePauseInput) (RolePauseView, int, error)
// KnownRoles returns the set of role names the watchdog tracks
// (the `daemon.role_health.roles` config slice). The POST handler
// uses it to gate operator-set pauses to known roles only — a
// `--pause-role bogus` typo surfaces as 400 rather than a
// silently-orphaned row the watchdog ignores. Returns a fresh
// slice; callers may mutate without affecting backend state.
KnownRoles() []string
}
RoleHealthBackend is the narrow daemon-side hook the /v1/role-pauses* handlers consume. Implemented by *daemon.Daemon via a thin adapter over the issuestore. Listing is read-only; clearing both removes the row from role_health_pauses AND emits an audit event so operator dashboards see the pause-cleared moment in the same /v1/events stream as the originating role_failure_streak. Setting (operator-driven pause) UPSERTs a fresh row with consecutive_pauses + failures both zero (operator pauses are a clean slate; the watchdog's escalating-duration counter does not apply) and emits a distinct `role_health_pause_set_by_operator` audit event.
type RolePauseClearRequest ¶
type RolePauseClearRequest = apiclient.RolePauseClearRequest
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type RolePauseClearResponse ¶
type RolePauseClearResponse = apiclient.RolePauseClearResponse
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type RolePauseListResponse ¶
type RolePauseListResponse = apiclient.RolePauseListResponse
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type RolePauseSetRequest ¶
type RolePauseSetRequest = apiclient.RolePauseSetRequest
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type RolePauseSetResponse ¶
type RolePauseSetResponse = apiclient.RolePauseSetResponse
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type RolePauseView ¶
type RolePauseView = apiclient.RolePauseView
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type RouteClassification ¶
type RouteClassification struct {
Action string
TargetDaemon string
TargetURL string
Rationale string
OverrideEvidence []string
}
RouteClassification is the dry-run routing decision returned by RouteClassifier.ClassifyRoute. Mirrors the (Action, TargetDaemon, TargetURL, Rationale, OverrideEvidence) tuple the daemon's resolver produces, lifted up to the httpapi-package layer so the SOR-1279 POST /v1/issues/route-classify handler can call across the daemon→httpapi boundary without dragging an httpapi → daemon import cycle. The wire shape served by the handler folds OverrideEvidence into the boolean `conflict` (true ⇔ non-empty) + the `evidence_paths` slice — see handleIssuesRouteClassify in issues_route.go.
type RouteClassifier ¶
type RouteClassifier interface {
ClassifyRoute(ctx context.Context, body string, repos []string) (RouteClassification, error)
}
RouteClassifier is the narrow lookup surface the SOR-1279 dry-run classify endpoint (POST /v1/issues/route-classify) consumes. Implemented by *daemon.Daemon (through cmdadapter.RouteClassifier) so the httpapi package stays off the daemon-package import. ClassifyRoute calls daemon.ResolveDestination with the supplied body + repos against the live topology snapshot and returns the decision verbatim; the classify handler renders it as JSON without ever appending an audit row (the resolver's audit-event side-effects happen in the create flow, not here — the dry-run path is mutation-free by construction).
type RouteMoveForwardError ¶
RouteMoveForwardError is the typed error an IssueMutator.RouteMove implementation returns when the cross-daemon forward leg of a `sorcerer issue route --to` move failed: the destination daemon returned a non-201 status, or the request never reached it. The POST /v1/issues/{key}/route-move handler maps it to 502 Bad Gateway with the documented body shape:
{"error":"<one-line message>","status":<destStatusCode>,
"body":"<truncated peer body or transport error text>"}
errors.As unwraps to *RouteMoveForwardError so the handler can surface the destination-side rejection without losing the typed payload. Status is 0 for transport-level failures (connection refused, timeout, DNS).
func (*RouteMoveForwardError) Error ¶
func (e *RouteMoveForwardError) Error() string
Error renders a single-line summary; the structured fields are what the handler emits on the wire.
type RouteMoveRefusedError ¶
RouteMoveRefusedError is the typed error an IssueMutator.RouteMove implementation returns when the SOR-1278 cross-daemon operator-move path declines to proceed because the source issue is not in a state safe to move: it has a live session attached (the operator must cancel the session first), or its current SM state does not admit abandonment (e.g. already merged / abandoned / rejected). The POST /v1/issues/{key}/route-move handler maps it to 409 Conflict with the documented body shape:
{"error":"<one-line message>","hint":"<actionable next step>"}
errors.As unwraps to *RouteMoveRefusedError so the handler can render the structured fields without losing the typed payload.
func (*RouteMoveRefusedError) Error ¶
func (e *RouteMoveRefusedError) Error() string
Error renders the operator-facing message verbatim; the Hint is surfaced separately on the wire so the CLI can format it on its own line without parsing the error string.
type SearchOptions ¶
type SearchOptions = apiclient.SearchOptions
SearchOptions / SearchResults / SearchHit / CommentHit — see apiclient for canonical declarations.
type SearchResults ¶
type SearchResults = apiclient.SearchResults
SearchResults is the wire envelope returned from /v1/search.
type SelfReflectionListResponse ¶
type SelfReflectionListResponse = apiclient.SelfReflectionListResponse
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type SelfReflectionView ¶
type SelfReflectionView = apiclient.SelfReflectionView
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type SelfReflectionsBackend ¶
type SelfReflectionsBackend interface {
// ListSelfReflections returns SelfReflectionView rows matching the
// filter, newest-first. An empty result is a non-nil empty slice
// (the JSON envelope is `{"self_reflections":[]}`).
ListSelfReflections(ctx context.Context, filter SelfReflectionsFilter) ([]SelfReflectionView, error)
}
SelfReflectionsBackend is the narrow daemon-side hook the /v1/self-reflections handler consumes. Implemented by a thin adapter over the issuestore (cmdadapter.SelfReflectionsBackend). Read-only: rows are append-only audit, the operator's intervention surface is the daemon-auto-filed meta-issue from the blocker rollup, not the row itself.
type SelfReflectionsFilter ¶
type SelfReflectionsFilter struct {
SinceTS int64 // unix seconds; rows with ts >= SinceTS only when > 0
Limit int // 0 → unlimited
Blocker string // exact match on blocker
}
SelfReflectionsFilter narrows a ListSelfReflections query. Built from the optional query params on GET /v1/self-reflections. Zero-valued fields impose no constraint.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server owns the HTTP listener and serving loop. One per daemon.
func New ¶
New constructs a Server. Returns an error on invalid config; does NOT bind or start serving — call Start for that.
func (*Server) Addr ¶
Addr returns the bound listen address (host:port). After Start, even when Bind=":0" was used, this returns the concrete port.
type SetRolePauseInput ¶
SetRolePauseInput is the daemon-side input to the operator-set pause path. PausedUntil is the absolute unix-second horizon the caller has already resolved (either now+duration or RolePauseIndefiniteSentinel); Reason is the operator's rationale (already trimmed, pre-prefix). The backend prepends `operator:` to Reason before persistence so the audit trail distinguishes operator pauses from watchdog-emitted ones.
type SpecAmendResponse ¶
type SpecAmendResponse = apiclient.SpecAmendResponse
Spec wire shapes are defined once in internal/apiclient (the storage structs carry no json tags). These aliases keep the httpapi handlers' signatures readable while preserving the single definition; the SpecBackend implementation (cmdadapter.SpecBackend) and the operator CLI verbs read the same apiclient types.
type SpecApproveResponse ¶
type SpecApproveResponse = apiclient.SpecApproveResponse
Spec wire shapes are defined once in internal/apiclient (the storage structs carry no json tags). These aliases keep the httpapi handlers' signatures readable while preserving the single definition; the SpecBackend implementation (cmdadapter.SpecBackend) and the operator CLI verbs read the same apiclient types.
type SpecBackend ¶
type SpecBackend interface {
// GetCurrentSpec returns the current (highest-version) spec for a
// planning issue plus its findings and the full version history.
// Returns ErrSpecNotFound when no spec has been drafted.
GetCurrentSpec(ctx context.Context, planningKey string) (SpecView, error)
// GetSpecVersion returns an explicit historical spec version (plus that
// version's findings and the full history). ErrSpecNotFound on a miss.
GetSpecVersion(ctx context.Context, planningKey string, version int64) (SpecView, error)
// VerifySpec re-runs the verifier pipeline against the current version,
// replaces its persisted findings, emits spec_verified, and returns the
// fresh finding set. ErrSpecNotFound / ErrSpecStateConflict on failure.
VerifySpec(ctx context.Context, planningKey string) (SpecVerifyResponse, error)
// ApplyPatch applies an RFC-6902 JSON-Patch to the current version
// atomically (optionally resolving findingID; 0 = free-form edit),
// re-enters verify, and emits spec_patch_applied. ErrSpecPatchFailed
// when the atomic apply rolled back.
ApplyPatch(ctx context.Context, planningKey string, findingID int64, patches json.RawMessage) (SpecPatchResponse, error)
// AmendSpec dispatches a TASK: amend on the spec_drafter with the
// operator's narrow change and emits spec_amend_dispatched. scope is the
// local|structural discriminator ("" defaults to the full re-author
// amend); the handler validates it before this call.
AmendSpec(ctx context.Context, planningKey, narrowChange, reason, scope string) (SpecAmendResponse, error)
// ApproveSpec transitions the planning issue spec_review → spec_approved
// (precondition: no open error findings) and emits spec_approved.
// ErrSpecApprovalBlocked / ErrSpecStateConflict on refusal.
ApproveSpec(ctx context.Context, planningKey, reason string) (SpecApproveResponse, error)
// SuppressFinding flips one finding open → suppressed with the
// operator's reason and emits spec_finding_suppressed.
// ErrSpecFindingNotFound on a miss.
SuppressFinding(ctx context.Context, planningKey string, findingID int64, reason string) (SpecSuppressFindingResponse, error)
}
SpecBackend is the narrow daemon-side hook the /v1/specs* handlers consume (implemented by cmdadapter.SpecBackend). Reads project the issuestore spec rows onto the wire shape; mutations route through the daemon's single-writer main loop, which performs the plan-SM transition, the spec / spec_findings writes, the drafter / verifier dispatch, and the typed audit-event emission. All methods take the planning issue key.
type SpecFindingView ¶
type SpecFindingView = apiclient.SpecFindingView
Spec wire shapes are defined once in internal/apiclient (the storage structs carry no json tags). These aliases keep the httpapi handlers' signatures readable while preserving the single definition; the SpecBackend implementation (cmdadapter.SpecBackend) and the operator CLI verbs read the same apiclient types.
type SpecFindingsSummary ¶
type SpecFindingsSummary = apiclient.SpecFindingsSummary
SpecFindingsSummary is the open-finding rollup on a spec-driven issue detail (SOR-2127). See apiclient.SpecFindingsSummary.
type SpecPatchResponse ¶
type SpecPatchResponse = apiclient.SpecPatchResponse
Spec wire shapes are defined once in internal/apiclient (the storage structs carry no json tags). These aliases keep the httpapi handlers' signatures readable while preserving the single definition; the SpecBackend implementation (cmdadapter.SpecBackend) and the operator CLI verbs read the same apiclient types.
type SpecSuppressFindingResponse ¶
type SpecSuppressFindingResponse = apiclient.SpecSuppressFindingResponse
Spec wire shapes are defined once in internal/apiclient (the storage structs carry no json tags). These aliases keep the httpapi handlers' signatures readable while preserving the single definition; the SpecBackend implementation (cmdadapter.SpecBackend) and the operator CLI verbs read the same apiclient types.
type SpecVerifyResponse ¶
type SpecVerifyResponse = apiclient.SpecVerifyResponse
Spec wire shapes are defined once in internal/apiclient (the storage structs carry no json tags). These aliases keep the httpapi handlers' signatures readable while preserving the single definition; the SpecBackend implementation (cmdadapter.SpecBackend) and the operator CLI verbs read the same apiclient types.
type SpecVersionMeta ¶
type SpecVersionMeta = apiclient.SpecVersionMeta
Spec wire shapes are defined once in internal/apiclient (the storage structs carry no json tags). These aliases keep the httpapi handlers' signatures readable while preserving the single definition; the SpecBackend implementation (cmdadapter.SpecBackend) and the operator CLI verbs read the same apiclient types.
type SpecVersionView ¶
type SpecVersionView = apiclient.SpecVersionView
Spec wire shapes are defined once in internal/apiclient (the storage structs carry no json tags). These aliases keep the httpapi handlers' signatures readable while preserving the single definition; the SpecBackend implementation (cmdadapter.SpecBackend) and the operator CLI verbs read the same apiclient types.
type SpecView ¶
Spec wire shapes are defined once in internal/apiclient (the storage structs carry no json tags). These aliases keep the httpapi handlers' signatures readable while preserving the single definition; the SpecBackend implementation (cmdadapter.SpecBackend) and the operator CLI verbs read the same apiclient types.
type StateDescriptor ¶
type StateDescriptor = apiclient.StateDescriptor
StateDescriptor and StatesResponse mirror the apiclient declarations so existing httpapi-package call sites keep using the local name.
type StatesResponse ¶
type StatesResponse = apiclient.StatesResponse
type StewardLedgerBackend ¶
type StewardLedgerBackend interface {
// CreateLedgerRow persists one steward_ledger row and returns it with the
// AUTOINCREMENT id + stamped timestamps populated. ErrBadRequest on an
// invalid row (empty row_type).
CreateLedgerRow(ctx context.Context, req *CreateStewardLedgerRowRequest) (StewardLedgerCreateResponse, error)
// ListLedgerRows returns every row (id ASC); a non-empty rowType filters
// to that kind.
ListLedgerRows(ctx context.Context, rowType string) (StewardLedgerListResponse, error)
// UpdateLedgerRow overwrites every mutable column of the row identified by
// req.Row.ID. ErrNotFound on a miss, ErrBadRequest on an invalid row.
UpdateLedgerRow(ctx context.Context, req *UpdateStewardLedgerRowRequest) error
// ExpireLedgerRow hard-deletes the row with the given id. ErrNotFound on a
// miss.
ExpireLedgerRow(ctx context.Context, id int64) error
// AnswerQuestion resolves the operator-question row id by applying the
// option labeled chosenLabel through the daemon's normal mutation surface
// and then expiring the row (SOR-2663, R17). ErrNotFound when the row is
// absent; ErrBadRequest when it is not an operator-question, the options are
// unparseable, no option matches chosenLabel, or the action kind is unknown.
AnswerQuestion(ctx context.Context, id int64, chosenLabel string) error
}
StewardLedgerBackend is the narrow daemon-side hook the /v1/steward/ledger handlers consume (implemented by cmdadapter.StewardLedgerBackend). Reads (ListLedgerRows) go direct to the issuestore (WAL-mode multi-reader, no main-loop hop); writes (Create / Update / Expire) route through the daemon's single-writer main loop, which emits the audited steward_ledger_write event. The backend returns errors wrapping the httpapi.Err* sentinels so the handlers map them to the right status without a translation layer.
type StewardLedgerCreateResponse ¶
type StewardLedgerCreateResponse = apiclient.StewardLedgerCreateResponse
Steward ledger wire shapes are defined once in internal/apiclient. These aliases keep the handler signatures readable while preserving the single definition; the StewardLedgerBackend implementation and the operator CLI read the same apiclient types.
type StewardLedgerListResponse ¶
type StewardLedgerListResponse = apiclient.StewardLedgerListResponse
Steward ledger wire shapes are defined once in internal/apiclient. These aliases keep the handler signatures readable while preserving the single definition; the StewardLedgerBackend implementation and the operator CLI read the same apiclient types.
type StewardLedgerRowView ¶
type StewardLedgerRowView = apiclient.StewardLedgerRowView
Steward ledger wire shapes are defined once in internal/apiclient. These aliases keep the handler signatures readable while preserving the single definition; the StewardLedgerBackend implementation and the operator CLI read the same apiclient types.
type SubmittedIssue ¶
type SubmittedIssue = apiclient.SubmittedIssue
SubmittedIssue mirrors issuestore.SubmittedIssue at the wire layer so the handler can hand a typed batch to the PlanStore. See apiclient.SubmittedIssue for the canonical declaration.
type SubscriptionBackend ¶
type SubscriptionBackend interface {
// ListSubscriptions returns every OAuth-pool entry's wire view,
// ordered by the picker's internal token-slice order for stable
// output. An empty result is a non-nil empty slice (the JSON
// envelope is `{"subscriptions":[]}`).
ListSubscriptions(ctx context.Context) ([]SubscriptionView, error)
// ClearSubscription zeroes the named token's unavailableSince /
// unavailableReason fields on the picker and writes an audit
// event. Returns the prior reason (empty when the token was
// already healthy) and the post-clear wire view. Returns
// ErrSubscriptionNotFound when name is not a registered token
// (the handler maps that to 404).
ClearSubscription(ctx context.Context, name string) (priorReason string, post SubscriptionView, err error)
// ResetSubscriptions clears a subscription's blocked state in place
// (both the throttle cooldown AND the auth-dead flag) and writes a
// subscription_reset audit event. When all is true the whole pool is
// reset; otherwise the single token named by name. Returns the
// post-reset wire views of the affected token(s) and the number of
// tokens reset. Returns ErrSubscriptionNotFound when a single-name
// reset targets a name that is not a registered token (the handler
// maps that to 404). The handler validates the name/all one-of
// before calling, so exactly one is set here.
ResetSubscriptions(ctx context.Context, name string, all bool) (post []SubscriptionView, count int, err error)
}
SubscriptionBackend is the narrow daemon-side hook the /v1/subscriptions* handlers consume. Implemented by a cmdadapter over agentcli.Runner. Listing is read-only; clearing both resets the picker's per-token unavailable fields AND emits an audit event so operators reading the events table see the clear in the same /v1/events stream as the originating auth-flag.
type SubscriptionClearResponse ¶
type SubscriptionClearResponse = apiclient.SubscriptionClearResponse
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type SubscriptionListResponse ¶
type SubscriptionListResponse = apiclient.SubscriptionListResponse
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type SubscriptionResetRequest ¶
type SubscriptionResetRequest = apiclient.SubscriptionResetRequest
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type SubscriptionResetResponse ¶
type SubscriptionResetResponse = apiclient.SubscriptionResetResponse
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type SubscriptionView ¶
type SubscriptionView = apiclient.SubscriptionView
Wire-shape types live in internal/apiclient/. See its declarations for documentation.
type TopologyLookup ¶
TopologyLookup is the narrow lookup surface the cross-daemon auth branch consumes. Implemented by *daemon.Topology so the httpapi package stays off the daemon-package import. AuthTokenHashOf returns the hex-lowercase SHA-256 of the named peer daemon's bearer (or empty for unknown / stale-cache / older-peer cases).
type UpdateStewardLedgerRowRequest ¶
type UpdateStewardLedgerRowRequest = apiclient.UpdateStewardLedgerRowRequest
Steward ledger wire shapes are defined once in internal/apiclient. These aliases keep the handler signatures readable while preserving the single definition; the StewardLedgerBackend implementation and the operator CLI read the same apiclient types.