sessionsvc

package
v0.0.0-...-7911669 Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 36 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// PolicyFileLimit bounds a session policy file, so a command that validates one before
	// handing it over reads the same amount this package will.
	PolicyFileLimit = 1 << 20
	// DefaultStopTimeout is how long the service waits for in-flight work to wind down, and the
	// budget a host's own HTTP shutdown should match.
	DefaultStopTimeout = 5 * time.Second
)
View Source
const MaxReviewErrorBytes = session.MaxErrorDetailBytes

MaxReviewErrorBytes bounds a gate's startup-error prose, so a host implementing ReviewGate truncates the same way the service's own paths do.

Variables

This section is empty.

Functions

func BoundedDetail

func BoundedDetail(value string) string

func EnsureAncestors

func EnsureAncestors(target string) error

EnsureAncestors creates missing parents of the state root without weakening permissions on an existing home/config directory. The state root itself is hardened by session.Open.

func ListenSocket

func ListenSocket(stateRoot, socketPath string) (net.Listener, func(), error)

func LoadPolicies

func LoadPolicies(path string, cfg *config.Config) (map[string]Policy, error)

LoadPolicies parses the strict operator policy file. A config is required when the caller wants credential availability checked; passing nil performs syntax/target/repository validation only and is useful for isolated parser tests.

func NewHTTPHandler

func NewHTTPHandler(service *Service) http.Handler

func ReviewCandidateUnchanged

func ReviewCandidateUnchanged(dir, branch, head, tree string) bool

func ReviewGitIdentity

func ReviewGitIdentity(dir, revision string) (string, string, error)

func RunIDFromEnv

func RunIDFromEnv() string

func SanitizeReviewText

func SanitizeReviewText(value string, maxBytes int) string

Types

type CompanionPolicy

type CompanionPolicy struct {
	Name       string `json:"name"`
	Repository string `json:"repository"`
	Remote     string `json:"remote,omitempty"`
	Branch     string `json:"branch,omitempty"`
}

type Config

type Config struct {
	StateRoot           string
	PolicyPath          string
	Policies            map[string]Policy
	SourceConfig        *config.Config
	Config              *config.Config
	Runtime             runtime.Runtime
	Executable          string
	Host                Host
	Runner              Runner
	RunnerFactory       RunnerFactory
	ReviewGate          ReviewGate
	StopTimeout         time.Duration
	CleanupInterval     time.Duration
	OperationStaleAfter time.Duration
	Logger              *slog.Logger
}

type CreateRemoteSessionRequest

type CreateRemoteSessionRequest struct {
	Policy      string                    `json:"policy"`
	Task        string                    `json:"task"`
	PullRequest *RemotePullRequestBinding `json:"pull_request,omitempty"`
}

type DiscardPlan

type DiscardPlan struct {
	SessionID  string                        `json:"session_id"`
	Revision   int64                         `json:"revision"`
	Workspace  WorkspaceDiscardPlan          `json:"workspace"`
	Companions []sessionCompanionDiscardPlan `json:"companions,omitempty"`
}

type DiscardRequest

type DiscardRequest struct {
	PlanOperationID string `json:"plan_operation_id"`
}

type EventDTO

type EventDTO struct {
	ID         string            `json:"id"`
	SessionID  string            `json:"session_id"`
	Sequence   int64             `json:"sequence"`
	TurnID     string            `json:"turn_id,omitempty"`
	Type       session.EventType `json:"type"`
	Version    int               `json:"version"`
	OccurredAt time.Time         `json:"occurred_at"`
	// Payload is the event's own record of what happened. It was withheld for
	// a long time, which left a caller able to count that a turn failed but
	// not to say why, and able to see that a turn ran without seeing any of
	// the work inside it.
	Payload json.RawMessage `json:"payload,omitempty"`
}

type Host

