Files
weichen 83561682eb feat(swboot): wire SWBOOT-10 controlled patch executor (fail-closed)
DB-backed repo_path_allowlist reader (deny-all until admin seeds), per-prefix
admin test_commands (goose 00075), execution evidence table (goose 00074),
isolated git-worktree patch executor with command allowlist/time/output caps
and failure-stops evidence; controlled_patch creation/preview unlocked only
with an allowlisted repo binding (projectworkflow gate + catalog flag);
creator-only server endpoints for bindings/executions; merge/push/deploy stay
behind a typed separate-authorization boundary. Module boundary: app->swboot
composition import registered.
2026-09-26 00:56:59 +00:00

287 lines
12 KiB
Go

package swboot
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"strings"
"time"
)
// Product domains and enumerations (SWBOOT-00 contract).
const (
DomainNonMedicalSoftware = "non_medical_software"
DomainMedicalDevice = "medical_device"
ProjectTypeGreenfield = "greenfield"
ProjectTypeExistingProduct = "existing_product"
ProjectTypeLegacyModernization = "legacy_modernization"
ProjectTypeExistingSelfBootstrap = "existing_self_bootstrap"
GovernanceLean = "lean"
GovernanceStandard = "standard"
GovernanceControlled = "controlled"
TeamModeSolo = "solo"
TeamModeCollaborative = "collaborative"
ExecutionDocsOnly = "docs_only"
ExecutionReadOnlyRepo = "read_only_repo"
ExecutionControlledPatch = "controlled_patch"
ApprovalIndependentThreeDuties = "independent_three_duties"
ApprovalCreatorAcknowledgement = "creator_acknowledgement"
)
// ProjectTypeCatalogItem is a create-wizard selectable project type.
type ProjectTypeCatalogItem struct {
Key string `json:"key"`
Name string `json:"name"`
ProductDomain string `json:"product_domain"`
Description string `json:"description"`
DefaultGovernance string `json:"default_governance"`
DefaultWorkflowKey string `json:"default_workflow_key"`
RecommendedWorkflows []string `json:"recommended_workflows"`
}
// ComplianceProfileCatalogItem is a create-wizard compliance option.
type ComplianceProfileCatalogItem struct {
Key string `json:"key"`
Name string `json:"name"`
Description string `json:"description"`
RequiresIndependentReview bool `json:"requires_independent_review"`
}
// CreationPreviewInput is the seven-step wizard confirmation payload.
type CreationPreviewInput struct {
Name string `json:"name"`
Code string `json:"code,omitempty"`
ProductDomain string `json:"product_domain"`
ProjectTypeKey string `json:"project_type_key"`
ProductShape string `json:"product_shape,omitempty"`
ProblemStatement string `json:"problem_statement"`
TargetUsers string `json:"target_users"`
TargetOutcome string `json:"target_outcome"`
GovernanceProfile string `json:"governance_profile"`
TeamMode string `json:"team_mode"`
ComplianceProfileKey string `json:"compliance_profile_key"`
DataSensitivity string `json:"data_sensitivity,omitempty"`
NetworkPolicy string `json:"network_policy,omitempty"`
ExecutionMode string `json:"execution_mode"`
// RepoAllowlisted is set by the transport layer after checking the admin
// repo_path_allowlist for RepoPath; controlled_patch is only valid when a
// bound repo path passed that check (fail-closed otherwise).
RepoAllowlisted bool `json:"repo_allowlisted,omitempty"`
RepoPath string `json:"repo_path,omitempty"`
BudgetClass string `json:"budget_class,omitempty"`
InitialWorkflowKey string `json:"initial_workflow_key,omitempty"`
CatalogVersionHint string `json:"expected_catalog_version,omitempty"`
}
// CreationPreviewResult is returned by POST /projects/creation-preview.
type CreationPreviewResult struct {
CreationToken string `json:"creation_token"`
PlanHash string `json:"plan_hash"`
SourceHash string `json:"source_hash"`
ExpectedCatalogVersion string `json:"expected_catalog_version"`
ExpiresAt time.Time `json:"expires_at"`
ApprovalPolicy string `json:"approval_policy"`
RecommendedWorkflow string `json:"recommended_workflow"`
Warnings []string `json:"warnings,omitempty"`
Issues []string `json:"issues,omitempty"`
Valid bool `json:"valid"`
}
// ProjectTypesCatalog returns seeded software project types.
// Wave 5 X-3: goose 00048 tables project_types / project_type_workflows exist with
// seed rows, but HTTP catalog still serves this hard-coded list. DB-backed
// consumption is deferred to SWBOOT-10 / W5b. See
// .doc/ops/short-term/2026-09-18-wave5-deadcode-decisions.md#x-3.
func ProjectTypesCatalog() []ProjectTypeCatalogItem {
return []ProjectTypeCatalogItem{
{
Key: ProjectTypeGreenfield, Name: "绿地立项", ProductDomain: DomainNonMedicalSoftware,
Description: "从零规划新软件", DefaultGovernance: GovernanceStandard,
DefaultWorkflowKey: "software_bootstrap", RecommendedWorkflows: []string{"software_bootstrap", "software_delivery"},
},
{
Key: ProjectTypeExistingProduct, Name: "既有产品梳理", ProductDomain: DomainNonMedicalSoftware,
Description: "梳理已有产品与仓库", DefaultGovernance: GovernanceStandard,
DefaultWorkflowKey: "software_bootstrap", RecommendedWorkflows: []string{"software_bootstrap", "software_delivery"},
},
{
Key: ProjectTypeLegacyModernization, Name: "遗留现代化", ProductDomain: DomainNonMedicalSoftware,
Description: "遗留系统改造与迁移", DefaultGovernance: GovernanceStandard,
DefaultWorkflowKey: "software_bootstrap", RecommendedWorkflows: []string{"software_bootstrap", "software_delivery"},
},
{
Key: ProjectTypeExistingSelfBootstrap, Name: "系统自举", ProductDomain: DomainNonMedicalSoftware,
Description: "用 Agent-System 梳理并建设自身", DefaultGovernance: GovernanceStandard,
DefaultWorkflowKey: "software_bootstrap", RecommendedWorkflows: []string{"software_bootstrap", "software_delivery"},
},
}
}
// ComplianceProfilesCatalog returns seeded compliance profiles.
// Wave 5 X-3: goose 00048 table compliance_profiles is seeded but unused at
// runtime; this hard-coded catalog remains authoritative until SWBOOT-10 / W5b.
// See .doc/ops/short-term/2026-09-18-wave5-deadcode-decisions.md#x-3.
func ComplianceProfilesCatalog() []ComplianceProfileCatalogItem {
return []ComplianceProfileCatalogItem{
{Key: "none", Name: "无外部合规预设", Description: "不预设外部法规步骤;鉴权/审计/安全基线仍生效"},
{Key: "internal_control", Name: "内部控制", Description: "内部流程、权限与审计约束"},
{Key: "security_baseline", Name: "安全基线", Description: "威胁建模、依赖与 secret 边界"},
{Key: "privacy", Name: "隐私保护", Description: "数据分类与最小权限", RequiresIndependentReview: true},
{Key: "custom", Name: "自定义合规", Description: "项目自定义合规包", RequiresIndependentReview: true},
{Key: "internal_control+security_baseline", Name: "内部控制+安全基线", Description: "首期推荐组合"},
}
}
// BuildCreationPreview validates wizard input and issues a short-lived token.
func BuildCreationPreview(in CreationPreviewInput, now time.Time) CreationPreviewResult {
res := CreationPreviewResult{
Valid: true,
ExpectedCatalogVersion: strings.TrimSpace(in.CatalogVersionHint),
ExpiresAt: now.Add(15 * time.Minute),
SourceHash: "sha256:empty",
}
if res.ExpectedCatalogVersion == "" {
res.ExpectedCatalogVersion = "software-v1"
}
name := strings.TrimSpace(in.Name)
if name == "" {
res.Valid = false
res.Issues = append(res.Issues, "name is required")
}
domain := strings.TrimSpace(in.ProductDomain)
if domain == "" {
res.Valid = false
res.Issues = append(res.Issues, "product_domain is required")
}
if domain != "" && domain != DomainNonMedicalSoftware && domain != DomainMedicalDevice {
res.Valid = false
res.Issues = append(res.Issues, "unsupported product_domain")
}
ptype := strings.TrimSpace(in.ProjectTypeKey)
if domain == DomainNonMedicalSoftware {
if !isKnownProjectType(ptype) {
res.Valid = false
res.Issues = append(res.Issues, "project_type_key is required for non_medical_software")
}
if strings.TrimSpace(in.ProblemStatement) == "" || strings.TrimSpace(in.TargetUsers) == "" || strings.TrimSpace(in.TargetOutcome) == "" {
res.Valid = false
res.Issues = append(res.Issues, "problem_statement, target_users and target_outcome are required")
}
}
gov := strings.TrimSpace(in.GovernanceProfile)
if gov == "" {
gov = GovernanceStandard
}
if gov != GovernanceLean && gov != GovernanceStandard && gov != GovernanceControlled {
res.Valid = false
res.Issues = append(res.Issues, "invalid governance_profile")
}
team := strings.TrimSpace(in.TeamMode)
if team == "" {
team = TeamModeCollaborative
}
compliance := strings.TrimSpace(in.ComplianceProfileKey)
if compliance == "" {
compliance = "internal_control+security_baseline"
}
if requiresIndependentReview(compliance) && team == TeamModeSolo {
res.Valid = false
res.Issues = append(res.Issues, "compliance profile requiring independent review forbids solo")
}
if gov == GovernanceControlled && team == TeamModeSolo {
res.Valid = false
res.Issues = append(res.Issues, "controlled governance forbids solo")
}
execMode := strings.TrimSpace(in.ExecutionMode)
if execMode == "" {
execMode = ExecutionDocsOnly
}
switch execMode {
case ExecutionDocsOnly, ExecutionReadOnlyRepo:
// ok for SWBOOT-00..09
case ExecutionControlledPatch:
if !in.RepoAllowlisted || strings.TrimSpace(in.RepoPath) == "" {
res.Valid = false
res.Issues = append(res.Issues, "ERR_EXECUTION_MODE_DISABLED: controlled_patch requires an allowlisted repo binding (SWBOOT-10)")
}
default:
res.Valid = false
res.Issues = append(res.Issues, "invalid execution_mode")
}
wf := strings.TrimSpace(in.InitialWorkflowKey)
if wf == "" && domain == DomainNonMedicalSoftware {
wf = "software_bootstrap"
}
if domain == DomainNonMedicalSoftware && wf != "" && wf != "software_bootstrap" && wf != "software_delivery" {
res.Warnings = append(res.Warnings, "non-software workflow selected for non_medical_software project")
}
res.RecommendedWorkflow = wf
if team == TeamModeSolo {
res.ApprovalPolicy = ApprovalCreatorAcknowledgement
res.Warnings = append(res.Warnings, "未独立评审:solo 使用 creator_acknowledgement")
} else {
res.ApprovalPolicy = ApprovalIndependentThreeDuties
}
payload := map[string]any{
"name": name, "code": strings.TrimSpace(in.Code),
"product_domain": domain, "project_type_key": ptype,
"governance_profile": gov, "team_mode": team,
"compliance_profile_key": compliance, "execution_mode": execMode,
"workflow": wf, "catalog": res.ExpectedCatalogVersion,
"problem_statement": strings.TrimSpace(in.ProblemStatement),
"target_users": strings.TrimSpace(in.TargetUsers),
"target_outcome": strings.TrimSpace(in.TargetOutcome),
}
raw, _ := json.Marshal(payload)
sum := sha256.Sum256(raw)
res.PlanHash = "sha256:" + hex.EncodeToString(sum[:])
res.CreationToken = fmt.Sprintf("crt_%s", hex.EncodeToString(sum[:8]))
if !res.Valid {
res.CreationToken = ""
res.PlanHash = ""
}
return res
}
// DeliverySuggestion is the post-bootstrap recommendation (never auto-activates).
type DeliverySuggestion struct {
WorkflowKey string `json:"workflow_key"`
AutoActivate bool `json:"auto_activate"`
Summary string `json:"summary"`
}
// SuggestDeliveryAfterBootstrap recommends software_delivery without activating it.
func SuggestDeliveryAfterBootstrap() DeliverySuggestion {
return DeliverySuggestion{
WorkflowKey: "software_delivery",
AutoActivate: false,
Summary: "bootstrap 完成后仅建议 software_delivery,须创建者确认后新建实例,禁止自动激活",
}
}
func isKnownProjectType(key string) bool {
switch key {
case ProjectTypeGreenfield, ProjectTypeExistingProduct, ProjectTypeLegacyModernization, ProjectTypeExistingSelfBootstrap:
return true
default:
return false
}
}
func requiresIndependentReview(profile string) bool {
p := strings.ToLower(profile)
return strings.Contains(p, "privacy") || strings.Contains(p, "custom") || p == "controlled"
}