Files
Assistant/modules/work/domain/self_development.go
m 1ccd43c552
CI / test (push) Failing after 15s
CI / test (pull_request) Failing after 14s
feat(work): add governed self-development gate
2026-08-22 13:46:01 +03:00

98 lines
3.2 KiB
Go

package domain
import (
"errors"
"slices"
"git.cin.su/m/Assistant/contracts/work"
)
var ErrInvalidDevelopmentGate = errors.New("invalid governed self-development gate")
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
}