type Host struct {
	// PolicyScan flags credential-looking or execution-risk changes in a review candidate,
	// with the SAME decider that shadows secrets from the box. Nil means no findings, which
	// makes a review MORE publishable — so a real serving host must always wire it.
	PolicyScan func(repo, ref string) []string

	// ReviewGateFactory builds the gate a review runs when the caller injected none. It is a
	// factory, not a gate, because the runtime is detected lazily: the service opens (and takes
	// the state-root lock) before any runtime-specific startup work happens.
	ReviewGateFactory func(*config.Config, runtime.Runtime) ReviewGate

	// Warnf reports a non-fatal condition to the operator — a failed janitorial cleanup that
	// something else will retry. Rendering belongs to the CLI, so this package never imports
	// internal/ui.
	Warnf func(format string, args ...any)
}

Host is what the sessions service needs from the CLI that owns the terminal and the merge policy, and cannot own itself: the fork/git contracts it shares live in leaf packages (internal/forkspace, internal/ladder) and are plain imports, so only these three remain.

Every field is optional and a zero Host is usable — a test that drives the service directly injects the gate it wants and never scans or warns. The seams are deliberately functions, not an interface: each is one call, and there is exactly one implementation (internal/cli).

type OperationDTO

type OperationDTO struct {
	ID           string                 `json:"id"`
	Method       string                 `json:"method"`
	State        session.OperationState `json:"state"`
	ResourceType string                 `json:"resource_type,omitempty"`
	ResourceID   string                 `json:"resource_id,omitempty"`
	ErrorCode    session.ErrorCode      `json:"error_code,omitempty"`
	ErrorDetail  string                 `json:"error_detail,omitempty"`
	CreatedAt    time.Time              `json:"created_at"`
	UpdatedAt    time.Time              `json:"updated_at"`
}

type PlanDiscardRequest

type PlanDiscardRequest struct {
	SessionID        string `json:"session_id"`
	ExpectedRevision int64  `json:"expected_revision"`
	AcceptDirty      bool   `json:"accept_dirty"`
	AcceptUnmerged   bool   `json:"accept_unmerged"`
}

type PlanDiscardResult

type PlanDiscardResult struct {
	OperationID string      `json:"operation_id"`
	Plan        DiscardPlan `json:"plan"`
}

type Policy

type Policy struct {
	Name               string
	Repository         string
	Remote             string
	Branch             string
	Companions         []CompanionPolicy
	Targets            []agents.Target
	OmitEnv            bool
	OmitMCP            bool
	RepositoryReadOnly bool
	MaxTurns           int
	MaxQueuedTurns     int
	MaxQueuedBytes     int
	TurnTimeout        time.Duration
	WarmIdleTimeout    time.Duration
	MaxPatchBytes      int
}

Policy is operator-owned authority for one remote session. It is intentionally small: repository, target, and resource bounds are not request fields.

func (*Policy) UnmarshalJSON

func (p *Policy) UnmarshalJSON(data []byte) error

UnmarshalJSON retains the write-ahead intent format written before target ladders. Policy files use YAML and new JSON intents marshal Targets, so compatibility stays read-only.

type RemotePullRequestBinding

type RemotePullRequestBinding struct {
	Number     int    `json:"number"`
	HeadCommit string `json:"head_commit"`
}

RemotePullRequestBinding selects one GitHub pull-request head through the operator-owned remote configured by the session policy. The caller cannot name a repository, remote, or arbitrary ref, and the expected head makes a PR update racing session creation fail closed instead of silently changing the task's approved source.

type ReviewDossier

type ReviewDossier struct {
	OperationID           string                      `json:"operation_id"`
	SessionID             string                      `json:"session_id"`
	SessionRevision       int64                       `json:"session_revision"`
	PolicyDigest          string                      `json:"policy_digest"`
	PullRequest           *session.PullRequestBinding `json:"pull_request,omitempty"`
	CreationBase          string                      `json:"creation_base"`
	SourceHead            string                      `json:"source_head"`
	SourceTree            string                      `json:"source_tree"`
	ParentHead            string                      `json:"parent_head"`
	ParentTree            string                      `json:"parent_tree"`
	CandidateHead         string                      `json:"candidate_head"`
	CandidateTree         string                      `json:"candidate_tree"`
	Rebase                ReviewRebaseStatus          `json:"rebase"`
	Gate                  ReviewGateStatus            `json:"gate"`
	GateError             string                      `json:"gate_error,omitempty"`
	PolicyFindings        []string                    `json:"policy_findings,omitempty"`
	Patch                 []byte                      `json:"patch,omitempty"`
	PatchTruncated        bool                        `json:"patch_truncated"`
	PatchArtifactID       string                      `json:"patch_artifact_id,omitempty"`
	PatchDigest           string                      `json:"patch_digest,omitempty"`
	PatchBytes            int64                       `json:"patch_bytes"`
	Publishable           bool                        `json:"publishable"`
	NotPublishableReasons []string                    `json:"not_publishable_reasons,omitempty"`
}

