feat: generate governed extension proposals from repeated evidence
CI / test (push) Failing after 41s
CI / test (pull_request) Failing after 42s

This commit is contained in:
m
2026-08-23 01:25:49 +03:00
parent cdbccb6238
commit ecff1385e5
6 changed files with 215 additions and 2 deletions
+77
View File
@@ -3,11 +3,88 @@ 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")
// 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
}
func AdvanceDevelopmentGate(current work.DevelopmentGate, next work.DevelopmentStage) (work.DevelopmentGate, error) {
if err := ValidateDevelopmentGate(current); err != nil {
@@ -2,6 +2,7 @@ package domain
import (
"testing"
"time"
"git.cin.su/m/Assistant/contracts/work"
)
@@ -20,6 +21,36 @@ func TestGovernedSelfDevelopmentRequiresIndependentVerifierBeforeCanary(t *testi
}
}
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 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 {