Files
2026-06-22 08:55:57 +08:00

120 lines
3.8 KiB
Go

// Package changerequest models the change_requests entity and the server-side
// merge gate. A change request links a project/workspace/(requirement|issue)
// and a source->target branch to an external (Gitea) pull request, carrying a
// review verdict + CI state. The crux is Service.Merge, which hard-blocks the
// host-side merge unless review_verdict=='approved' (and optionally
// ci_state=='success'): an agent can open and merge PRs via MCP, but cannot
// self-approve — approval is HTTP/web-only by policy.
//
// Layout mirrors internal/workspace (single package, service + repository +
// handler + sqlc) to keep the aggregate cohesive.
package changerequest
import (
"context"
"time"
"github.com/google/uuid"
)
// State mirrors the change_requests.state CHECK constraint.
type State string
// State enum values.
const (
StateOpen State = "open"
StateMerged State = "merged"
StateClosed State = "closed"
)
// Verdict mirrors the change_requests.review_verdict CHECK constraint.
type Verdict string
// Verdict enum values.
const (
VerdictPending Verdict = "pending"
VerdictApproved Verdict = "approved"
VerdictChangesRequested Verdict = "changes_requested"
VerdictRejected Verdict = "rejected"
)
// IsValid reports whether v is in the enum set.
func (v Verdict) IsValid() bool {
switch v {
case VerdictPending, VerdictApproved, VerdictChangesRequested, VerdictRejected:
return true
}
return false
}
// CIState mirrors the change_requests.ci_state CHECK constraint.
type CIState string
// CIState enum values.
const (
CIUnknown CIState = "unknown"
CIPending CIState = "pending"
CISuccess CIState = "success"
CIFailure CIState = "failure"
)
// ChangeRequest is the change_requests row projection.
type ChangeRequest struct {
ID uuid.UUID
ProjectID uuid.UUID
WorkspaceID uuid.UUID
RequirementID *uuid.UUID
IssueID *uuid.UUID
Number int
Title string
Description string
SourceBranch string
TargetBranch string
State State
ReviewVerdict Verdict
CIState CIState
Provider string
ExternalID *int64
ExternalURL string
MergeCommitSHA string
ReviewedBy *uuid.UUID
ReviewedAt *time.Time
CreatedBy uuid.UUID
MergedAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
// Caller mirrors workspace.Caller: the user context driving auth + audit.
type Caller struct {
UserID uuid.UUID
IsAdmin bool
SessionID *uuid.UUID
}
// CreateInput carries the fields to open a change request (and host PR).
type CreateInput struct {
SourceBranch string
TargetBranch string // empty => workspace default branch
Title string
Description string
RequirementID *uuid.UUID
IssueID *uuid.UUID
}
// Service is the change-request application service.
type Service interface {
Create(ctx context.Context, c Caller, wsID uuid.UUID, in CreateInput) (*ChangeRequest, error)
Get(ctx context.Context, c Caller, id uuid.UUID) (*ChangeRequest, error)
GetByNumber(ctx context.Context, c Caller, projectSlug string, number int) (*ChangeRequest, error)
ListByWorkspace(ctx context.Context, c Caller, wsID uuid.UUID) ([]*ChangeRequest, error)
// Review records a human verdict (owner/admin only). It is intentionally NOT
// exposed as an MCP tool so agents cannot self-approve their own PRs.
Review(ctx context.Context, c Caller, id uuid.UUID, verdict Verdict, note string) (*ChangeRequest, error)
// Merge is the gate: requires review_verdict=='approved' (+ ci success when
// RequireCIPass), then merges the host PR and persists merged state.
Merge(ctx context.Context, c Caller, id uuid.UUID, method string) (*ChangeRequest, error)
// SyncStatus refreshes ci_state/state from the host (provider.GetPR).
SyncStatus(ctx context.Context, c Caller, id uuid.UUID) (*ChangeRequest, error)
}