type ReviewGate

type ReviewGate interface {
	Run(context.Context, string, string) (ReviewGateResult, error)
}

ReviewGate is the narrow gate seam used by RunReview. Implementations must not mutate gateRepo. A gate may create ignored build output in the disposable candidate; RunReview rejects any change to its pinned commit, tree, branch, tracked files, or non-ignored untracked files.

type ReviewGateFunc

type ReviewGateFunc func(context.Context, string, string) (ReviewGateResult, error)

func (ReviewGateFunc) Run

func (f ReviewGateFunc) Run(ctx context.Context, gateRepo, treeDir string) (ReviewGateResult, error)

type ReviewGateResult

type ReviewGateResult struct {
	Configured   bool
	Passed       bool
	StartupError string
}

ReviewGateResult is the complete outcome of the trusted parent gate. StartupError is a successful review outcome, not a failed operation.

type ReviewGateStatus

type ReviewGateStatus string
const (
	ReviewGateNone         ReviewGateStatus = "none"
	ReviewGatePassed       ReviewGateStatus = "passed"
	ReviewGateFailed       ReviewGateStatus = "failed"
	ReviewGateStartupError ReviewGateStatus = "startup_error"
	ReviewGateNotRun       ReviewGateStatus = "not_run"
)

type ReviewRebaseStatus

type ReviewRebaseStatus string
const (
	ReviewRebaseClean    ReviewRebaseStatus = "clean"
	ReviewRebaseConflict ReviewRebaseStatus = "conflict"
)

type RunReviewRequest

type RunReviewRequest struct {
	SessionID        string `json:"session_id"`
	ExpectedRevision int64  `json:"expected_revision"`
}

type Runner

type Runner interface {
	Run(context.Context, session.Session, session.Turn) (session.Turn, error)
}

type RunnerFactory

type RunnerFactory func(*session.Store) Runner

type RunnerFunc

type RunnerFunc func(context.Context, session.Session, session.Turn) (session.Turn, error)

func (RunnerFunc) Run

func (f RunnerFunc) Run(ctx context.Context, sess session.Session, turn session.Turn) (session.Turn, error)

type Service

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

func NewService

func NewService(cfg Config) (*Service, error)

func (*Service) AcceptTurnCandidate

func (s *Service) AcceptTurnCandidate(ctx context.Context, key, sessionID, turnID, digest string) (session.Turn, error)

func (*Service) CancelTurn

func (s *Service) CancelTurn(ctx context.Context, key string, req session.CancelTurnRequest) (session.Turn, error)

func (*Service) Close

func (*Service) CloseSession

func (s *Service) CloseSession(ctx context.Context, key string, req session.CloseSessionRequest) (session.Session, error)

func (*Service) CreateRemoteSession

func (s *Service) CreateRemoteSession(ctx context.Context, key string, req CreateRemoteSessionRequest) (session.Session, error)

func (*Service) CreateRemoteSessionAsync

func (s *Service) CreateRemoteSessionAsync(
	ctx context.Context,
	key string,
	req CreateRemoteSessionRequest,
) (session.Operation, error)

CreateRemoteSessionAsync durably admits a create before returning. Slow Git resolution and workspace materialization run under the service lifetime, independent of the HTTP request that admitted them.

func (*Service) Discard

func (s *Service) Discard(ctx context.Context, key string, req DiscardRequest) (session.Session, error)

func (*Service) ExtendBudget

func (s *Service) ExtendBudget(ctx context.Context, key string, req session.ExtendBudgetRequest) (session.Session, error)

func (*Service) GetChanges

func (s *Service) GetChanges(ctx context.Context, sessionID string) (WorkspaceChanges, error)

