76 lines
4.7 KiB
Go
76 lines
4.7 KiB
Go
package domain
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"git.cin.su/m/Assistant/contracts/work"
|
|
)
|
|
|
|
func TestGovernedSelfDevelopmentRequiresIndependentVerifierBeforeCanary(t *testing.T) {
|
|
gate := work.DevelopmentGate{ID: "dev-1", Stage: work.DevelopmentEvidenceCollected, EvidenceRefs: []string{"obs-1"}, ProposalRef: "proposal-1", TaskPacketRef: "task-1", WorktreeRef: "worktree-1", ProducerRunRefs: []work.AgentRunID{"producer"}, VerifierRunRefs: []work.AgentRunID{"verifier"}, VerificationEvidence: "verify-1", CanaryEvidence: "canary-1", PromotionRef: "promotion-1"}
|
|
for _, stage := range []work.DevelopmentStage{work.DevelopmentProposalCreated, work.DevelopmentTaskAdmitted, work.DevelopmentIsolated, work.DevelopmentVerified, work.DevelopmentCanary, work.DevelopmentPromoted} {
|
|
var err error
|
|
gate, err = AdvanceDevelopmentGate(gate, stage)
|
|
if err != nil {
|
|
t.Fatalf("stage %s: %v", stage, err)
|
|
}
|
|
}
|
|
if _, err := AdvanceDevelopmentGate(gate, work.DevelopmentRolledBack); err == nil {
|
|
t.Fatal("promoted gate must be terminal")
|
|
}
|
|
}
|
|
|
|
func TestBuildExtensionProposalRequiresRepeatedEvidenceAndRetainsLineage(t *testing.T) {
|
|
proposal, err := BuildExtensionProposal([]work.ExtensionEvidence{
|
|
{Ref: "reflection-1", SignalKey: "send-email-gap"},
|
|
{Ref: "reflection-2", SignalKey: "send-email-gap"},
|
|
{Ref: "reflection-2", SignalKey: "send-email-gap"},
|
|
}, work.ExtensionProposalRequest{
|
|
ID: "proposal-1", DesiredOutcome: "connect a reviewed email capability", SmallestChangeLayer: "capability-adapter",
|
|
AffectedOwners: []string{"capability", "commit"}, AffectedContracts: []string{"CapabilityImplementation@v1"}, RiskTier: "D",
|
|
EvaluationPlan: []string{"fake transport", "adversarial corpus"}, Alternatives: []string{"keep capability gap"},
|
|
RollbackPlan: "restore previous implementation reference", Assumptions: []string{"human promotion required"}, CreatedAt: time.UnixMilli(42),
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if proposal.Stage != work.ExtensionProposalProposed || len(proposal.EvidenceRefs) != 2 || len(proposal.DeduplicatedSignalKeys) != 1 || proposal.CreatedAtUnixMillis != 42 {
|
|
t.Fatalf("unexpected proposal: %+v", proposal)
|
|
}
|
|
}
|
|
|
|
func TestBuildExtensionProposalRejectsSingleFailureAndIncompleteGovernance(t *testing.T) {
|
|
request := work.ExtensionProposalRequest{ID: "proposal-1", DesiredOutcome: "x", SmallestChangeLayer: "skill", AffectedOwners: []string{"capability"}, AffectedContracts: []string{"Skill@v1"}, RiskTier: "B", EvaluationPlan: []string{"test"}, Alternatives: []string{"keep current behavior"}, RollbackPlan: "disable", Assumptions: []string{"human review"}, CreatedAt: time.UnixMilli(42)}
|
|
if _, err := BuildExtensionProposal([]work.ExtensionEvidence{{Ref: "failure-1", SignalKey: "same"}}, request); err != ErrInsufficientExtensionEvidence {
|
|
t.Fatalf("single failure must be rejected, got %v", err)
|
|
}
|
|
request.Assumptions = nil
|
|
if _, err := BuildExtensionProposal([]work.ExtensionEvidence{{Ref: "failure-1", SignalKey: "same"}, {Ref: "failure-2", SignalKey: "same"}}, request); err != ErrInvalidExtensionProposal {
|
|
t.Fatalf("missing assumptions must be rejected, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBuildDevelopmentScaffoldFailsClosedForProtectedScope(t *testing.T) {
|
|
base := work.DevelopmentScaffoldRequest{ID: "scaffold-1", ProposalRef: "proposal-1", BaseRepoRevision: "abc123", WorktreeRef: "worktree-1", TaskIntent: "add reviewed adapter", Modules: []string{"capability"}, AllowedPaths: []string{"modules/capability/**"}, ForbiddenPaths: []string{"security/**"}, ProtectedPaths: []string{"architecture/**", "deploy/**"}, RequiredTests: []string{"go test ./..."}, ArtifactLineageRefs: []string{"proposal-1"}, ProtectedMainAccess: "DENIED", ProductionAccess: "DENIED", SecretAccess: "DENIED", CreatedAt: time.UnixMilli(1)}
|
|
scaffold, err := BuildDevelopmentScaffold(base)
|
|
if err != nil || scaffold.Stage != work.DevelopmentScaffoldSandboxed {
|
|
t.Fatalf("valid scaffold rejected: %+v %v", scaffold, err)
|
|
}
|
|
base.AllowedPaths = []string{"deploy/docker-compose.prod.yml"}
|
|
if _, err := BuildDevelopmentScaffold(base); err != ErrInvalidDevelopmentScaffold {
|
|
t.Fatalf("protected path must fail closed, got %v", err)
|
|
}
|
|
base.AllowedPaths = []string{"modules/capability/../security"}
|
|
if _, err := BuildDevelopmentScaffold(base); err != ErrInvalidDevelopmentScaffold {
|
|
t.Fatalf("traversal path must fail closed, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestGovernedSelfDevelopmentRejectsSameProducerAndVerifier(t *testing.T) {
|
|
gate := work.DevelopmentGate{ID: "dev-1", Stage: work.DevelopmentIsolated, EvidenceRefs: []string{"obs-1"}, ProducerRunRefs: []work.AgentRunID{"same"}, VerifierRunRefs: []work.AgentRunID{"same"}, VerificationEvidence: "verify-1"}
|
|
if _, err := AdvanceDevelopmentGate(gate, work.DevelopmentVerified); err == nil {
|
|
t.Fatal("same run cannot verify its own output")
|
|
}
|
|
}
|