package domain import ( "errors" "slices" "strings" "git.cin.su/m/Assistant/contracts/work" ) var ErrInvalidDevelopmentGate = errors.New("invalid governed self-development gate") var ErrInsufficientExtensionEvidence = errors.New("insufficient repeated extension evidence") var ErrInvalidExtensionProposal = errors.New("invalid extension proposal") var ErrInvalidDevelopmentScaffold = errors.New("invalid development scaffold candidate") // BuildExtensionProposal deterministically turns a repeated evidence signal // into a proposal. It retains every distinct evidence reference for lineage, // while deduplicating signal keys so repeated reports do not create duplicate // proposals. It never creates a task, mutates an implementation or grants // authority. func BuildExtensionProposal(evidence []work.ExtensionEvidence, request work.ExtensionProposalRequest) (work.ExtensionProposal, error) { owners := cleanStrings(request.AffectedOwners) contracts := cleanStrings(request.AffectedContracts) evaluation := cleanStrings(request.EvaluationPlan) alternatives := cleanStrings(request.Alternatives) assumptions := cleanStrings(request.Assumptions) if request.ID == "" || request.CreatedAt.IsZero() || strings.TrimSpace(request.DesiredOutcome) == "" || strings.TrimSpace(request.SmallestChangeLayer) == "" || strings.TrimSpace(request.RiskTier) == "" || strings.TrimSpace(request.RollbackPlan) == "" || len(owners) == 0 || len(contracts) == 0 || len(evaluation) == 0 || len(alternatives) == 0 || len(assumptions) == 0 { return work.ExtensionProposal{}, ErrInvalidExtensionProposal } refs := make([]string, 0, len(evidence)) refSeen := make(map[string]struct{}, len(evidence)) counts := make(map[string]int, len(evidence)) keys := make([]string, 0, len(evidence)) keySeen := make(map[string]struct{}, len(evidence)) for _, item := range evidence { ref := strings.TrimSpace(item.Ref) key := strings.TrimSpace(item.SignalKey) if ref == "" || key == "" { return work.ExtensionProposal{}, ErrInvalidExtensionProposal } if _, ok := refSeen[ref]; !ok { refs = append(refs, ref) refSeen[ref] = struct{}{} } counts[key]++ if _, ok := keySeen[key]; !ok { keys = append(keys, key) keySeen[key] = struct{}{} } } if len(refs) < 2 { return work.ExtensionProposal{}, ErrInsufficientExtensionEvidence } repeated := false for _, count := range counts { if count >= 2 { repeated = true break } } if !repeated { return work.ExtensionProposal{}, ErrInsufficientExtensionEvidence } return work.ExtensionProposal{ ID: request.ID, Stage: work.ExtensionProposalProposed, EvidenceRefs: refs, DeduplicatedSignalKeys: keys, DesiredOutcome: strings.TrimSpace(request.DesiredOutcome), SmallestChangeLayer: strings.TrimSpace(request.SmallestChangeLayer), AffectedOwners: owners, AffectedContracts: contracts, RiskTier: strings.TrimSpace(request.RiskTier), EvaluationPlan: evaluation, Alternatives: alternatives, RollbackPlan: strings.TrimSpace(request.RollbackPlan), Assumptions: assumptions, CreatedAtUnixMillis: request.CreatedAt.UnixMilli(), }, nil } func cleanStrings(values []string) []string { result := make([]string, 0, len(values)) seen := make(map[string]struct{}, len(values)) for _, value := range values { value = strings.TrimSpace(value) if value == "" { continue } if _, ok := seen[value]; ok { continue } seen[value] = struct{}{} result = append(result, value) } return result } // BuildDevelopmentScaffold creates only a typed, isolated candidate. The // caller supplies the protected path set so this domain function cannot infer // or widen repository authority from ambient process state. func BuildDevelopmentScaffold(request work.DevelopmentScaffoldRequest) (work.DevelopmentScaffold, error) { modules := cleanStrings(request.Modules) allowed := cleanPaths(request.AllowedPaths) forbidden := cleanPaths(request.ForbiddenPaths) protected := cleanPaths(request.ProtectedPaths) tests := cleanStrings(request.RequiredTests) lineage := cleanStrings(request.ArtifactLineageRefs) if request.ID == "" || request.ProposalRef == "" || strings.TrimSpace(request.BaseRepoRevision) == "" || strings.TrimSpace(request.WorktreeRef) == "" || strings.TrimSpace(request.TaskIntent) == "" || request.CreatedAt.IsZero() || len(modules) == 0 || len(allowed) == 0 || len(tests) == 0 || len(lineage) == 0 || request.ProtectedMainAccess != "DENIED" || request.ProductionAccess != "DENIED" || request.SecretAccess != "DENIED" { return work.DevelopmentScaffold{}, ErrInvalidDevelopmentScaffold } for _, path := range allowed { if unsafePath(path) || pathMatchesAny(path, forbidden) || pathMatchesAny(path, protected) { return work.DevelopmentScaffold{}, ErrInvalidDevelopmentScaffold } } worktree := strings.ToLower(strings.TrimSpace(request.WorktreeRef)) if worktree == "main" || worktree == "protected-main" || worktree == "production" { return work.DevelopmentScaffold{}, ErrInvalidDevelopmentScaffold } return work.DevelopmentScaffold{ID: request.ID, Stage: work.DevelopmentScaffoldSandboxed, ProposalRef: request.ProposalRef, BaseRepoRevision: strings.TrimSpace(request.BaseRepoRevision), WorktreeRef: strings.TrimSpace(request.WorktreeRef), TaskIntent: strings.TrimSpace(request.TaskIntent), Modules: modules, AllowedPaths: allowed, ForbiddenPaths: forbidden, RequiredTests: tests, ArtifactLineageRefs: lineage, ProtectedMainAccess: "DENIED", ProductionAccess: "DENIED", SecretAccess: "DENIED", CreatedAtUnixMillis: request.CreatedAt.UnixMilli()}, nil } func cleanPaths(values []string) []string { result := make([]string, 0, len(values)) seen := make(map[string]struct{}, len(values)) for _, value := range values { value = strings.TrimSpace(strings.ReplaceAll(value, "\\", "/")) if value == "" { continue } if _, ok := seen[value]; ok { continue } seen[value] = struct{}{} result = append(result, value) } return result } func unsafePath(path string) bool { return strings.HasPrefix(path, "/") || strings.HasPrefix(path, "~") || (len(path) > 1 && path[1] == ':') || slices.Contains(strings.Split(path, "/"), "..") } func pathMatchesAny(path string, scopes []string) bool { for _, scope := range scopes { scope = strings.TrimSuffix(scope, "/") scope = strings.TrimSuffix(scope, "**") scope = strings.TrimSuffix(scope, "/") if path == scope || strings.HasPrefix(path, scope+"/") { return true } } return false } func AdvanceDevelopmentGate(current work.DevelopmentGate, next work.DevelopmentStage) (work.DevelopmentGate, error) { if err := ValidateDevelopmentGate(current); err != nil { return work.DevelopmentGate{}, err } if !validDevelopmentTransition(current.Stage, next) { return work.DevelopmentGate{}, ErrInvalidDevelopmentGate } current.Stage = next switch next { case work.DevelopmentProposalCreated: if current.ProposalRef == "" { return work.DevelopmentGate{}, ErrInvalidDevelopmentGate } case work.DevelopmentTaskAdmitted: if current.TaskPacketRef == "" { return work.DevelopmentGate{}, ErrInvalidDevelopmentGate } case work.DevelopmentIsolated: if current.WorktreeRef == "" { return work.DevelopmentGate{}, ErrInvalidDevelopmentGate } case work.DevelopmentVerified: if current.VerificationEvidence == "" || len(current.VerifierRunRefs) == 0 || len(current.ProducerRunRefs) == 0 || sameRuns(current.ProducerRunRefs, current.VerifierRunRefs) { return work.DevelopmentGate{}, ErrInvalidDevelopmentGate } case work.DevelopmentCanary: if current.CanaryEvidence == "" { return work.DevelopmentGate{}, ErrInvalidDevelopmentGate } case work.DevelopmentPromoted: if current.PromotionRef == "" { return work.DevelopmentGate{}, ErrInvalidDevelopmentGate } case work.DevelopmentRolledBack: if current.RollbackRef == "" { return work.DevelopmentGate{}, ErrInvalidDevelopmentGate } } return current, nil } func ValidateDevelopmentGate(value work.DevelopmentGate) error { if value.ID == "" || len(value.EvidenceRefs) == 0 || !validDevelopmentStage(value.Stage) || duplicateStrings(value.EvidenceRefs) { return ErrInvalidDevelopmentGate } return nil } func validDevelopmentTransition(current, next work.DevelopmentStage) bool { if current == work.DevelopmentCanary && next == work.DevelopmentRolledBack { return true } if current == work.DevelopmentPromoted || current == work.DevelopmentRolledBack { return false } order := []work.DevelopmentStage{work.DevelopmentEvidenceCollected, work.DevelopmentProposalCreated, work.DevelopmentTaskAdmitted, work.DevelopmentIsolated, work.DevelopmentVerified, work.DevelopmentCanary, work.DevelopmentPromoted} index := func(value work.DevelopmentStage) int { return slices.Index(order, value) } return index(next) == index(current)+1 } func validDevelopmentStage(value work.DevelopmentStage) bool { return slices.Contains([]work.DevelopmentStage{work.DevelopmentEvidenceCollected, work.DevelopmentProposalCreated, work.DevelopmentTaskAdmitted, work.DevelopmentIsolated, work.DevelopmentVerified, work.DevelopmentCanary, work.DevelopmentPromoted, work.DevelopmentRolledBack}, value) } func duplicateStrings(values []string) bool { seen := map[string]bool{} for _, value := range values { if value == "" || seen[value] { return true } seen[value] = true } return false } func sameRuns(left, right []work.AgentRunID) bool { if len(left) != len(right) { return false } for _, value := range left { if !slices.Contains(right, value) { return false } } return true }