func (*Service) GetChangesPage

func (s *Service) GetChangesPage(
	ctx context.Context,
	sessionID string,
	patchOffset int64,
	patchLimit int,
) (WorkspaceChanges, error)

func (*Service) GetOperation

func (s *Service) GetOperation(ctx context.Context, key string) (session.Operation, error)

func (*Service) GetOperationByID

func (s *Service) GetOperationByID(ctx context.Context, id string) (session.Operation, error)

func (*Service) GetOutputArtifact

func (s *Service) GetOutputArtifact(ctx context.Context, sessionID, turnID, artifactID string) (session.OutputArtifact, error)

func (*Service) GetSession

func (s *Service) GetSession(ctx context.Context, id string) (session.Session, error)

func (*Service) GetTurn

func (s *Service) GetTurn(ctx context.Context, sessionID, turnID string) (session.Turn, error)

func (*Service) ListEvents

func (s *Service) ListEvents(ctx context.Context, sessionID string, after int64, limit int) ([]session.Event, error)

func (*Service) ListSessions

func (s *Service) ListSessions(ctx context.Context, limit int) ([]session.Session, error)

func (*Service) ListTurns

func (s *Service) ListTurns(ctx context.Context, sessionID string, afterOrdinal int64, limit int) ([]session.Turn, error)

func (*Service) OpenReviewPatch

func (s *Service) OpenReviewPatch(
	ctx context.Context,
	operationID string,
) (*os.File, ReviewDossier, error)

func (*Service) PlanDiscard

func (s *Service) PlanDiscard(ctx context.Context, key string, req PlanDiscardRequest) (PlanDiscardResult, error)

func (*Service) PrepareSession

func (s *Service) PrepareSession(ctx context.Context, id string, expectedRevision int64) (session.Session, error)

func (*Service) RejectTurnCandidate

func (s *Service) RejectTurnCandidate(ctx context.Context, key string, req session.RejectTurnCandidateRequest) (session.Turn, error)

func (*Service) RunReview

func (s *Service) RunReview(ctx context.Context, key string, req RunReviewRequest) (ReviewDossier, error)

func (*Service) Start

func (s *Service) Start(parent context.Context) error

func (*Service) Stop

func (s *Service) Stop() error

func (*Service) Store

func (s *Service) Store() *session.Store

func (*Service) SubmitTurn

func (s *Service) SubmitTurn(ctx context.Context, key string, req session.SubmitTurnRequest) (session.Turn, error)

type SessionChangeDTO

type SessionChangeDTO struct {
	Path         string `json:"path,omitempty"`
	PathBytes    []byte `json:"path_bytes,omitempty"`
	OldPath      string `json:"old_path,omitempty"`
	OldPathBytes []byte `json:"old_path_bytes,omitempty"`
	Status       string `json:"status"`
}

type SessionChangesDTO

type SessionChangesDTO struct {
	BaseCommit       string                     `json:"base_commit"`
	ForkHead         string                     `json:"fork_head"`
	ForkTree         string                     `json:"fork_tree"`
	PullRequestTree  string                     `json:"pull_request_tree,omitempty"`
	ParentHead       string                     `json:"parent_head"`
	Committed        []SessionChangeDTO         `json:"committed"`
	Staged           []SessionChangeDTO         `json:"staged"`
	Unstaged         []SessionChangeDTO         `json:"unstaged"`
	Untracked        []SessionChangeDTO         `json:"untracked"`
	Conflicts        []SessionChangeDTO         `json:"conflicts"`
	ParentDivergence SessionParentDivergenceDTO `json:"parent_divergence"`
	Patch            []byte                     `json:"patch,omitempty"`
	Truncated        bool                       `json:"truncated"`
	PatchDigest      string                     `json:"patch_digest,omitempty"`
	PatchBytes       int64                      `json:"patch_bytes"`
	PatchOffset      int64                      `json:"patch_offset"`
	PatchNextOffset  int64                      `json:"patch_next_offset"`
	PatchHasMore     bool                       `json:"patch_has_more"`
}

type SessionCompanionDTO

type SessionCompanionDTO struct {
	Name       string `json:"name"`
	Path       string `json:"path"`
	BaseCommit string `json:"base_commit"`
}

type SessionDTO

type SessionDTO struct {
	ID                 string                      `json:"id"`
	ExternalRef        string                      `json:"external_ref"`
	Target             string                      `json:"target"`
	Policy             string                      `json:"policy"`
	PolicyDigest       string                      `json:"policy_digest"`
	RepositoryReadOnly bool                        `json:"repository_read_only"`
	BaseCommit         string                      `json:"base_commit"`
	PullRequest        *session.PullRequestBinding `json:"pull_request,omitempty"`
	Companions         []SessionCompanionDTO       `json:"companions,omitempty"`
	ForkName           string                      `json:"fork_name"`
	Revision           int64                       `json:"revision"`
	State              session.SessionState        `json:"state"`
	Activity           session.ActivityState       `json:"activity"`
	MaxTurns           int                         `json:"max_turns"`
	MaxQueuedTurns     int                         `json:"max_queued_turns"`
	MaxQueuedBytes     int                         `json:"max_queued_bytes"`
	TurnsUsed          int                         `json:"turns_used"`
	QueuedTurnCount    int                         `json:"queued_turn_count"`
	QueuedPromptBytes  int                         `json:"queued_prompt_bytes"`
	ActiveTurnID       string                      `json:"active_turn_id,omitempty"`
	LastEventSequence  int64                       `json:"last_event_sequence"`
	CreatedAt          time.Time                   `json:"created_at"`
	UpdatedAt          time.Time                   `json:"updated_at"`
}

These DTOs are the public v1 wire types. They deliberately do not mirror the durable records: the latter contain prompts, operation secrets, native provider identities, and host paths.

type SessionDiscardPlanDTO

type SessionDiscardPlanDTO struct {
	SessionID string                     `json:"session_id"`
	Revision  int64                      `json:"revision"`
	Workspace SessionDiscardWorkspaceDTO `json:"workspace"`
}

type SessionDiscardWorkspaceDTO

type SessionDiscardWorkspaceDTO struct {
	Branch           string `json:"branch"`
	Head             string `json:"head"`
	StatusDigest     string `json:"status_digest"`
	Dirty            bool   `json:"dirty"`
	Unmerged         bool   `json:"unmerged"`
	Running          bool   `json:"running"`
	AcceptedDirty    bool   `json:"accepted_dirty,omitempty"`
	AcceptedUnmerged bool   `json:"accepted_unmerged,omitempty"`
}

type SessionParentDivergenceDTO

type SessionParentDivergenceDTO struct {
	Ahead        int  `json:"ahead"`
	Behind       int  `json:"behind"`
	BaseToFork   int  `json:"base_to_fork"`
	BaseToParent int  `json:"base_to_parent"`
	Diverged     bool `json:"diverged"`
}

type SessionPlanDiscardDTO

type SessionPlanDiscardDTO struct {
	OperationID string                `json:"operation_id"`
	Plan        SessionDiscardPlanDTO `json:"plan"`
}

type SessionReviewDTO

type SessionReviewDTO struct {
	OperationID           string                      `json:"operation_id"`
	SessionID             string                      `json:"session_id"`
	SessionRevision       int64                       `json:"session_revision"`
	PolicyDigest          string                      `json:"policy_digest"`
	PullRequest           *session.PullRequestBinding `json:"pull_request,omitempty"`
	CreationBase          string                      `json:"creation_base"`
	SourceHead            string                      `json:"source_head"`
	SourceTree            string                      `json:"source_tree"`
	ParentHead            string                      `json:"parent_head"`
	ParentTree            string                      `json:"parent_tree"`
	CandidateHead         string                      `json:"candidate_head"`
	CandidateTree         string                      `json:"candidate_tree"`
	Rebase                ReviewRebaseStatus          `json:"rebase"`
	Gate                  ReviewGateStatus            `json:"gate"`
	GateError             string                      `json:"gate_error,omitempty"`
	PolicyFindings        []string                    `json:"policy_findings,omitempty"`
	Patch                 []byte                      `json:"patch,omitempty"`
	PatchTruncated        bool                        `json:"patch_truncated"`
	PatchArtifactID       string                      `json:"patch_artifact_id,omitempty"`
	PatchDigest           string                      `json:"patch_digest,omitempty"`
	PatchBytes            int64                       `json:"patch_bytes"`
	Publishable           bool                        `json:"publishable"`
	NotPublishableReasons []string                    `json:"not_publishable_reasons,omitempty"`
}

type TurnArtifactDTO

type TurnArtifactDTO struct {
	ID        string `json:"id"`
	Name      string `json:"name"`
	MediaType string `json:"media_type"`
	SHA256    string `json:"sha256"`
	Bytes     int64  `json:"bytes"`
}

type TurnDTO

type TurnDTO struct {
	ID                        string                 `json:"id"`
	SessionID                 string                 `json:"session_id"`
	Ordinal                   int64                  `json:"ordinal"`
	State                     session.TurnState      `json:"state"`
	SendState                 session.SendState      `json:"send_state"`
	AssistantMessage          string                 `json:"assistant_message,omitempty"`
	StopReason                session.StopReason     `json:"stop_reason,omitempty"`
	ErrorCode                 session.ErrorCode      `json:"error_code,omitempty"`
	ErrorDetail               string                 `json:"error_detail,omitempty"`
	QueuedAt                  time.Time              `json:"queued_at"`
	StartedAt                 time.Time              `json:"started_at,omitempty"`
	FinishedAt                time.Time              `json:"finished_at,omitempty"`
	OutputArtifacts           []TurnArtifactDTO      `json:"output_artifacts,omitempty"`
	Usage                     session.Usage          `json:"usage,omitzero"`
	Candidate                 *session.TurnCandidate `json:"candidate,omitempty"`
	ValidationCandidateSHA256 string                 `json:"validation_candidate_sha256,omitempty"`
	ValidationAttempt         int                    `json:"validation_attempt,omitempty"`
	ValidationError           string                 `json:"validation_error,omitempty"`
	ValidationReceipt         string                 `json:"validation_receipt,omitempty"`
}

type WorkspaceChanges

type WorkspaceChanges struct {
	BaseCommit       string                           `json:"base_commit"`
	ForkHead         string                           `json:"fork_head"`
	ForkTree         string                           `json:"fork_tree"`
	PullRequestTree  string                           `json:"pull_request_tree,omitempty"`
	ParentHead       string                           `json:"parent_head"`
	Committed        []sessionWorkspaceChange         `json:"committed,omitempty"`
	Staged           []sessionWorkspaceChange         `json:"staged,omitempty"`
	Unstaged         []sessionWorkspaceChange         `json:"unstaged,omitempty"`
	Untracked        []sessionWorkspaceChange         `json:"untracked,omitempty"`
	Conflicts        []sessionWorkspaceChange         `json:"conflicts,omitempty"`
	ParentDivergence sessionWorkspaceParentDivergence `json:"parent_divergence"`
	Patch            string                           `json:"patch,omitempty"`
	Truncated        bool                             `json:"truncated"`
	PatchDigest      string                           `json:"patch_digest,omitempty"`
	PatchBytes       int64                            `json:"patch_bytes"`
	PatchOffset      int64                            `json:"patch_offset"`
	PatchNextOffset  int64                            `json:"patch_next_offset"`
	PatchHasMore     bool                             `json:"patch_has_more"`
}

type WorkspaceDiscardPlan

type WorkspaceDiscardPlan struct {
	Repo              string                   `json:"repo"`
	Name              string                   `json:"name"`
	Workspace         string                   `json:"workspace"`
	WorkspaceIdentity sessionWorkspaceIdentity `json:"workspace_identity"`
	Branch            string                   `json:"branch"`
	Head              string                   `json:"head"`
	ParentHead        string                   `json:"parent_head"`
	StatusDigest      string                   `json:"status_digest"`
	Dirty             bool                     `json:"dirty"`
	Unmerged          bool                     `json:"unmerged"`
	Running           bool                     `json:"running"`
	AcceptedDirty     bool                     `json:"accepted_dirty,omitempty"`
	AcceptedUnmerged  bool                     `json:"accepted_unmerged,omitempty"`
}

Jump to

Keyboard shortcuts

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