Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd00dabf32 | ||
|
|
33365ce7ee | ||
|
|
5017a854ee | ||
|
|
839b35f310 | ||
|
|
96687140ce | ||
|
|
b282038949 | ||
|
|
4de506a2af | ||
|
|
36272b9036 | ||
|
|
a779568622 | ||
|
|
243a11490b | ||
|
|
26207d93ef | ||
|
|
42871f2e47 | ||
|
|
6ea1d4b729 | ||
|
|
c6d1ebf36f | ||
|
|
25d8d6d219 | ||
|
|
6cbef9af41 | ||
|
|
2fe9802e5a | ||
|
|
0882df3ad2 | ||
|
|
0acbe53fad |
@@ -0,0 +1,18 @@
|
||||
package multimodal
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"git.cin.su/m/Assistant/contracts/artifact"
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
"git.cin.su/m/Assistant/contracts/interaction"
|
||||
)
|
||||
|
||||
func NormalizeIngress(id string, modality interaction.IngressModality, sourceRef, deviceRef, locationRef, principalID, contentType string, data []byte, response capability.MultimodalResponse, at int64) (interaction.IngressEnvelope, artifact.MediaArtifact) {
|
||||
hash := sha256.Sum256(data)
|
||||
artifactValue := artifact.MediaArtifact{ID: artifact.ArtifactID("artifact:media:" + hex.EncodeToString(hash[:8])), ContentType: contentType, SizeBytes: int64(len(data)), ContentDigest: hex.EncodeToString(hash[:]), SourceRef: sourceRef, RecordedAtUnixMillis: at}
|
||||
if len(data) == 0 {
|
||||
return interaction.IngressEnvelope{ID: id, Modality: modality, SourceRef: sourceRef, DeviceRef: deviceRef, LocationRef: locationRef, PrincipalID: principalID, Degraded: true, RecordedAtUnixMillis: at}, artifact.MediaArtifact{}
|
||||
}
|
||||
return interaction.IngressEnvelope{ID: id, Modality: modality, SourceRef: sourceRef, DeviceRef: deviceRef, LocationRef: locationRef, PrincipalID: principalID, ArtifactRef: string(artifactValue.ID), ObservationRef: response.EvidenceRef, ProviderRef: response.Provider, ModelRef: response.Model, ModelVersion: response.ModelVersion, ConfidenceMillis: response.ConfidenceMillis, RecordedAtUnixMillis: at}, artifactValue
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package multimodal_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/adapters/multimodal"
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
"git.cin.su/m/Assistant/contracts/interaction"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIngressNormalizesMediaArtifactAndObservationProvenance(t *testing.T) {
|
||||
e, a := multimodal.NormalizeIngress("ingress-1", interaction.IngressImage, "camera:street", "device:cam", "world:street", "principal", "image/png", []byte("image"), capability.MultimodalResponse{Provider: "local", Model: "vision", ModelVersion: "1", EvidenceRef: "observation-1", ConfidenceMillis: 800}, 1)
|
||||
if !e.Valid() || !a.Valid() || e.ArtifactRef == "" || e.ObservationRef != "observation-1" || e.DeviceRef == "" || e.LocationRef == "" {
|
||||
t.Fatalf("envelope=%+v artifact=%+v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnavailableIngressDegradesWithoutFabricatingObservation(t *testing.T) {
|
||||
e, a := multimodal.NormalizeIngress("ingress-1", interaction.IngressAudio, "mic", "device:mic", "", "principal", "audio/wav", nil, capability.MultimodalResponse{}, 1)
|
||||
if !e.Degraded || e.ObservationRef != "" || a.Valid() {
|
||||
t.Fatalf("envelope=%+v artifact=%+v", e, a)
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,121 @@ var (
|
||||
ErrDevelopmentCandidateConflict = errors.New("development candidate conflict")
|
||||
)
|
||||
|
||||
func (r *WorkRepository) SaveInvestigation(ctx context.Context, value work.Investigation, expectedRevision int64) error {
|
||||
if !value.Valid() || expectedRevision < 0 {
|
||||
return errors.New("invalid investigation revision")
|
||||
}
|
||||
attempts := make([]string, 0, len(value.AttemptRefs))
|
||||
for _, item := range value.AttemptRefs {
|
||||
attempts = append(attempts, string(item))
|
||||
}
|
||||
hypotheses := make([]string, 0, len(value.HypothesisRefs))
|
||||
for _, item := range value.HypothesisRefs {
|
||||
hypotheses = append(hypotheses, string(item))
|
||||
}
|
||||
findings := make([]string, 0, len(value.FindingRefs))
|
||||
for _, item := range value.FindingRefs {
|
||||
findings = append(findings, string(item))
|
||||
}
|
||||
blockers := append([]string(nil), value.Blockers...)
|
||||
checkpoints := make([]string, 0, len(value.CheckpointRefs))
|
||||
for _, item := range value.CheckpointRefs {
|
||||
checkpoints = append(checkpoints, string(item))
|
||||
}
|
||||
if expectedRevision == 0 {
|
||||
result, err := r.pool.Exec(ctx, `INSERT INTO work.long_running_work(investigation_id,objective,hypothesis_refs,attempt_refs,finding_refs,blockers,checkpoint_refs,next_step,state,revision,time_budget_millis,cost_budget_micros,max_attempts,recorded_at_unix_millis) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) ON CONFLICT(investigation_id) DO NOTHING`, value.ID, value.Objective, hypotheses, attempts, findings, blockers, checkpoints, value.NextStep, value.State, value.Revision, value.TimeBudgetMillis, value.CostBudgetMicros, value.MaxAttempts, value.RecordedAtUnixMillis)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.RowsAffected() != 1 {
|
||||
return errors.New("investigation already exists")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
result, err := r.pool.Exec(ctx, `UPDATE work.long_running_work SET objective=$2,hypothesis_refs=$3,attempt_refs=$4,finding_refs=$5,blockers=$6,checkpoint_refs=$7,next_step=$8,state=$9,revision=$10,time_budget_millis=$11,cost_budget_micros=$12,max_attempts=$13,recorded_at_unix_millis=$14 WHERE investigation_id=$1 AND revision=$15`, value.ID, value.Objective, hypotheses, attempts, findings, blockers, checkpoints, value.NextStep, value.State, value.Revision, value.TimeBudgetMillis, value.CostBudgetMicros, value.MaxAttempts, value.RecordedAtUnixMillis, expectedRevision)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.RowsAffected() != 1 {
|
||||
return errors.New("investigation revision conflict")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *WorkRepository) LoadInvestigation(ctx context.Context, id work.LongRunningWorkID) (work.Investigation, error) {
|
||||
var value work.Investigation
|
||||
var hypotheses, attempts, findings, blockers, checkpoints []string
|
||||
err := r.pool.QueryRow(ctx, `SELECT investigation_id,objective,hypothesis_refs,attempt_refs,finding_refs,blockers,checkpoint_refs,next_step,state,revision,time_budget_millis,cost_budget_micros,max_attempts,recorded_at_unix_millis FROM work.long_running_work WHERE investigation_id=$1`, id).Scan(&value.ID, &value.Objective, &hypotheses, &attempts, &findings, &blockers, &checkpoints, &value.NextStep, &value.State, &value.Revision, &value.TimeBudgetMillis, &value.CostBudgetMicros, &value.MaxAttempts, &value.RecordedAtUnixMillis)
|
||||
if err != nil {
|
||||
return work.Investigation{}, fmt.Errorf("load investigation: %w", err)
|
||||
}
|
||||
for _, item := range hypotheses {
|
||||
value.HypothesisRefs = append(value.HypothesisRefs, work.EvidenceRef(item))
|
||||
}
|
||||
for _, item := range attempts {
|
||||
value.AttemptRefs = append(value.AttemptRefs, work.AgentRunID(item))
|
||||
}
|
||||
for _, item := range findings {
|
||||
value.FindingRefs = append(value.FindingRefs, work.EvidenceRef(item))
|
||||
}
|
||||
value.Blockers = append(value.Blockers, blockers...)
|
||||
for _, item := range checkpoints {
|
||||
value.CheckpointRefs = append(value.CheckpointRefs, work.AgentCheckpointID(item))
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// SaveInvestigationCheckpoint atomically records the checkpoint and marks its
|
||||
// attempts verified. The unique key makes retries idempotent.
|
||||
func (r *WorkRepository) SaveInvestigationCheckpoint(ctx context.Context, checkpoint work.InvestigationCheckpoint) error {
|
||||
if !checkpoint.Valid() {
|
||||
return errors.New("invalid investigation checkpoint")
|
||||
}
|
||||
tx, err := r.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
if _, err = tx.Exec(ctx, `INSERT INTO work.investigation_checkpoints(checkpoint_id,investigation_id,completed_attempt_refs,completed_finding_refs,next_step,recorded_at_unix_millis) VALUES($1,$2,$3,$4,$5,$6) ON CONFLICT(checkpoint_id) DO NOTHING`, checkpoint.ID, checkpoint.InvestigationID, stringIDs(checkpoint.CompletedAttemptRefs), stringEvidence(checkpoint.CompletedFindingRefs), checkpoint.NextStep, checkpoint.RecordedAtUnixMillis); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, attempt := range checkpoint.CompletedAttemptRefs {
|
||||
if _, err = tx.Exec(ctx, `INSERT INTO work.investigation_attempts(investigation_id,attempt_ref,verified_at_unix_millis) VALUES($1,$2,$3) ON CONFLICT(investigation_id,attempt_ref) DO NOTHING`, checkpoint.InvestigationID, attempt, checkpoint.RecordedAtUnixMillis); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
func (r *WorkRepository) UnverifiedInvestigationAttempts(ctx context.Context, id work.LongRunningWorkID, candidates []work.AgentRunID) ([]work.AgentRunID, error) {
|
||||
result := make([]work.AgentRunID, 0, len(candidates))
|
||||
for _, candidate := range candidates {
|
||||
var exists bool
|
||||
if err := r.pool.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM work.investigation_attempts WHERE investigation_id=$1 AND attempt_ref=$2)`, id, candidate).Scan(&exists); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
result = append(result, candidate)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func stringIDs(values []work.AgentRunID) []string {
|
||||
result := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
result = append(result, string(value))
|
||||
}
|
||||
return result
|
||||
}
|
||||
func stringEvidence(values []work.EvidenceRef) []string {
|
||||
result := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
result = append(result, string(value))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func OpenWork(ctx context.Context, config ContinuityConfig) (*WorkRepository, error) {
|
||||
pool, err := openPool(ctx, config)
|
||||
if err != nil {
|
||||
|
||||
@@ -533,7 +533,7 @@ func main() {
|
||||
log.Printf("interaction principal history loaded: %d utterances", len(recentUserHistory))
|
||||
}
|
||||
conversationHistory = append(conversationHistory, recentUserHistory...)
|
||||
personalMemory := interactiondomain.CompilePersonalMemory(conversationHistory)
|
||||
gen2Context := interactiondomain.BuildGen2Context(conversationHistory)
|
||||
if !creativeUserRequest && locationReply == "" {
|
||||
if continuation := interactiondomain.ContinueLocalSearch(effectiveTaskMessage, conversationHistory); continuation != "" {
|
||||
continuationMessage = continuation
|
||||
@@ -829,7 +829,7 @@ func main() {
|
||||
reply, backed := renderResearchReply(searchQuery, result, researchErr)
|
||||
if !backed && cognitiveAvailable {
|
||||
answerCtx, cancelAnswer := context.WithTimeout(r.Context(), 12*time.Second)
|
||||
answer, answerErr := cognitiveClient.AnswerStableKnowledge(answerCtx, session, part, personalMemory.ContextText(), cognitive.TaskMeaning{
|
||||
answer, answerErr := cognitiveClient.AnswerStableKnowledge(answerCtx, session, part, gen2Context.Text, cognitive.TaskMeaning{
|
||||
SpeechAct: string(semantic.Interpretation.SpeechAct), CapabilityHints: toCapabilityKinds(semantic.CapabilityHints), RequestedOutcome: semantic.Interpretation.RequestedOutcome, InformationRequestKind: semantic.InformationRequestKind, ConfidenceMillis: semantic.Interpretation.ConfidenceMillis,
|
||||
})
|
||||
cancelAnswer()
|
||||
@@ -869,7 +869,7 @@ func main() {
|
||||
canGenerateTextReply := shouldGenerateNonMutatingTextDirective(semantic, routePlan, helpPath, creativeRequest) && planningReply == deterministicReply
|
||||
if (planningReply == "" && needsCognition && semantic.Interpretation.SpeechAct == interaction.SpeechActQuestion || canGenerateTextReply) && !hasChatRoute(routePlan, chatRouteResearch) && !creativeRequest {
|
||||
answerCtx, cancelAnswer := context.WithTimeout(r.Context(), 12*time.Second)
|
||||
answer, answerErr := cognitiveClient.AnswerStableKnowledge(answerCtx, session, effectiveTaskMessage, personalMemory.ContextText(), cognitive.TaskMeaning{
|
||||
answer, answerErr := cognitiveClient.AnswerStableKnowledge(answerCtx, session, effectiveTaskMessage, gen2Context.Text, cognitive.TaskMeaning{
|
||||
SpeechAct: string(semantic.Interpretation.SpeechAct), CapabilityHints: toCapabilityKinds(semantic.CapabilityHints), RequestedOutcome: semantic.Interpretation.RequestedOutcome, InformationRequestKind: semantic.InformationRequestKind, ConfidenceMillis: semantic.Interpretation.ConfidenceMillis,
|
||||
})
|
||||
cancelAnswer()
|
||||
@@ -992,7 +992,7 @@ func main() {
|
||||
}
|
||||
if strings.TrimSpace(planningReply) != "" {
|
||||
reviewContext, cancelReview := context.WithTimeout(r.Context(), 12*time.Second)
|
||||
reviewedReply, reviewErr := cognitiveClient.ReviewResponse(reviewContext, session, utterance.Content, planningReply, personalMemory.ContextText(), cognitive.TaskMeaning{
|
||||
reviewedReply, reviewErr := cognitiveClient.ReviewResponse(reviewContext, session, utterance.Content, planningReply, gen2Context.Text, cognitive.TaskMeaning{
|
||||
SpeechAct: string(semantic.Interpretation.SpeechAct),
|
||||
CapabilityHints: toCapabilityKinds(semantic.CapabilityHints),
|
||||
RequestedOutcome: semantic.Interpretation.RequestedOutcome,
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import "net/http"
|
||||
|
||||
func main() {
|
||||
_ = http.ListenAndServe(":8088", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("lifeboat-ready\n"))
|
||||
}))
|
||||
}
|
||||
@@ -5,6 +5,18 @@ import "git.cin.su/m/Assistant/platform/ids"
|
||||
import "errors"
|
||||
|
||||
type ArtifactID ids.ID
|
||||
type MediaArtifact struct {
|
||||
ID ArtifactID
|
||||
ContentType string
|
||||
SizeBytes int64
|
||||
ContentDigest string
|
||||
SourceRef string
|
||||
RecordedAtUnixMillis int64
|
||||
}
|
||||
|
||||
func (a MediaArtifact) Valid() bool {
|
||||
return a.ID != "" && a.ContentType != "" && a.SizeBytes > 0 && a.ContentDigest != "" && a.SourceRef != "" && a.RecordedAtUnixMillis > 0
|
||||
}
|
||||
|
||||
type AgentArtifactAttestation struct {
|
||||
ArtifactID ArtifactID
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package capability
|
||||
|
||||
type IntelligencePath string
|
||||
|
||||
const (
|
||||
PathRule IntelligencePath = "RULE"
|
||||
PathReflex IntelligencePath = "REFLEX"
|
||||
PathSkill IntelligencePath = "SKILL"
|
||||
PathSmallLocal IntelligencePath = "SMALL_LOCAL"
|
||||
PathStrongLocal IntelligencePath = "STRONG_LOCAL"
|
||||
PathExternal IntelligencePath = "EXTERNAL"
|
||||
PathResearch IntelligencePath = "RESEARCH"
|
||||
PathTeam IntelligencePath = "TEAM"
|
||||
)
|
||||
|
||||
type StrategyRequest struct {
|
||||
RequiredQuality int64
|
||||
PreferLowLatency bool
|
||||
CostBudgetMicros int64
|
||||
RiskClass string
|
||||
FreshnessRequired bool
|
||||
CostOfError int64
|
||||
}
|
||||
type StrategyDecision struct {
|
||||
Path IntelligencePath
|
||||
Reason string
|
||||
Escalate bool
|
||||
}
|
||||
|
||||
func (r StrategyRequest) Select() StrategyDecision {
|
||||
if r.RiskClass == "HIGH" || r.CostOfError >= 800 {
|
||||
return StrategyDecision{Path: PathStrongLocal, Reason: "high consequence requires deeper reasoning", Escalate: true}
|
||||
}
|
||||
if r.RequiredQuality <= 500 && r.CostBudgetMicros > 0 {
|
||||
return StrategyDecision{Path: PathRule, Reason: "bounded low-risk path"}
|
||||
}
|
||||
if r.PreferLowLatency {
|
||||
return StrategyDecision{Path: PathReflex, Reason: "latency preference with later escalation"}
|
||||
}
|
||||
return StrategyDecision{Path: PathSkill, Reason: "validated reusable strategy"}
|
||||
}
|
||||
@@ -15,11 +15,15 @@ const (
|
||||
)
|
||||
|
||||
type MultimodalResponse struct {
|
||||
Operation MultimodalOperation `json:"operation"`
|
||||
Text string `json:"text"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
ObservedAt int64 `json:"observed_at_unix_millis"`
|
||||
ConfidenceNote string `json:"confidence_note,omitempty"`
|
||||
ContentType string `json:"content_type,omitempty"`
|
||||
DataBase64 string `json:"data_base64,omitempty"`
|
||||
Operation MultimodalOperation `json:"operation"`
|
||||
Text string `json:"text"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
ObservedAt int64 `json:"observed_at_unix_millis"`
|
||||
ConfidenceNote string `json:"confidence_note,omitempty"`
|
||||
ContentType string `json:"content_type,omitempty"`
|
||||
DataBase64 string `json:"data_base64,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
ModelVersion string `json:"model_version,omitempty"`
|
||||
ConfidenceMillis int64 `json:"confidence_millis,omitempty"`
|
||||
EvidenceRef string `json:"evidence_ref,omitempty"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package capability
|
||||
|
||||
type ReflexID string
|
||||
type ReflexLifecycle string
|
||||
type ReflexKind string
|
||||
|
||||
const (
|
||||
ReflexCandidate ReflexLifecycle = "CANDIDATE"
|
||||
ReflexValidating ReflexLifecycle = "VALIDATING"
|
||||
ReflexActive ReflexLifecycle = "ACTIVE"
|
||||
ReflexDegraded ReflexLifecycle = "DEGRADED"
|
||||
ReflexStale ReflexLifecycle = "STALE"
|
||||
ReflexSuspended ReflexLifecycle = "SUSPENDED"
|
||||
ReflexRetired ReflexLifecycle = "RETIRED"
|
||||
ReflexDeterministicCode ReflexKind = "DETERMINISTIC_CODE"
|
||||
ReflexRule ReflexKind = "RULE"
|
||||
ReflexWorkflow ReflexKind = "WORKFLOW"
|
||||
ReflexQueryTemplate ReflexKind = "QUERY_TEMPLATE"
|
||||
ReflexSmallModelChain ReflexKind = "SMALL_MODEL_CHAIN"
|
||||
ReflexBoundedAdapter ReflexKind = "BOUNDED_ADAPTER_OPERATION"
|
||||
)
|
||||
|
||||
type ShadowPolicy struct {
|
||||
Enabled bool
|
||||
SampleRateMillis int64
|
||||
ComparatorRef string
|
||||
MaxShadowCostMicros *int64
|
||||
EligibleErrorCostClasses []string
|
||||
}
|
||||
type RevalidationPolicy struct {
|
||||
MaxAgeMillis *int64
|
||||
RevalidateOnDependencyRevisionChange bool
|
||||
RevalidateOnWorldVersionChange bool
|
||||
RevalidateAfterFailureCount *int64
|
||||
RevalidateAfterCorrectionCount *int64
|
||||
SampleAuditRateMillis *int64
|
||||
}
|
||||
type Reflex struct {
|
||||
ID ReflexID
|
||||
PrincipalID string
|
||||
SourceSkillRef SkillID
|
||||
CreatedAt int64
|
||||
}
|
||||
type ReflexRevision struct {
|
||||
ReflexID ReflexID
|
||||
Revision int64
|
||||
SourceSkillRevision int64
|
||||
Kind ReflexKind
|
||||
Applicability ApplicabilitySpec
|
||||
ImplementationRef string
|
||||
ImplementationVersion string
|
||||
ArtifactDigest string
|
||||
DependencyRefs []string
|
||||
ValidationEvidenceRefs []SkillEvidenceRef
|
||||
KnownFailureExperienceRefs []SkillEvidenceRef
|
||||
Shadow ShadowPolicy
|
||||
Revalidation RevalidationPolicy
|
||||
LastValidatedAt int64
|
||||
Lifecycle ReflexLifecycle
|
||||
RecordedAt int64
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package capability
|
||||
|
||||
type SkillID string
|
||||
type SkillRevision struct {
|
||||
SkillID SkillID
|
||||
Revision int64
|
||||
Description string
|
||||
Applicability ApplicabilitySpec
|
||||
ImplementationRef string
|
||||
ImplementationVersion string
|
||||
DependencyRefs []string
|
||||
SupportingExperienceRefs []SkillEvidenceRef
|
||||
KnownFailureExperienceRefs []SkillEvidenceRef
|
||||
ValidationEvidenceRefs []SkillEvidenceRef
|
||||
QualityAssessmentRefs []string
|
||||
EnvironmentVersionConstraints []string
|
||||
LastValidatedAt int64
|
||||
Lifecycle SkillLifecycleStatus
|
||||
SupersedesRevision int64
|
||||
RecordedAt int64
|
||||
}
|
||||
type ApplicabilitySpec struct {
|
||||
RequiredContextBindings []string
|
||||
PreferredContextBindings []string
|
||||
ExcludedContextBindings []string
|
||||
RequiredWorldKinds []string
|
||||
VersionConstraints []string
|
||||
InputConditions []string
|
||||
MinimumEvidenceFreshness string
|
||||
}
|
||||
type Skill struct {
|
||||
ID SkillID
|
||||
PrincipalID string
|
||||
Name string
|
||||
CreatedAt int64
|
||||
}
|
||||
@@ -11,14 +11,24 @@ import (
|
||||
var ErrInvalidContextBudget = errors.New("context budget must be positive")
|
||||
|
||||
type ContextCandidate struct {
|
||||
Excerpt ContextExcerpt
|
||||
Relevance int
|
||||
Excerpt ContextExcerpt
|
||||
Relevance int
|
||||
Lane string
|
||||
ContextScopeRef string
|
||||
FreshnessState string
|
||||
Negative bool
|
||||
}
|
||||
|
||||
type ContextCompileInput struct {
|
||||
Candidates []ContextCandidate
|
||||
DataPolicy capability.DataPolicy
|
||||
MaxBytes int
|
||||
Candidates []ContextCandidate
|
||||
DataPolicy capability.DataPolicy
|
||||
MaxBytes int
|
||||
TaskRef string
|
||||
FocusFrameRef string
|
||||
ContextScopeRef string
|
||||
FreshnessRequirement string
|
||||
RiskClass string
|
||||
TokenBudget int
|
||||
}
|
||||
|
||||
type ContextOmission struct {
|
||||
@@ -30,6 +40,24 @@ type ContextBundle struct {
|
||||
Excerpts []ContextExcerpt
|
||||
Omissions []ContextOmission
|
||||
Bytes int
|
||||
Trace ContextAssemblyTrace
|
||||
}
|
||||
|
||||
type ContextAssemblyTrace struct {
|
||||
Included []ContextExcerptID
|
||||
Excluded []ContextOmission
|
||||
Coverage string
|
||||
CompilerVersion string
|
||||
}
|
||||
|
||||
type IterativeRetrievalRequest struct {
|
||||
Query string
|
||||
RemainingBytes int
|
||||
Pass int
|
||||
}
|
||||
type IterativeRetrievalResponse struct {
|
||||
Candidates []ContextCandidate
|
||||
Exhausted bool
|
||||
}
|
||||
|
||||
// CompileContext deterministically selects permitted, relevant excerpts for a
|
||||
@@ -54,6 +82,10 @@ func CompileContext(input ContextCompileInput) (ContextBundle, error) {
|
||||
continue
|
||||
}
|
||||
seen[excerpt.ID] = struct{}{}
|
||||
if input.ContextScopeRef != "" && candidate.ContextScopeRef != input.ContextScopeRef {
|
||||
bundle.Omissions = append(bundle.Omissions, ContextOmission{ID: excerpt.ID, Reason: "context_scope"})
|
||||
continue
|
||||
}
|
||||
reason := permittedContextReason(excerpt, input.DataPolicy)
|
||||
if reason != "" {
|
||||
bundle.Omissions = append(bundle.Omissions, ContextOmission{ID: excerpt.ID, Reason: reason})
|
||||
@@ -70,7 +102,11 @@ func CompileContext(input ContextCompileInput) (ContextBundle, error) {
|
||||
}
|
||||
bundle.Excerpts = append(bundle.Excerpts, excerpt)
|
||||
bundle.Bytes += bytes
|
||||
bundle.Trace.Included = append(bundle.Trace.Included, excerpt.ID)
|
||||
}
|
||||
bundle.Trace.Excluded = append([]ContextOmission(nil), bundle.Omissions...)
|
||||
bundle.Trace.CompilerVersion = "context-compiler-v2-additive"
|
||||
bundle.Trace.Coverage = "BOUNDED"
|
||||
return bundle, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package cognitive_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
"git.cin.su/m/Assistant/contracts/cognitive"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestContextCompilerV2TraceAndBudgetRemainDeterministic(t *testing.T) {
|
||||
b, err := cognitive.CompileContext(cognitive.ContextCompileInput{MaxBytes: 5, DataPolicy: capability.DataPolicyLocalOnly, TaskRef: "task", FocusFrameRef: "frame", ContextScopeRef: "company-a", Candidates: []cognitive.ContextCandidate{{Excerpt: cognitive.ContextExcerpt{ID: "a", Text: "123", Sensitivity: cognitive.SensitivityPrivateAllowed}, ContextScopeRef: "company-a", Relevance: 10}, {Excerpt: cognitive.ContextExcerpt{ID: "b", Text: "123456", Sensitivity: cognitive.SensitivityPrivateAllowed}, ContextScopeRef: "company-b", Relevance: 100}}})
|
||||
if err != nil || len(b.Excerpts) != 1 || len(b.Trace.Excluded) != 1 {
|
||||
t.Fatalf("bundle=%#v err=%v", b, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package commit
|
||||
|
||||
type ExecutorResult string
|
||||
type AmbiguityState string
|
||||
|
||||
const (
|
||||
ExecutorAccepted ExecutorResult = "ACCEPTED"
|
||||
ExecutorReportedSuccess ExecutorResult = "REPORTED_SUCCESS"
|
||||
ExecutorReportedFailure ExecutorResult = "REPORTED_FAILURE"
|
||||
ExecutorNoResponse ExecutorResult = "NO_RESPONSE"
|
||||
ExecutorAmbiguous ExecutorResult = "AMBIGUOUS"
|
||||
)
|
||||
|
||||
type ExecutionReceipt struct {
|
||||
ID string
|
||||
ActionAttemptRef ActionAttemptID
|
||||
ExecutorImplementationRef string
|
||||
ExternalOperationRef string
|
||||
ExternalRequestID string
|
||||
RequestDigest string
|
||||
ResponseDigest string
|
||||
DispatchAtUnixMillis int64
|
||||
CompletedAtUnixMillis *int64
|
||||
ExecutorResult ExecutorResult
|
||||
Ambiguity AmbiguityState
|
||||
ReconciliationKey string
|
||||
RawArtifactRef string
|
||||
}
|
||||
|
||||
func (r ExecutionReceipt) Valid() bool {
|
||||
return r.ID != "" && r.ActionAttemptRef != "" && r.ExecutorImplementationRef != "" && r.RequestDigest != "" && r.ExecutorResult != "" && r.ReconciliationKey != "" && r.DispatchAtUnixMillis > 0
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.cin.su/m/Assistant/contracts/secrets"
|
||||
"git.cin.su/m/Assistant/platform/ids"
|
||||
)
|
||||
|
||||
type SettingDefinitionID ids.StableID
|
||||
type SettingAssignmentID ids.StableID
|
||||
type ConfigurationRevision ids.Revision
|
||||
type ValueType string
|
||||
type ResolutionMode string
|
||||
type SettingLifecycle string
|
||||
type SettingSource string
|
||||
|
||||
const (
|
||||
ValueString ValueType = "STRING"
|
||||
ValueInteger ValueType = "INTEGER"
|
||||
ValueBoolean ValueType = "BOOLEAN"
|
||||
ValueSecretRef ValueType = "SECRET_REF"
|
||||
ResolutionMostSpecific ResolutionMode = "MOST_SPECIFIC_WINS"
|
||||
ResolutionFirstHuman ResolutionMode = "FIRST_EXPLICIT_HUMAN_WINS"
|
||||
ResolutionMergeList ResolutionMode = "MERGE_LIST"
|
||||
SettingActive SettingLifecycle = "ACTIVE"
|
||||
SettingDeprecated SettingLifecycle = "DEPRECATED"
|
||||
SettingRetired SettingLifecycle = "RETIRED"
|
||||
SourceHumanDirective SettingSource = "HUMAN_DIRECTIVE"
|
||||
SourceMigrated SettingSource = "MIGRATED_CONFIG"
|
||||
SourceSkillDefault SettingSource = "SKILL_DEFAULT"
|
||||
SourceSystemDefault SettingSource = "SYSTEM_DEFAULT"
|
||||
SourceGovernedChange SettingSource = "GOVERNED_CHANGE"
|
||||
)
|
||||
|
||||
type SettingDefinition struct {
|
||||
ID SettingDefinitionID
|
||||
OwnerNamespace string
|
||||
StableKey string
|
||||
Revision ConfigurationRevision
|
||||
ValueType ValueType
|
||||
DefaultValue string
|
||||
Sensitivity string
|
||||
Lifecycle SettingLifecycle
|
||||
RecordedAtUnixMillis int64
|
||||
}
|
||||
type ScopeSelector struct {
|
||||
PrincipalRef string
|
||||
RequiredContextBindings []string
|
||||
ActivityRef string
|
||||
SkillRef string
|
||||
TaskRef string
|
||||
SessionRef string
|
||||
TemporaryOverride bool
|
||||
}
|
||||
type SettingAssignment struct {
|
||||
ID SettingAssignmentID
|
||||
PrincipalID string
|
||||
DefinitionRef string
|
||||
DefinitionRevision ConfigurationRevision
|
||||
Revision ConfigurationRevision
|
||||
Selector ScopeSelector
|
||||
TypedValue string
|
||||
SecretRef *secrets.CredentialRef
|
||||
EffectiveFromUnixMillis int64
|
||||
EffectiveUntilUnixMillis *int64
|
||||
Source SettingSource
|
||||
SourceRef string
|
||||
Lifecycle SettingLifecycle
|
||||
RecordedAtUnixMillis int64
|
||||
}
|
||||
type EffectiveSetting struct {
|
||||
DefinitionRef string
|
||||
Value string
|
||||
WinningAssignmentRef string
|
||||
ResolutionTrace []string
|
||||
}
|
||||
type BootstrapConfig struct {
|
||||
NodeIdentity string
|
||||
ControlDatabaseLocator string
|
||||
RootSecretResolverLocator string
|
||||
RecoveryLocator string
|
||||
}
|
||||
|
||||
// ConversationalConfigurationCommand is an intent, not an assignment. Only
|
||||
// Configuration may turn a SET command into durable state.
|
||||
|
||||
func (d SettingDefinition) Validate() error {
|
||||
if d.ID == "" || d.OwnerNamespace == "" || d.StableKey == "" || !ids.Revision(d.Revision).Valid() || d.ValueType == "" || d.Lifecycle == "" || d.RecordedAtUnixMillis < 1 {
|
||||
return fmt.Errorf("invalid setting definition")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (a SettingAssignment) Validate() error {
|
||||
if a.ID == "" || a.PrincipalID == "" || a.DefinitionRef == "" || !ids.Revision(a.Revision).Valid() || a.Source == "" || a.Lifecycle == "" || a.RecordedAtUnixMillis < 1 {
|
||||
return fmt.Errorf("invalid setting assignment")
|
||||
}
|
||||
if a.SecretRef != nil && a.TypedValue != "" {
|
||||
return fmt.Errorf("secret assignment must not contain typed value")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package epistemics
|
||||
|
||||
type WorldOutcomeState string
|
||||
|
||||
const (
|
||||
OutcomeVerifiedSuccess WorldOutcomeState = "VERIFIED_SUCCESS"
|
||||
OutcomeVerifiedPartial WorldOutcomeState = "VERIFIED_PARTIAL"
|
||||
OutcomeVerifiedFailure WorldOutcomeState = "VERIFIED_FAILURE"
|
||||
OutcomeUnverified WorldOutcomeState = "UNVERIFIED"
|
||||
OutcomeUnknownState WorldOutcomeState = "UNKNOWN"
|
||||
OutcomeConflicted WorldOutcomeState = "CONFLICTED"
|
||||
)
|
||||
|
||||
type WorldOutcomeAssessment struct {
|
||||
ID string
|
||||
PrincipalID string
|
||||
ActionAttemptRef string
|
||||
ExecutionReceiptRefs []string
|
||||
ExpectedEffectRef string
|
||||
ObservationRefs []ObservationID
|
||||
ClaimRefs []ClaimID
|
||||
State WorldOutcomeState
|
||||
ConfidenceMillis int64
|
||||
EvidenceRefs []EvidenceRef
|
||||
AssessedAt int64
|
||||
}
|
||||
|
||||
func (a WorldOutcomeAssessment) Valid() bool {
|
||||
return a.ID != "" && a.PrincipalID != "" && a.ActionAttemptRef != "" && a.State != "" && a.ConfidenceMillis >= 0 && a.ConfidenceMillis <= 1000 && a.AssessedAt > 0
|
||||
}
|
||||
@@ -17,23 +17,27 @@ type CoverageState string
|
||||
type ConflictState string
|
||||
type EvidenceKind string
|
||||
type ResearchFindingState string
|
||||
type ReliabilityState string
|
||||
|
||||
const (
|
||||
FreshnessCurrent FreshnessState = "CURRENT"
|
||||
FreshnessStale FreshnessState = "STALE"
|
||||
FreshnessUnknown FreshnessState = "UNKNOWN"
|
||||
CoverageComplete CoverageState = "COMPLETE"
|
||||
CoveragePartial CoverageState = "PARTIAL"
|
||||
CoverageUnknown CoverageState = "UNKNOWN"
|
||||
ConflictNone ConflictState = "NONE"
|
||||
ConflictPresent ConflictState = "PRESENT"
|
||||
ConflictUnknown ConflictState = "UNKNOWN"
|
||||
EvidenceObserved EvidenceKind = "OBSERVED"
|
||||
EvidenceAssumed EvidenceKind = "ASSUMED"
|
||||
FindingSupported ResearchFindingState = "SUPPORTED"
|
||||
FindingStale ResearchFindingState = "STALE"
|
||||
FindingConflicted ResearchFindingState = "CONFLICTED"
|
||||
FindingUnknown ResearchFindingState = "UNKNOWN"
|
||||
FreshnessCurrent FreshnessState = "CURRENT"
|
||||
FreshnessStale FreshnessState = "STALE"
|
||||
FreshnessUnknown FreshnessState = "UNKNOWN"
|
||||
CoverageComplete CoverageState = "COMPLETE"
|
||||
CoveragePartial CoverageState = "PARTIAL"
|
||||
CoverageUnknown CoverageState = "UNKNOWN"
|
||||
ConflictNone ConflictState = "NONE"
|
||||
ConflictPresent ConflictState = "PRESENT"
|
||||
ConflictUnknown ConflictState = "UNKNOWN"
|
||||
EvidenceObserved EvidenceKind = "OBSERVED"
|
||||
EvidenceAssumed EvidenceKind = "ASSUMED"
|
||||
FindingSupported ResearchFindingState = "SUPPORTED"
|
||||
FindingStale ResearchFindingState = "STALE"
|
||||
FindingConflicted ResearchFindingState = "CONFLICTED"
|
||||
FindingUnknown ResearchFindingState = "UNKNOWN"
|
||||
ReliabilityCurrent ReliabilityState = "CURRENT"
|
||||
ReliabilityAging ReliabilityState = "AGING"
|
||||
ReliabilityUnknown ReliabilityState = "UNKNOWN"
|
||||
)
|
||||
|
||||
type ResearchPlan struct {
|
||||
@@ -52,6 +56,21 @@ type Source struct {
|
||||
Publisher string
|
||||
}
|
||||
|
||||
type SourceReliabilityAssessment struct {
|
||||
SourceRef string
|
||||
DomainRef string
|
||||
Freshness FreshnessState
|
||||
HistoryScoreMillis int64
|
||||
ReliabilityScoreMillis int64
|
||||
State ReliabilityState
|
||||
EvidenceRefs []string
|
||||
AssessedAtUnixMillis int64
|
||||
}
|
||||
|
||||
func (a SourceReliabilityAssessment) Valid() bool {
|
||||
return a.SourceRef != "" && a.DomainRef != "" && a.Freshness != "" && a.HistoryScoreMillis >= 0 && a.HistoryScoreMillis <= 1000 && a.ReliabilityScoreMillis >= 0 && a.ReliabilityScoreMillis <= 1000 && a.State != "" && a.AssessedAtUnixMillis > 0
|
||||
}
|
||||
|
||||
type SourceSnapshot struct {
|
||||
ID SourceSnapshotID
|
||||
SourceID SourceID
|
||||
|
||||
@@ -6,6 +6,30 @@ type ActorID ids.ID
|
||||
type PrincipalID ids.ID
|
||||
type IdentityID ids.ID
|
||||
|
||||
type DataRealmKind string
|
||||
|
||||
const (
|
||||
DataRealmPersonal DataRealmKind = "PERSONAL"
|
||||
DataRealmShared DataRealmKind = "SHARED"
|
||||
DataRealmPublic DataRealmKind = "PUBLIC"
|
||||
)
|
||||
|
||||
// PrincipalDataRealm is an identity-owned access boundary. It does not make
|
||||
// a speaker or World person referent an authenticated principal.
|
||||
type PrincipalDataRealm struct {
|
||||
ID string
|
||||
OwnerPrincipalID PrincipalID
|
||||
Kind DataRealmKind
|
||||
MemberPrincipalIDs []PrincipalID
|
||||
Shared bool
|
||||
Revision int64
|
||||
RecordedAtUnixMillis int64
|
||||
}
|
||||
|
||||
func (r PrincipalDataRealm) Valid() bool {
|
||||
return r.ID != "" && r.OwnerPrincipalID != "" && (r.Kind == DataRealmPersonal || r.Kind == DataRealmShared || r.Kind == DataRealmPublic) && r.Revision > 0 && r.RecordedAtUnixMillis > 0 && (r.Kind != DataRealmShared || r.Shared)
|
||||
}
|
||||
|
||||
// PrincipalBinding keeps the operational actor separate from the interest on
|
||||
// whose behalf it acts. A Creator is not exempt from this distinction.
|
||||
type PrincipalBinding struct {
|
||||
|
||||
@@ -25,6 +25,44 @@ type SemanticApplicability string
|
||||
type SemanticCandidateSource string
|
||||
type SemanticCandidateSelectionState string
|
||||
type InformationRequestKind string
|
||||
type ConfigurationCommandKind string
|
||||
type SpeakerAttribution struct {
|
||||
SourceRef string
|
||||
PrincipalCandidate string
|
||||
ConfidenceMillis int64
|
||||
Authenticated bool
|
||||
ObservationRef string
|
||||
}
|
||||
type IngressModality string
|
||||
|
||||
const (
|
||||
IngressText IngressModality = "TEXT"
|
||||
IngressAudio IngressModality = "AUDIO"
|
||||
IngressImage IngressModality = "IMAGE"
|
||||
IngressVideo IngressModality = "VIDEO"
|
||||
IngressDevice IngressModality = "DEVICE"
|
||||
)
|
||||
|
||||
type IngressEnvelope struct {
|
||||
ID string
|
||||
Modality IngressModality
|
||||
SourceRef string
|
||||
DeviceRef string
|
||||
LocationRef string
|
||||
PrincipalID string
|
||||
ArtifactRef string
|
||||
ObservationRef string
|
||||
ProviderRef string
|
||||
ModelRef string
|
||||
ModelVersion string
|
||||
ConfidenceMillis int64
|
||||
Degraded bool
|
||||
RecordedAtUnixMillis int64
|
||||
}
|
||||
|
||||
func (e IngressEnvelope) Valid() bool {
|
||||
return e.ID != "" && e.Modality != "" && e.SourceRef != "" && e.ConfidenceMillis >= 0 && e.ConfidenceMillis <= 1000 && e.RecordedAtUnixMillis > 0 && (!e.Degraded || e.ObservationRef == "")
|
||||
}
|
||||
|
||||
const (
|
||||
ChannelWebChat Channel = "WEB_CHAT"
|
||||
@@ -75,6 +113,8 @@ const (
|
||||
InformationRequestMemoryInventory InformationRequestKind = "MEMORY_INVENTORY"
|
||||
InformationRequestTimeDifference InformationRequestKind = "TIME_DIFFERENCE"
|
||||
InformationRequestResearchFailureExplanation InformationRequestKind = "RESEARCH_FAILURE_EXPLANATION"
|
||||
ConfigurationCommandSet ConfigurationCommandKind = "SET"
|
||||
ConfigurationCommandQuery ConfigurationCommandKind = "QUERY"
|
||||
)
|
||||
|
||||
type Conversation struct {
|
||||
@@ -106,6 +146,18 @@ type OriginalHumanIntent struct {
|
||||
RecordedAtUnixMillis int64
|
||||
Text string
|
||||
}
|
||||
|
||||
// ConfigurationCommand is Interaction-owned intent. Configuration remains
|
||||
// the only owner allowed to materialise a SettingAssignment.
|
||||
type ConfigurationCommand struct {
|
||||
Kind ConfigurationCommandKind
|
||||
PrincipalID string
|
||||
DefinitionRef string
|
||||
TypedValue string
|
||||
TemporaryOverride bool
|
||||
SessionRef string
|
||||
SourceRef string
|
||||
}
|
||||
type InteractionRelation struct {
|
||||
ID InteractionRelationID
|
||||
FromUtteranceID HumanUtteranceID
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package self
|
||||
|
||||
type BenchmarkCaseRef string
|
||||
type PersonalBenchmark struct {
|
||||
ID string
|
||||
PrincipalID string
|
||||
ApprovedCaseRefs []BenchmarkCaseRef
|
||||
GoalProfile []string
|
||||
PreferenceEvidenceRefs []SemanticReference
|
||||
RecordedAt int64
|
||||
}
|
||||
type BenchmarkEvaluation struct {
|
||||
CaseRef BenchmarkCaseRef
|
||||
Path string
|
||||
QualityMillis int64
|
||||
ReliabilityMillis int64
|
||||
LatencyMillis int64
|
||||
CostMicros int64
|
||||
CorrectionCount int64
|
||||
EvidenceRefs []SemanticReference
|
||||
}
|
||||
|
||||
func (e BenchmarkEvaluation) Valid() bool {
|
||||
return e.CaseRef != "" && len(e.EvidenceRefs) > 0 && e.QualityMillis >= 0 && e.ReliabilityMillis >= 0 && e.CorrectionCount >= 0
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package self
|
||||
|
||||
import "fmt"
|
||||
|
||||
type AssessmentID string
|
||||
type LearningLedgerEntryID string
|
||||
type AssessmentState string
|
||||
|
||||
const (
|
||||
AssessmentCandidate AssessmentState = "CANDIDATE"
|
||||
AssessmentConfirmed AssessmentState = "CONFIRMED"
|
||||
AssessmentMonitoring AssessmentState = "MONITORING"
|
||||
AssessmentResolved AssessmentState = "RESOLVED"
|
||||
)
|
||||
|
||||
type CompetenceAssessment struct {
|
||||
ID AssessmentID
|
||||
PrincipalID string
|
||||
SubjectRef SemanticReference
|
||||
Revision uint64
|
||||
ContextMatcher []string
|
||||
QualityMillis int64
|
||||
ReliabilityMillis int64
|
||||
ConfidenceMillis int64
|
||||
EvidenceRefs []SemanticReference
|
||||
LastValidatedAt int64
|
||||
AssessedAt int64
|
||||
SupersedesRef *SemanticReference
|
||||
}
|
||||
type WeaknessPattern struct {
|
||||
ID AssessmentID
|
||||
PrincipalID string
|
||||
Revision uint64
|
||||
PatternKey string
|
||||
ContextMatcher []string
|
||||
FailureMode string
|
||||
DetectionEvidenceRefs []SemanticReference
|
||||
State AssessmentState
|
||||
RecordedAt int64
|
||||
}
|
||||
type DependencyAssessment struct {
|
||||
ID AssessmentID
|
||||
SubjectRef SemanticReference
|
||||
DependencyRef SemanticReference
|
||||
ImpactClass string
|
||||
CurrentEffect string
|
||||
EvidenceRefs []SemanticReference
|
||||
AssessedAt int64
|
||||
}
|
||||
type ImprovementNeed struct {
|
||||
ID AssessmentID
|
||||
PrincipalID string
|
||||
SubjectRef SemanticReference
|
||||
ProblemSummary string
|
||||
EvidenceRefs []SemanticReference
|
||||
ExpectedBenefit string
|
||||
Priority int
|
||||
State string
|
||||
CreatedAt int64
|
||||
}
|
||||
type LearningLedgerEntry struct {
|
||||
ID LearningLedgerEntryID
|
||||
PrincipalID string
|
||||
ImprovementNeedRef *SemanticReference
|
||||
ObservedProblem string
|
||||
EvidenceRefs []SemanticReference
|
||||
Diagnosis string
|
||||
Hypothesis string
|
||||
ProposedChangeRef SemanticReference
|
||||
ExpectedImprovement string
|
||||
ActualEvaluationRefs []SemanticReference
|
||||
DevelopmentGateRef *SemanticReference
|
||||
BeforeVersionRefs []SemanticReference
|
||||
AfterVersionRefs []SemanticReference
|
||||
Decision string
|
||||
SideEffects []string
|
||||
RollbackRef *SemanticReference
|
||||
RecordedAt int64
|
||||
}
|
||||
|
||||
func (a CompetenceAssessment) Validate() error {
|
||||
if a.ID == "" || a.PrincipalID == "" || !a.SubjectRef.Valid() || a.Revision < 1 || a.ConfidenceMillis < 0 || a.ConfidenceMillis > 1000 || len(a.EvidenceRefs) == 0 || a.AssessedAt < 1 {
|
||||
return fmt.Errorf("competence requires evidence")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (l LearningLedgerEntry) Validate() error {
|
||||
if l.ID == "" || l.PrincipalID == "" || l.ObservedProblem == "" || len(l.EvidenceRefs) == 0 || l.Decision == "" || l.RecordedAt < 1 {
|
||||
return fmt.Errorf("learning ledger entry is incomplete")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package work
|
||||
|
||||
type ConsequenceAssessment struct {
|
||||
ID string
|
||||
TargetRefs []string
|
||||
Impact int64
|
||||
Reversibility int64
|
||||
Uncertainty int64
|
||||
Scope int64
|
||||
Recoverability int64
|
||||
CostOfError int64
|
||||
ExpectedConsequences []string
|
||||
MitigationOptions []string
|
||||
RequiresDryRun bool
|
||||
RequiresBackup bool
|
||||
RecommendedConfirmation string
|
||||
OwnerAccepted bool
|
||||
EvidenceRefs []EvidenceRef
|
||||
RecordedAtUnixMillis int64
|
||||
}
|
||||
|
||||
func (a ConsequenceAssessment) Valid() bool {
|
||||
return a.ID != "" && len(a.TargetRefs) > 0 && a.Impact >= 0 && a.Impact <= 1000 && a.Reversibility >= 0 && a.Reversibility <= 1000 && a.Uncertainty >= 0 && a.Uncertainty <= 1000 && a.Recoverability >= 0 && a.Recoverability <= 1000 && a.RecordedAtUnixMillis > 0
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package work
|
||||
|
||||
type EvolutionRunStage string
|
||||
|
||||
const (
|
||||
EvolutionNeedRecorded EvolutionRunStage = "IMPROVEMENT_NEED"
|
||||
EvolutionCandidateBuilt EvolutionRunStage = "CANDIDATE"
|
||||
EvolutionIndependentlyEvaluated EvolutionRunStage = "INDEPENDENT_EVALUATION"
|
||||
EvolutionSandboxed EvolutionRunStage = "SANDBOX_BUILD"
|
||||
EvolutionBenchmarked EvolutionRunStage = "BENCHMARK"
|
||||
EvolutionCanaried EvolutionRunStage = "CANARY"
|
||||
EvolutionPromoted EvolutionRunStage = "PROMOTION"
|
||||
EvolutionRolledBack EvolutionRunStage = "ROLLBACK"
|
||||
)
|
||||
|
||||
type GovernedEvolutionResult struct {
|
||||
NeedRef string
|
||||
CandidateRef string
|
||||
EvaluationRef string
|
||||
Stages []EvolutionRunStage
|
||||
EvidenceRefs []EvidenceRef
|
||||
LearningLedgerOutcomeRef string
|
||||
Promoted bool
|
||||
RollbackRef string
|
||||
}
|
||||
|
||||
// GovernedEvolutionOps are explicit gates. The caller supplies existing
|
||||
// development executor/supervisor operations; this interface grants no new
|
||||
// authority and has no autonomous promotion implementation.
|
||||
type GovernedEvolutionOps struct {
|
||||
CreateCandidate func() (EvolutionCandidate, error)
|
||||
Evaluate func(EvolutionCandidate) (EvolutionEvaluation, error)
|
||||
SandboxBuild func(EvolutionCandidate) (EvidenceRef, error)
|
||||
Benchmark func(EvolutionCandidate) (EvidenceRef, bool, error)
|
||||
Canary func(EvolutionCandidate) (EvidenceRef, bool, error)
|
||||
Promote func(EvolutionCandidate, EvolutionEvaluation) (string, error)
|
||||
Rollback func(EvolutionCandidate) (string, error)
|
||||
LearningLedgerOutcome func(GovernedEvolutionResult) (string, error)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package work
|
||||
|
||||
type LongRunningWorkID string
|
||||
type LongRunningState string
|
||||
|
||||
const (
|
||||
LongRunningProposed LongRunningState = "PROPOSED"
|
||||
LongRunningRunning LongRunningState = "RUNNING"
|
||||
LongRunningPaused LongRunningState = "PAUSED"
|
||||
LongRunningBlocked LongRunningState = "BLOCKED"
|
||||
LongRunningCompleted LongRunningState = "COMPLETED"
|
||||
LongRunningExhausted LongRunningState = "EXHAUSTED"
|
||||
LongRunningCancelled LongRunningState = "CANCELLED"
|
||||
)
|
||||
|
||||
type Investigation struct {
|
||||
ID LongRunningWorkID
|
||||
Objective string
|
||||
HypothesisRefs []EvidenceRef
|
||||
AttemptRefs []AgentRunID
|
||||
FindingRefs []EvidenceRef
|
||||
Blockers []string
|
||||
CheckpointRefs []AgentCheckpointID
|
||||
NextStep string
|
||||
State LongRunningState
|
||||
Revision int64
|
||||
TimeBudgetMillis int64
|
||||
CostBudgetMicros int64
|
||||
MaxAttempts int
|
||||
RecordedAtUnixMillis int64
|
||||
}
|
||||
|
||||
// InvestigationCheckpoint is the durable boundary between attempts. A
|
||||
// checkpoint is written only after an attempt has been verified, so a worker
|
||||
// restart can resume at NextStep without replaying that attempt.
|
||||
type InvestigationCheckpoint struct {
|
||||
ID AgentCheckpointID
|
||||
InvestigationID LongRunningWorkID
|
||||
CompletedAttemptRefs []AgentRunID
|
||||
CompletedFindingRefs []EvidenceRef
|
||||
NextStep string
|
||||
RecordedAtUnixMillis int64
|
||||
}
|
||||
|
||||
func (c InvestigationCheckpoint) Valid() bool {
|
||||
return c.ID != "" && c.InvestigationID != "" && c.NextStep != "" && c.RecordedAtUnixMillis > 0
|
||||
}
|
||||
|
||||
func (w Investigation) Valid() bool {
|
||||
return w.ID != "" && w.Objective != "" && w.State != "" && w.Revision > 0 && w.TimeBudgetMillis >= 0 && w.CostBudgetMicros >= 0 && w.MaxAttempts >= 0 && w.RecordedAtUnixMillis > 0
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package work
|
||||
|
||||
type PerspectiveID string
|
||||
type PerspectiveSpec struct {
|
||||
ID PerspectiveID
|
||||
ObjectiveLens string
|
||||
FocusRef string
|
||||
RetrievalProfileRef string
|
||||
SkillRefs []string
|
||||
ToolRefs []string
|
||||
EvaluationCriteria []string
|
||||
CapabilityRequirements []string
|
||||
ExpectedOutputShape string
|
||||
Critic bool
|
||||
}
|
||||
type SpecialistAssignment struct {
|
||||
Perspective PerspectiveSpec
|
||||
SubtaskRef string
|
||||
FocusRef string
|
||||
SkillRefs []string
|
||||
ToolRefs []string
|
||||
EvidenceRefs []EvidenceRef
|
||||
}
|
||||
type TeamDecision struct {
|
||||
UseTeam bool
|
||||
Reason string
|
||||
Assignments []SpecialistAssignment
|
||||
}
|
||||
|
||||
func (p PerspectiveSpec) Valid() bool {
|
||||
return p.ID != "" && p.ObjectiveLens != "" && p.FocusRef != "" && len(p.SkillRefs) > 0 && len(p.ToolRefs) > 0 && len(p.EvaluationCriteria) > 0 && p.ExpectedOutputShape != ""
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package work
|
||||
|
||||
type EvolutionChangeLevel string
|
||||
|
||||
const (
|
||||
EvolutionKnowledge EvolutionChangeLevel = "KNOWLEDGE"
|
||||
EvolutionBehavior EvolutionChangeLevel = "BEHAVIOR"
|
||||
EvolutionCapability EvolutionChangeLevel = "CAPABILITY"
|
||||
EvolutionCore EvolutionChangeLevel = "CORE"
|
||||
)
|
||||
|
||||
type EvolutionCandidate struct {
|
||||
ID string
|
||||
ProposalRef string
|
||||
ChangeLevel EvolutionChangeLevel
|
||||
CreatorIdentity string
|
||||
BaseVersionRef string
|
||||
CandidateVersionRef string
|
||||
MustNotWorsen []string
|
||||
SandboxEvidenceRefs []EvidenceRef
|
||||
BenchmarkEvidenceRefs []EvidenceRef
|
||||
RollbackRef string
|
||||
State string
|
||||
}
|
||||
type EvolutionEvaluation struct {
|
||||
CandidateRef string
|
||||
EvaluatorIdentity string
|
||||
Approved bool
|
||||
EvidenceRefs []EvidenceRef
|
||||
MustNotWorsenPassed bool
|
||||
RecordedAtUnixMillis int64
|
||||
}
|
||||
|
||||
func (e EvolutionEvaluation) ValidFor(c EvolutionCandidate) bool {
|
||||
return e.CandidateRef == c.ID && e.EvaluatorIdentity != "" && e.EvaluatorIdentity != c.CreatorIdentity && e.Approved && e.MustNotWorsenPassed && len(e.EvidenceRefs) > 0 && e.RecordedAtUnixMillis > 0
|
||||
}
|
||||
@@ -104,6 +104,16 @@ type ValueTypeSpec struct {
|
||||
type WorldObjectID ids.StableID
|
||||
type KindDefinitionID ids.StableID
|
||||
type WorldAliasID ids.StableID
|
||||
type PersonReferent struct {
|
||||
WorldObjectRef SemanticReference
|
||||
DisplayName string
|
||||
ConfidenceMillis int64
|
||||
}
|
||||
|
||||
func (p PersonReferent) Valid() bool {
|
||||
return p.WorldObjectRef.Valid() && p.DisplayName != "" && p.ConfidenceMillis >= 0 && p.ConfidenceMillis <= 1000
|
||||
}
|
||||
|
||||
type FacetDefinitionID ids.StableID
|
||||
type RelationDefinitionID ids.StableID
|
||||
type ResolutionDecisionID ids.StableID
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
BEGIN;
|
||||
SET ROLE configuration_role;
|
||||
CREATE TABLE IF NOT EXISTS configuration.setting_definitions (setting_definition_id TEXT PRIMARY KEY, owner_namespace TEXT NOT NULL, stable_key TEXT NOT NULL, revision BIGINT NOT NULL CHECK(revision>0), value_type TEXT NOT NULL, default_value TEXT, sensitivity TEXT NOT NULL, lifecycle TEXT NOT NULL, recorded_at_unix_millis BIGINT NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS configuration.setting_assignments (setting_assignment_id TEXT PRIMARY KEY, principal_id TEXT NOT NULL, definition_ref JSONB NOT NULL, revision BIGINT NOT NULL CHECK(revision>0), selector JSONB NOT NULL, typed_value TEXT, secret_ref TEXT, effective_from_unix_millis BIGINT NOT NULL, effective_until_unix_millis BIGINT, source TEXT NOT NULL, source_ref TEXT NOT NULL, lifecycle TEXT NOT NULL, recorded_at_unix_millis BIGINT NOT NULL);
|
||||
GRANT SELECT, INSERT, UPDATE ON configuration.setting_definitions, configuration.setting_assignments TO configuration_role;
|
||||
RESET ROLE;
|
||||
COMMIT;
|
||||
@@ -0,0 +1,7 @@
|
||||
BEGIN;
|
||||
SET ROLE self_role;
|
||||
CREATE TABLE IF NOT EXISTS self.competence_assessments (assessment_id TEXT PRIMARY KEY, principal_id TEXT NOT NULL, subject_ref JSONB NOT NULL, revision BIGINT NOT NULL CHECK(revision>0), context_matcher JSONB NOT NULL, confidence_millis BIGINT NOT NULL, evidence_refs JSONB NOT NULL, assessed_at_unix_millis BIGINT NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS self.learning_ledger (entry_id TEXT PRIMARY KEY, principal_id TEXT NOT NULL, observed_problem TEXT NOT NULL, evidence_refs JSONB NOT NULL, diagnosis TEXT NOT NULL, hypothesis TEXT NOT NULL, decision TEXT NOT NULL, recorded_at_unix_millis BIGINT NOT NULL);
|
||||
GRANT SELECT, INSERT, UPDATE ON self.competence_assessments, self.learning_ledger TO self_role;
|
||||
RESET ROLE;
|
||||
COMMIT;
|
||||
@@ -0,0 +1,22 @@
|
||||
BEGIN;
|
||||
SET ROLE work_role;
|
||||
CREATE TABLE IF NOT EXISTS work.long_running_work (
|
||||
investigation_id TEXT PRIMARY KEY, objective TEXT NOT NULL,
|
||||
hypothesis_refs TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[], attempt_refs TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||
finding_refs TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[], blockers TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||
checkpoint_refs TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[], next_step TEXT NOT NULL, state TEXT NOT NULL,
|
||||
revision BIGINT NOT NULL, time_budget_millis BIGINT NOT NULL, cost_budget_micros BIGINT NOT NULL,
|
||||
max_attempts INTEGER NOT NULL, recorded_at_unix_millis BIGINT NOT NULL,
|
||||
CHECK (investigation_id <> ''), CHECK (objective <> ''), CHECK (next_step <> ''), CHECK (revision > 0), CHECK (time_budget_millis >= 0), CHECK (cost_budget_micros >= 0), CHECK (max_attempts >= 0), CHECK (recorded_at_unix_millis > 0)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS work.investigation_checkpoints (
|
||||
checkpoint_id TEXT PRIMARY KEY, investigation_id TEXT NOT NULL, completed_attempt_refs TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||
completed_finding_refs TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[], next_step TEXT NOT NULL, recorded_at_unix_millis BIGINT NOT NULL,
|
||||
CHECK (checkpoint_id <> ''), CHECK (investigation_id <> ''), CHECK (next_step <> ''), CHECK (recorded_at_unix_millis > 0)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS work.investigation_attempts (
|
||||
investigation_id TEXT NOT NULL, attempt_ref TEXT NOT NULL, verified_at_unix_millis BIGINT NOT NULL,
|
||||
PRIMARY KEY (investigation_id, attempt_ref), CHECK (investigation_id <> ''), CHECK (attempt_ref <> ''), CHECK (verified_at_unix_millis > 0)
|
||||
);
|
||||
RESET ROLE;
|
||||
COMMIT;
|
||||
@@ -0,0 +1,11 @@
|
||||
BEGIN;
|
||||
SET ROLE identity_role;
|
||||
CREATE TABLE IF NOT EXISTS identity.principal_data_realms (
|
||||
realm_id TEXT PRIMARY KEY, owner_principal_id TEXT NOT NULL, realm_kind TEXT NOT NULL,
|
||||
member_principal_ids TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[], shared BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
revision BIGINT NOT NULL, recorded_at_unix_millis BIGINT NOT NULL,
|
||||
CHECK (realm_id <> ''), CHECK (owner_principal_id <> ''), CHECK (realm_kind IN ('PERSONAL','SHARED','PUBLIC')),
|
||||
CHECK (realm_kind <> 'SHARED' OR shared), CHECK (revision > 0), CHECK (recorded_at_unix_millis > 0)
|
||||
);
|
||||
RESET ROLE;
|
||||
COMMIT;
|
||||
@@ -1 +1 @@
|
||||
{"task_file":"dev/tasks/GEN2-008-promote-episode-and-experience-v2-including-negative-experience.json","task_id":"GEN2-008","task_revision":1}
|
||||
{"task_file":"dev/tasks/GEN2-025-multimodal-and-ambient-ingress-readiness-without-implementing-the-whole-.json","task_id":"GEN2-025","task_revision":1}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStrategyUsesRiskAndCostSignals(t *testing.T) {
|
||||
if got := (capability.StrategyRequest{RequiredQuality: 400, CostBudgetMicros: 10}).Select().Path; got != capability.PathRule {
|
||||
t.Fatal(got)
|
||||
}
|
||||
if got := (capability.StrategyRequest{RiskClass: "HIGH", CostOfError: 900}).Select(); got.Path != capability.PathStrongLocal || !got.Escalate {
|
||||
t.Fatalf("%#v", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
)
|
||||
|
||||
func ValidateReflexRevision(v capability.ReflexRevision) error {
|
||||
if v.ReflexID == "" || v.SourceSkillRevision < 1 || v.Kind == "" || v.ImplementationRef == "" || v.Lifecycle == "" || v.RecordedAt < 1 {
|
||||
return fmt.Errorf("invalid reflex revision")
|
||||
}
|
||||
if v.Lifecycle == capability.ReflexActive && len(v.ValidationEvidenceRefs) == 0 {
|
||||
return fmt.Errorf("active reflex requires evidence")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func ReflexFallback(d capability.FastPathDecision) string {
|
||||
if d.Decision == capability.FastPathApplied {
|
||||
return "reflex"
|
||||
}
|
||||
return "skill->reasoning"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
"git.cin.su/m/Assistant/modules/capability/domain"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReflexActiveRequiresEvidenceAndFallbacks(t *testing.T) {
|
||||
v := capability.ReflexRevision{ReflexID: "r", SourceSkillRevision: 1, Kind: capability.ReflexRule, ImplementationRef: "rule", Lifecycle: capability.ReflexActive, RecordedAt: 1}
|
||||
if domain.ValidateReflexRevision(v) == nil {
|
||||
t.Fatal("active reflex without evidence")
|
||||
}
|
||||
v.ValidationEvidenceRefs = []capability.SkillEvidenceRef{"eval"}
|
||||
if err := domain.ValidateReflexRevision(v); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if domain.ReflexFallback(capability.FastPathDecision{Decision: capability.FastPathEscalate}) != "skill->reasoning" {
|
||||
t.Fatal("missing escalation")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
)
|
||||
|
||||
func ValidateSkillRevision(value capability.SkillRevision) error {
|
||||
if value.SkillID == "" || value.Revision < 1 || value.ImplementationRef == "" || value.Lifecycle == "" || value.RecordedAt < 1 {
|
||||
return fmt.Errorf("invalid skill revision")
|
||||
}
|
||||
if value.Lifecycle == capability.SkillValidated && len(value.ValidationEvidenceRefs) == 0 {
|
||||
return fmt.Errorf("validated skill requires evidence")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# configuration
|
||||
|
||||
Configuration owns typed, scoped, versioned settings. ENV is limited to
|
||||
bootstrap primitives; secret values are never configuration values.
|
||||
@@ -0,0 +1,41 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.cin.su/m/Assistant/contracts/configuration"
|
||||
"sort"
|
||||
)
|
||||
|
||||
var ErrInvalidConfigurationCommand = fmt.Errorf("invalid conversational configuration command")
|
||||
|
||||
func Resolve(definition configuration.SettingDefinition, assignments []configuration.SettingAssignment, selector configuration.ScopeSelector) (configuration.EffectiveSetting, error) {
|
||||
if err := definition.Validate(); err != nil {
|
||||
return configuration.EffectiveSetting{}, err
|
||||
}
|
||||
var matches []configuration.SettingAssignment
|
||||
for _, a := range assignments {
|
||||
if err := a.Validate(); err != nil {
|
||||
return configuration.EffectiveSetting{}, err
|
||||
}
|
||||
if a.DefinitionRef == string(definition.ID) && a.Lifecycle == configuration.SettingActive && a.Selector.PrincipalRef == selector.PrincipalRef && a.Selector.SkillRef == selector.SkillRef {
|
||||
matches = append(matches, a)
|
||||
}
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
return configuration.EffectiveSetting{DefinitionRef: string(definition.ID), Value: definition.DefaultValue, ResolutionTrace: []string{"system-default"}}, nil
|
||||
}
|
||||
sort.SliceStable(matches, func(i, j int) bool { return specificity(matches[i].Selector) > specificity(matches[j].Selector) })
|
||||
if len(matches) > 1 && specificity(matches[0].Selector) == specificity(matches[1].Selector) {
|
||||
return configuration.EffectiveSetting{}, fmt.Errorf("configuration conflict")
|
||||
}
|
||||
return configuration.EffectiveSetting{DefinitionRef: string(definition.ID), Value: matches[0].TypedValue, WinningAssignmentRef: string(matches[0].ID), ResolutionTrace: []string{"specificity-selected"}}, nil
|
||||
}
|
||||
func specificity(s configuration.ScopeSelector) int {
|
||||
return len(s.RequiredContextBindings) + boolInt(s.SkillRef != "") + boolInt(s.TaskRef != "") + boolInt(s.SessionRef != "") + boolInt(s.TemporaryOverride)
|
||||
}
|
||||
func boolInt(v bool) int {
|
||||
if v {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package domain_test
|
||||
import("testing"; "git.cin.su/m/Assistant/contracts/configuration"; "git.cin.su/m/Assistant/modules/configuration/domain")
|
||||
func TestConfigurationResolvesCompanyAndSkillScope(t *testing.T){d:=configuration.SettingDefinition{ID:"x",OwnerNamespace:"capability",StableKey:"endpoint",Revision:1,ValueType:configuration.ValueString,Lifecycle:configuration.SettingActive,RecordedAtUnixMillis:1};a:=configuration.SettingAssignment{ID:"a",PrincipalID:"company-a",DefinitionRef:"x",Revision:1,Selector:configuration.ScopeSelector{PrincipalRef:"company-a",SkillRef:"skill"},TypedValue:"a-value",Source:configuration.SourceHumanDirective,Lifecycle:configuration.SettingActive,RecordedAtUnixMillis:1};got,err:=domain.Resolve(d,[]configuration.SettingAssignment{a},configuration.ScopeSelector{PrincipalRef:"company-a",SkillRef:"skill"});if err!=nil||got.Value!="a-value"{t.Fatalf("got %#v err=%v",got,err)}}
|
||||
@@ -0,0 +1,32 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.cin.su/m/Assistant/contracts/configuration"
|
||||
"git.cin.su/m/Assistant/platform/ids"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrConfigurationAuthorityRequired = fmt.Errorf("configuration change requires Authority and Commit")
|
||||
|
||||
func AssignmentFromConversation(kind, principalID, definitionRef, typedValue, sessionRef, sourceRef string, temporaryOverride bool, definition configuration.SettingDefinition, now time.Time) (configuration.SettingAssignment, error) {
|
||||
if kind != "SET" || principalID == "" || definitionRef != string(definition.ID) || strings.TrimSpace(typedValue) == "" || now.IsZero() || definition.ValueType == configuration.ValueSecretRef {
|
||||
return configuration.SettingAssignment{}, ErrInvalidConfigurationCommand
|
||||
}
|
||||
if sourceRef == "" {
|
||||
return configuration.SettingAssignment{}, ErrInvalidConfigurationCommand
|
||||
}
|
||||
id, err := ids.New()
|
||||
if err != nil {
|
||||
return configuration.SettingAssignment{}, err
|
||||
}
|
||||
return configuration.SettingAssignment{ID: configuration.SettingAssignmentID(id), PrincipalID: principalID, DefinitionRef: string(definition.ID), DefinitionRevision: definition.Revision, Revision: 1, Selector: configuration.ScopeSelector{PrincipalRef: principalID, SessionRef: sessionRef, TemporaryOverride: temporaryOverride}, TypedValue: strings.TrimSpace(typedValue), Source: configuration.SourceHumanDirective, SourceRef: sourceRef, Lifecycle: configuration.SettingActive, RecordedAtUnixMillis: now.UnixMilli()}, nil
|
||||
}
|
||||
|
||||
func ExplainEffectiveSetting(definition configuration.SettingDefinition, effective configuration.EffectiveSetting) string {
|
||||
if effective.WinningAssignmentRef == "" {
|
||||
return fmt.Sprintf("Настройка %s сейчас имеет системное значение %q.", definition.StableKey, effective.Value)
|
||||
}
|
||||
return fmt.Sprintf("Настройка %s сейчас имеет значение %q; применено scoped-правило %s.", definition.StableKey, effective.Value, effective.WinningAssignmentRef)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/configuration"
|
||||
"git.cin.su/m/Assistant/modules/configuration/domain"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConversationCreatesScopedAssignmentWithoutBypassingAuthority(t *testing.T) {
|
||||
d := configuration.SettingDefinition{ID: "setting:language", OwnerNamespace: "interaction", StableKey: "language", Revision: 2, ValueType: configuration.ValueString, Lifecycle: configuration.SettingActive, RecordedAtUnixMillis: 1}
|
||||
a, err := domain.AssignmentFromConversation("SET", "principal-1", "setting:language", "ru", "session-1", "utterance-1", true, d, time.UnixMilli(2))
|
||||
if err != nil || a.Selector.PrincipalRef != "principal-1" || !a.Selector.TemporaryOverride || a.Source != configuration.SourceHumanDirective {
|
||||
t.Fatalf("assignment=%+v err=%v", a, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretConfigurationCannotReceiveConversationalValue(t *testing.T) {
|
||||
d := configuration.SettingDefinition{ID: "setting:key", OwnerNamespace: "capability", StableKey: "key", Revision: 1, ValueType: configuration.ValueSecretRef, Lifecycle: configuration.SettingActive, RecordedAtUnixMillis: 1}
|
||||
if _, err := domain.AssignmentFromConversation("SET", "p", string(d.ID), "secret", "", "u", false, d, time.UnixMilli(2)); err == nil {
|
||||
t.Fatal("secret value accepted")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
"git.cin.su/m/Assistant/platform/ids"
|
||||
)
|
||||
|
||||
var ErrInvalidOutcomeAssessment = errors.New("invalid world outcome assessment")
|
||||
|
||||
// AssessWorldOutcome keeps Commit's execution fact separate from Epistemics'
|
||||
// observation. No observation means UNKNOWN; a transport receipt alone never
|
||||
// becomes success.
|
||||
func AssessWorldOutcome(principalID, attemptRef, expectedEffect string, receiptRefs []string, observationRefs []epistemics.ObservationID, claimRefs []epistemics.ClaimID, evidenceRefs []epistemics.EvidenceRef, observedState epistemics.WorldOutcomeState, confidence, assessedAt int64) (epistemics.WorldOutcomeAssessment, error) {
|
||||
if principalID == "" || attemptRef == "" || expectedEffect == "" || assessedAt < 1 || confidence < 0 || confidence > 1000 {
|
||||
return epistemics.WorldOutcomeAssessment{}, ErrInvalidOutcomeAssessment
|
||||
}
|
||||
state := epistemics.OutcomeUnknownState
|
||||
if len(observationRefs) == 0 {
|
||||
state = epistemics.OutcomeUnknownState
|
||||
} else if observedState == epistemics.OutcomeVerifiedSuccess || observedState == epistemics.OutcomeVerifiedPartial || observedState == epistemics.OutcomeVerifiedFailure || observedState == epistemics.OutcomeConflicted {
|
||||
state = observedState
|
||||
} else {
|
||||
state = epistemics.OutcomeVerifiedPartial
|
||||
}
|
||||
id, err := ids.New()
|
||||
if err != nil {
|
||||
return epistemics.WorldOutcomeAssessment{}, err
|
||||
}
|
||||
value := epistemics.WorldOutcomeAssessment{ID: string(id), PrincipalID: principalID, ActionAttemptRef: attemptRef, ExecutionReceiptRefs: append([]string(nil), receiptRefs...), ExpectedEffectRef: expectedEffect, ObservationRefs: append([]epistemics.ObservationID(nil), observationRefs...), ClaimRefs: append([]epistemics.ClaimID(nil), claimRefs...), State: state, ConfidenceMillis: confidence, EvidenceRefs: append([]epistemics.EvidenceRef(nil), evidenceRefs...), AssessedAt: assessedAt}
|
||||
if !value.Valid() {
|
||||
return epistemics.WorldOutcomeAssessment{}, ErrInvalidOutcomeAssessment
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
"git.cin.su/m/Assistant/modules/epistemics/domain"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExecutionReceiptWithoutObservationRemainsUnknown(t *testing.T) {
|
||||
value, err := domain.AssessWorldOutcome("principal", "attempt", "effect", []string{"receipt"}, nil, nil, nil, epistemics.OutcomeVerifiedSuccess, 1000, 1)
|
||||
if err != nil || value.State != epistemics.OutcomeUnknownState {
|
||||
t.Fatalf("value=%+v err=%v", value, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestObservedOutcomeCanBeVerifiedSeparately(t *testing.T) {
|
||||
value, err := domain.AssessWorldOutcome("principal", "attempt", "effect", []string{"receipt"}, []epistemics.ObservationID{"observation"}, []epistemics.ClaimID{"claim"}, []epistemics.EvidenceRef{"evidence"}, epistemics.OutcomeVerifiedSuccess, 900, 1)
|
||||
if err != nil || value.State != epistemics.OutcomeVerifiedSuccess || len(value.EvidenceRefs) != 1 {
|
||||
t.Fatalf("value=%+v err=%v", value, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package domain
|
||||
|
||||
import "git.cin.su/m/Assistant/contracts/epistemics"
|
||||
|
||||
func AssessSourceReliability(sourceRef, domainRef string, freshness epistemics.FreshnessState, historyScore, priorScore int64, evidence []string, at int64) epistemics.SourceReliabilityAssessment {
|
||||
score := (historyScore + priorScore) / 2
|
||||
state := epistemics.ReliabilityCurrent
|
||||
if freshness == epistemics.FreshnessUnknown {
|
||||
state = epistemics.ReliabilityUnknown
|
||||
} else if freshness == epistemics.FreshnessStale {
|
||||
state = epistemics.ReliabilityAging
|
||||
}
|
||||
return epistemics.SourceReliabilityAssessment{SourceRef: sourceRef, DomainRef: domainRef, Freshness: freshness, HistoryScoreMillis: historyScore, ReliabilityScoreMillis: score, State: state, EvidenceRefs: append([]string(nil), evidence...), AssessedAtUnixMillis: at}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
"git.cin.su/m/Assistant/modules/epistemics/domain"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSourceReliabilityIncludesDomainFreshnessAndHistory(t *testing.T) {
|
||||
a := domain.AssessSourceReliability("source", "weather", epistemics.FreshnessStale, 900, 700, []string{"history"}, 1)
|
||||
if !a.Valid() || a.State != epistemics.ReliabilityAging || a.ReliabilityScoreMillis != 800 {
|
||||
t.Fatalf("assessment=%+v", a)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.cin.su/m/Assistant/contracts/identity"
|
||||
)
|
||||
|
||||
var ErrInvalidRealm = errors.New("invalid principal data realm")
|
||||
|
||||
func ValidatePrincipalDataRealm(value identity.PrincipalDataRealm) error {
|
||||
if !value.Valid() {
|
||||
return ErrInvalidRealm
|
||||
}
|
||||
if value.Kind == identity.DataRealmPersonal && len(value.MemberPrincipalIDs) != 0 {
|
||||
return ErrInvalidRealm
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CanReadRealm(value identity.PrincipalDataRealm, principalID identity.PrincipalID) bool {
|
||||
if ValidatePrincipalDataRealm(value) != nil || principalID == "" {
|
||||
return false
|
||||
}
|
||||
if value.Kind == identity.DataRealmPublic {
|
||||
return true
|
||||
}
|
||||
if principalID == value.OwnerPrincipalID {
|
||||
return true
|
||||
}
|
||||
for _, member := range value.MemberPrincipalIDs {
|
||||
if member == principalID {
|
||||
return value.Kind == identity.DataRealmShared && value.Shared
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/identity"
|
||||
"git.cin.su/m/Assistant/modules/identity/domain"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRealmSeparatesOwnerSharedAndPublicAccess(t *testing.T) {
|
||||
r := identity.PrincipalDataRealm{ID: "realm", OwnerPrincipalID: "owner", Kind: identity.DataRealmShared, Shared: true, MemberPrincipalIDs: []identity.PrincipalID{"member"}, Revision: 1, RecordedAtUnixMillis: 1}
|
||||
if !domain.CanReadRealm(r, "owner") || !domain.CanReadRealm(r, "member") || domain.CanReadRealm(r, "guest") {
|
||||
t.Fatalf("realm access leaked: %+v", r)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/interaction"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseConfigurationCommand recognises bounded human intent and never writes
|
||||
// configuration or invokes Authority/Commit.
|
||||
func ParseConfigurationCommand(message, principalID, sourceRef string) interaction.ConfigurationCommand {
|
||||
text := strings.TrimSpace(strings.ToLower(message))
|
||||
if principalID == "" || sourceRef == "" {
|
||||
return interaction.ConfigurationCommand{}
|
||||
}
|
||||
if strings.Contains(text, "какая настройка") || strings.HasPrefix(text, "какое значение") {
|
||||
return interaction.ConfigurationCommand{Kind: interaction.ConfigurationCommandQuery, PrincipalID: principalID, SourceRef: sourceRef}
|
||||
}
|
||||
temporary := strings.Contains(text, "только сейчас") || strings.Contains(text, "на этот раз") || strings.Contains(text, "временно")
|
||||
if strings.Contains(text, "настрой") || strings.Contains(text, "установи") || strings.Contains(text, "сделай") {
|
||||
parts := strings.SplitN(message, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
key := strings.TrimSpace(strings.TrimPrefix(strings.ToLower(parts[0]), "настройка"))
|
||||
value := strings.TrimSpace(parts[1])
|
||||
for _, marker := range []string{"только сейчас", "на этот раз", "временно"} {
|
||||
value = strings.TrimSpace(strings.ReplaceAll(strings.ToLower(value), marker, ""))
|
||||
}
|
||||
return interaction.ConfigurationCommand{Kind: interaction.ConfigurationCommandSet, PrincipalID: principalID, DefinitionRef: key, TypedValue: value, TemporaryOverride: temporary, SourceRef: sourceRef}
|
||||
}
|
||||
}
|
||||
return interaction.ConfigurationCommand{}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/interaction"
|
||||
interactiondomain "git.cin.su/m/Assistant/modules/interaction/domain"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConversationParserProducesIntentOnly(t *testing.T) {
|
||||
value := interactiondomain.ParseConfigurationCommand("Настройка language = ru только сейчас", "principal-1", "utterance-1")
|
||||
if value.Kind != interaction.ConfigurationCommandSet || value.DefinitionRef != "language" || value.TypedValue != "ru" || !value.TemporaryOverride {
|
||||
t.Fatalf("value=%+v", value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package domain
|
||||
|
||||
import "git.cin.su/m/Assistant/contracts/interaction"
|
||||
|
||||
type ContextProjection struct {
|
||||
Text string
|
||||
Source string
|
||||
UsedPersonalMemoryFallback bool
|
||||
}
|
||||
|
||||
// BuildGen2Context is a read-side projection over canonical utterance history.
|
||||
// PersonalMemory remains a compatibility fallback and is never written here.
|
||||
func BuildGen2Context(history []interaction.HumanUtterance) ContextProjection {
|
||||
text := CompilePersonalMemory(history).ContextText()
|
||||
if text != "" {
|
||||
return ContextProjection{Text: text, Source: "retrieval:interaction-utterances"}
|
||||
}
|
||||
return ContextProjection{Text: text, Source: "personal-memory-v1-fallback", UsedPersonalMemoryFallback: true}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/interaction"
|
||||
"git.cin.su/m/Assistant/modules/interaction/domain"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGen2ContextUsesRetrievalProjectionAndKeepsV1Fallback(t *testing.T) {
|
||||
got := domain.BuildGen2Context([]interaction.HumanUtterance{{ID: "u", Content: "Я сейчас в Москве", ReceivedAtUnixMillis: 1}})
|
||||
if got.Source != "retrieval:interaction-utterances" || got.UsedPersonalMemoryFallback {
|
||||
t.Fatalf("got %#v", got)
|
||||
}
|
||||
empty := domain.BuildGen2Context(nil)
|
||||
if !empty.UsedPersonalMemoryFallback {
|
||||
t.Fatalf("fallback missing: %#v", empty)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package domain
|
||||
|
||||
import "git.cin.su/m/Assistant/contracts/interaction"
|
||||
|
||||
// AttributeSpeaker is an observational hint only. It deliberately cannot
|
||||
// manufacture an authenticated principal binding from voice recognition.
|
||||
func AttributeSpeaker(sourceRef, principalCandidate, observationRef string, confidence int64) interaction.SpeakerAttribution {
|
||||
if sourceRef == "" || observationRef == "" || confidence < 0 || confidence > 1000 {
|
||||
return interaction.SpeakerAttribution{}
|
||||
}
|
||||
return interaction.SpeakerAttribution{SourceRef: sourceRef, PrincipalCandidate: principalCandidate, ConfidenceMillis: confidence, ObservationRef: observationRef, Authenticated: false}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/modules/interaction/domain"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSpeakerRecognitionRemainsObservationNotAuthentication(t *testing.T) {
|
||||
a := domain.AttributeSpeaker("mic-1", "principal-member", "observation-1", 700)
|
||||
if a.Authenticated || a.PrincipalCandidate == "" || a.ObservationRef == "" {
|
||||
t.Fatalf("attribution=%+v", a)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
selfcontract "git.cin.su/m/Assistant/contracts/self"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBenchmarkRequiresEvidenceAndDoesNotPromoteAlone(t *testing.T) {
|
||||
e := selfcontract.BenchmarkEvaluation{CaseRef: "case", QualityMillis: 900}
|
||||
if e.Valid() {
|
||||
t.Fatal("benchmark without evidence accepted")
|
||||
}
|
||||
e.EvidenceRefs = []selfcontract.SemanticReference{{OwnerNamespace: "epistemics", SemanticKind: "evaluation", StableID: "e"}}
|
||||
if !e.Valid() {
|
||||
t.Fatal("evidence-backed evaluation rejected")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/self"
|
||||
)
|
||||
|
||||
func ValidateCompetence(v self.CompetenceAssessment) error { return v.Validate() }
|
||||
func ValidateLearning(v self.LearningLedgerEntry) error { return v.Validate() }
|
||||
@@ -0,0 +1,18 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
selfcontract "git.cin.su/m/Assistant/contracts/self"
|
||||
"git.cin.su/m/Assistant/modules/self/domain"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompetenceCannotBeSelfDeclaredWithoutEvidence(t *testing.T) {
|
||||
v := selfcontract.CompetenceAssessment{ID: "a", PrincipalID: "p", SubjectRef: selfcontract.SemanticReference{OwnerNamespace: "capability", SemanticKind: "skill", StableID: "s"}, Revision: 1, ConfidenceMillis: 1000, AssessedAt: 1}
|
||||
if domain.ValidateCompetence(v) == nil {
|
||||
t.Fatal("unsupported competence accepted")
|
||||
}
|
||||
v.EvidenceRefs = []selfcontract.SemanticReference{{OwnerNamespace: "epistemics", SemanticKind: "evaluation", StableID: "e"}}
|
||||
if err := domain.ValidateCompetence(v); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package domain
|
||||
|
||||
import "git.cin.su/m/Assistant/contracts/work"
|
||||
|
||||
func AssessConsequences(id string, target string, impact, reversibility, uncertainty, recoverability, cost int64, at int64) work.ConsequenceAssessment {
|
||||
a := work.ConsequenceAssessment{ID: id, TargetRefs: []string{target}, Impact: impact, Reversibility: reversibility, Uncertainty: uncertainty, Recoverability: recoverability, CostOfError: cost, RecordedAtUnixMillis: at}
|
||||
a.RequiresDryRun = impact >= 700 || reversibility >= 700
|
||||
a.RequiresBackup = reversibility >= 500
|
||||
a.RecommendedConfirmation = "STANDARD"
|
||||
if uncertainty*cost > 500000 {
|
||||
a.RecommendedConfirmation = "EXPLICIT_OWNER_CONFIRMATION"
|
||||
}
|
||||
return a
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/modules/work/domain"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConsequenceAdvisesMitigationWithoutCapabilityBlacklist(t *testing.T) {
|
||||
a := domain.AssessConsequences("a", "database:a", 800, 800, 900, 300, 900, 1)
|
||||
if !a.Valid() || !a.RequiresDryRun || !a.RequiresBackup || a.RecommendedConfirmation != "EXPLICIT_OWNER_CONFIRMATION" {
|
||||
t.Fatalf("%#v", a)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
)
|
||||
|
||||
var ErrGovernedEvolution = errors.New("governed evolution gate failed")
|
||||
|
||||
func RunGovernedEvolution(needRef string, ops work.GovernedEvolutionOps) (work.GovernedEvolutionResult, error) {
|
||||
result := work.GovernedEvolutionResult{NeedRef: needRef, Stages: []work.EvolutionRunStage{work.EvolutionNeedRecorded}}
|
||||
if needRef == "" || ops.CreateCandidate == nil || ops.Evaluate == nil || ops.SandboxBuild == nil || ops.Benchmark == nil || ops.Promote == nil || ops.Rollback == nil || ops.LearningLedgerOutcome == nil {
|
||||
return result, ErrGovernedEvolution
|
||||
}
|
||||
candidate, err := ops.CreateCandidate()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if candidate.ID == "" || candidate.ProposalRef == "" {
|
||||
return result, ErrGovernedEvolution
|
||||
}
|
||||
result.CandidateRef = candidate.ID
|
||||
result.Stages = append(result.Stages, work.EvolutionCandidateBuilt)
|
||||
evaluation, err := ops.Evaluate(candidate)
|
||||
if err != nil || !evaluation.ValidFor(candidate) {
|
||||
return result, ErrGovernedEvolution
|
||||
}
|
||||
result.EvaluationRef = evaluation.CandidateRef
|
||||
result.Stages = append(result.Stages, work.EvolutionIndependentlyEvaluated)
|
||||
sandboxEvidence, err := ops.SandboxBuild(candidate)
|
||||
if err != nil || sandboxEvidence == "" {
|
||||
return result, ErrGovernedEvolution
|
||||
}
|
||||
candidate.SandboxEvidenceRefs = append(candidate.SandboxEvidenceRefs, sandboxEvidence)
|
||||
result.EvidenceRefs = append(result.EvidenceRefs, sandboxEvidence)
|
||||
result.Stages = append(result.Stages, work.EvolutionSandboxed)
|
||||
benchmarkEvidence, passed, err := ops.Benchmark(candidate)
|
||||
if err != nil || !passed || benchmarkEvidence == "" {
|
||||
result = rollbackEvolution(result, candidate, ops)
|
||||
return result, ErrGovernedEvolution
|
||||
}
|
||||
candidate.BenchmarkEvidenceRefs = append(candidate.BenchmarkEvidenceRefs, benchmarkEvidence)
|
||||
result.EvidenceRefs = append(result.EvidenceRefs, benchmarkEvidence)
|
||||
result.Stages = append(result.Stages, work.EvolutionBenchmarked)
|
||||
if ops.Canary != nil {
|
||||
canaryEvidence, passed, canaryErr := ops.Canary(candidate)
|
||||
if canaryErr != nil || !passed || canaryEvidence == "" {
|
||||
result = rollbackEvolution(result, candidate, ops)
|
||||
return result, ErrGovernedEvolution
|
||||
}
|
||||
result.EvidenceRefs = append(result.EvidenceRefs, canaryEvidence)
|
||||
result.Stages = append(result.Stages, work.EvolutionCanaried)
|
||||
}
|
||||
promotionRef, err := ops.Promote(candidate, evaluation)
|
||||
if err != nil || promotionRef == "" {
|
||||
result = rollbackEvolution(result, candidate, ops)
|
||||
return result, ErrGovernedEvolution
|
||||
}
|
||||
result.Promoted = true
|
||||
result.Stages = append(result.Stages, work.EvolutionPromoted)
|
||||
result.LearningLedgerOutcomeRef, err = ops.LearningLedgerOutcome(result)
|
||||
if err != nil || result.LearningLedgerOutcomeRef == "" {
|
||||
return result, ErrGovernedEvolution
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func rollbackEvolution(result work.GovernedEvolutionResult, candidate work.EvolutionCandidate, ops work.GovernedEvolutionOps) work.GovernedEvolutionResult {
|
||||
ref, err := ops.Rollback(candidate)
|
||||
if err == nil && ref != "" {
|
||||
result.RollbackRef = ref
|
||||
result.Stages = append(result.Stages, work.EvolutionRolledBack)
|
||||
result.LearningLedgerOutcomeRef, _ = ops.LearningLedgerOutcome(result)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
"git.cin.su/m/Assistant/modules/work/domain"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGovernedEvolutionExecutesGatesAndLedgerOutcome(t *testing.T) {
|
||||
calls := []string{}
|
||||
result, err := domain.RunGovernedEvolution("need-1", work.GovernedEvolutionOps{
|
||||
CreateCandidate: func() (work.EvolutionCandidate, error) {
|
||||
calls = append(calls, "candidate")
|
||||
return work.EvolutionCandidate{ID: "candidate-1", ProposalRef: "proposal-1", CreatorIdentity: "creator", RollbackRef: "rollback-plan"}, nil
|
||||
},
|
||||
Evaluate: func(c work.EvolutionCandidate) (work.EvolutionEvaluation, error) {
|
||||
calls = append(calls, "evaluate")
|
||||
return work.EvolutionEvaluation{CandidateRef: c.ID, EvaluatorIdentity: "independent", Approved: true, MustNotWorsenPassed: true, EvidenceRefs: []work.EvidenceRef{"eval"}, RecordedAtUnixMillis: 1}, nil
|
||||
},
|
||||
SandboxBuild: func(work.EvolutionCandidate) (work.EvidenceRef, error) {
|
||||
calls = append(calls, "sandbox-build")
|
||||
return "sandbox", nil
|
||||
},
|
||||
Benchmark: func(work.EvolutionCandidate) (work.EvidenceRef, bool, error) {
|
||||
calls = append(calls, "benchmark")
|
||||
return "benchmark", true, nil
|
||||
},
|
||||
Canary: func(work.EvolutionCandidate) (work.EvidenceRef, bool, error) {
|
||||
calls = append(calls, "canary")
|
||||
return "canary", true, nil
|
||||
},
|
||||
Promote: func(work.EvolutionCandidate, work.EvolutionEvaluation) (string, error) {
|
||||
calls = append(calls, "promote")
|
||||
return "promotion", nil
|
||||
},
|
||||
Rollback: func(work.EvolutionCandidate) (string, error) {
|
||||
calls = append(calls, "rollback")
|
||||
return "rollback", nil
|
||||
},
|
||||
LearningLedgerOutcome: func(work.GovernedEvolutionResult) (string, error) {
|
||||
calls = append(calls, "ledger")
|
||||
return "ledger-outcome", nil
|
||||
},
|
||||
})
|
||||
if err != nil || !result.Promoted || result.LearningLedgerOutcomeRef != "ledger-outcome" {
|
||||
t.Fatalf("result=%+v err=%v", result, err)
|
||||
}
|
||||
want := []string{"candidate", "evaluate", "sandbox-build", "benchmark", "canary", "promote", "ledger"}
|
||||
if len(calls) != len(want) {
|
||||
t.Fatalf("calls=%v", calls)
|
||||
}
|
||||
for i := range want {
|
||||
if calls[i] != want[i] {
|
||||
t.Fatalf("calls=%v", calls)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGovernedEvolutionRollsBackOnMustNotWorsenFailure(t *testing.T) {
|
||||
rolledBack := false
|
||||
result, err := domain.RunGovernedEvolution("need-1", work.GovernedEvolutionOps{
|
||||
CreateCandidate: func() (work.EvolutionCandidate, error) {
|
||||
return work.EvolutionCandidate{ID: "candidate-1", ProposalRef: "proposal-1", CreatorIdentity: "creator", RollbackRef: "rollback-plan"}, nil
|
||||
},
|
||||
Evaluate: func(c work.EvolutionCandidate) (work.EvolutionEvaluation, error) {
|
||||
return work.EvolutionEvaluation{CandidateRef: c.ID, EvaluatorIdentity: "independent", Approved: true, MustNotWorsenPassed: true, EvidenceRefs: []work.EvidenceRef{"eval"}, RecordedAtUnixMillis: 1}, nil
|
||||
},
|
||||
SandboxBuild: func(work.EvolutionCandidate) (work.EvidenceRef, error) { return "sandbox", nil }, Benchmark: func(work.EvolutionCandidate) (work.EvidenceRef, bool, error) { return "benchmark", false, nil },
|
||||
Promote: func(work.EvolutionCandidate, work.EvolutionEvaluation) (string, error) {
|
||||
t.Fatal("promotion bypassed failed benchmark")
|
||||
return "", nil
|
||||
}, Rollback: func(work.EvolutionCandidate) (string, error) { rolledBack = true; return "rollback-evidence", nil }, LearningLedgerOutcome: func(r work.GovernedEvolutionResult) (string, error) { return "ledger-rollback", nil },
|
||||
})
|
||||
if err == nil || !rolledBack || result.RollbackRef != "rollback-evidence" || result.Promoted {
|
||||
t.Fatalf("result=%+v err=%v", result, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package domain
|
||||
|
||||
import "git.cin.su/m/Assistant/contracts/work"
|
||||
|
||||
func PauseInvestigation(value work.Investigation) work.Investigation {
|
||||
if value.Valid() {
|
||||
value.State = work.LongRunningPaused
|
||||
value.Revision++
|
||||
}
|
||||
return value
|
||||
}
|
||||
func ResumeInvestigation(value work.Investigation) work.Investigation {
|
||||
if value.Valid() && value.State == work.LongRunningPaused {
|
||||
value.State = work.LongRunningRunning
|
||||
value.Revision++
|
||||
}
|
||||
return value
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
"git.cin.su/m/Assistant/modules/work/domain"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInvestigationPauseResumeIsDurableStateTransition(t *testing.T) {
|
||||
w := work.Investigation{ID: "i", Objective: "diagnose", State: work.LongRunningRunning, Revision: 1, RecordedAtUnixMillis: 1}
|
||||
w = domain.PauseInvestigation(w)
|
||||
w = domain.ResumeInvestigation(w)
|
||||
if w.State != work.LongRunningRunning || w.Revision != 3 {
|
||||
t.Fatalf("%#v", w)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package domain
|
||||
|
||||
import "git.cin.su/m/Assistant/contracts/work"
|
||||
|
||||
func AssembleSpecialists(p work.PerspectiveSpec, subtasks []string) work.TeamDecision {
|
||||
if !p.Valid() {
|
||||
return work.TeamDecision{Reason: "invalid perspective"}
|
||||
}
|
||||
if len(subtasks) < 2 {
|
||||
return work.TeamDecision{Reason: "single reasoning path is sufficient"}
|
||||
}
|
||||
assignments := make([]work.SpecialistAssignment, 0, len(subtasks))
|
||||
for _, s := range subtasks {
|
||||
assignments = append(assignments, work.SpecialistAssignment{Perspective: p, SubtaskRef: s, FocusRef: p.FocusRef, SkillRefs: append([]string(nil), p.SkillRefs...), ToolRefs: append([]string(nil), p.ToolRefs...)})
|
||||
}
|
||||
return work.TeamDecision{UseTeam: true, Reason: "independent subtasks justify bounded perspectives", Assignments: assignments}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
"git.cin.su/m/Assistant/modules/work/domain"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPerspectiveTeamIsBoundedByIndependentSubtasks(t *testing.T) {
|
||||
p := work.PerspectiveSpec{ID: "architect", ObjectiveLens: "architecture", FocusRef: "focus-1", SkillRefs: []string{"skill-1"}, ToolRefs: []string{"tool-1"}, EvaluationCriteria: []string{"correctness"}, ExpectedOutputShape: "finding"}
|
||||
if d := domain.AssembleSpecialists(p, []string{"a"}); d.UseTeam {
|
||||
t.Fatal("team for one subtask")
|
||||
}
|
||||
if d := domain.AssembleSpecialists(p, []string{"a", "b"}); !d.UseTeam || len(d.Assignments) != 2 {
|
||||
t.Fatalf("%#v", d)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package domain
|
||||
|
||||
import "git.cin.su/m/Assistant/contracts/work"
|
||||
|
||||
func PromoteEvolution(c work.EvolutionCandidate, e work.EvolutionEvaluation) bool {
|
||||
return e.ValidFor(c) && c.ProposalRef != "" && c.RollbackRef != "" && len(c.SandboxEvidenceRefs) > 0 && len(c.BenchmarkEvidenceRefs) > 0
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package domain_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/work"
|
||||
"git.cin.su/m/Assistant/modules/work/domain"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEvolutionRequiresIndependentEvaluatorAndRollbackEvidence(t *testing.T) {
|
||||
c := work.EvolutionCandidate{ID: "c", ProposalRef: "p", ChangeLevel: work.EvolutionBehavior, CreatorIdentity: "creator", RollbackRef: "rollback", SandboxEvidenceRefs: []work.EvidenceRef{"sandbox"}, BenchmarkEvidenceRefs: []work.EvidenceRef{"benchmark"}}
|
||||
e := work.EvolutionEvaluation{CandidateRef: "c", EvaluatorIdentity: "creator", Approved: true, MustNotWorsenPassed: true, EvidenceRefs: []work.EvidenceRef{"eval"}, RecordedAtUnixMillis: 1}
|
||||
if domain.PromoteEvolution(c, e) {
|
||||
t.Fatal("creator promoted own change")
|
||||
}
|
||||
e.EvaluatorIdentity = "independent"
|
||||
if !domain.PromoteEvolution(c, e) {
|
||||
t.Fatal("independently evaluated candidate rejected")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package secrets
|
||||
|
||||
import "git.cin.su/m/Assistant/contracts/configuration"
|
||||
|
||||
func ValidateBootstrap(value configuration.BootstrapConfig) bool {
|
||||
return value.NodeIdentity != "" && value.ControlDatabaseLocator != "" && value.RootSecretResolverLocator != ""
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package retrieval
|
||||
|
||||
import "sort"
|
||||
|
||||
type Lane string
|
||||
|
||||
const (
|
||||
LaneSemantic Lane = "SEMANTIC"
|
||||
LaneEntity Lane = "ENTITY"
|
||||
LaneTemporal Lane = "TEMPORAL"
|
||||
LaneGoal Lane = "GOAL"
|
||||
LanePositiveExperience Lane = "POSITIVE_EXPERIENCE"
|
||||
LaneNegativeExperience Lane = "NEGATIVE_EXPERIENCE"
|
||||
LaneContradiction Lane = "CONTRADICTION"
|
||||
LaneSkill Lane = "SKILL"
|
||||
)
|
||||
|
||||
// RetrievalDocument is disposable read-side projection data. SourceRef and
|
||||
// Owner preserve provenance; this type never becomes canonical meaning.
|
||||
type RetrievalDocument struct {
|
||||
ID string
|
||||
SourceRef string
|
||||
Owner string
|
||||
PrincipalID string
|
||||
ContextScopeRef string
|
||||
VersionRef string
|
||||
Lane Lane
|
||||
Text string
|
||||
Relevance int
|
||||
Freshness int
|
||||
Confidence int
|
||||
Salience int
|
||||
Cost int
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
PrincipalID string
|
||||
ContextScopeRef string
|
||||
VersionRef string
|
||||
Lanes []Lane
|
||||
Limit int
|
||||
}
|
||||
|
||||
func FilterAndRank(documents []RetrievalDocument, query Query) []RetrievalDocument {
|
||||
filtered := make([]RetrievalDocument, 0, len(documents))
|
||||
for _, doc := range documents {
|
||||
if doc.PrincipalID != query.PrincipalID || (query.ContextScopeRef != "" && doc.ContextScopeRef != query.ContextScopeRef) || (query.VersionRef != "" && doc.VersionRef != query.VersionRef) || !containsLane(query.Lanes, doc.Lane) {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, doc)
|
||||
}
|
||||
sort.SliceStable(filtered, func(i, j int) bool {
|
||||
left := filtered[i].Relevance + filtered[i].Freshness + filtered[i].Confidence + filtered[i].Salience - filtered[i].Cost
|
||||
right := filtered[j].Relevance + filtered[j].Freshness + filtered[j].Confidence + filtered[j].Salience - filtered[j].Cost
|
||||
if left != right {
|
||||
return left > right
|
||||
}
|
||||
return filtered[i].ID < filtered[j].ID
|
||||
})
|
||||
if query.Limit > 0 && len(filtered) > query.Limit {
|
||||
filtered = filtered[:query.Limit]
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func containsLane(lanes []Lane, lane Lane) bool {
|
||||
if len(lanes) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, v := range lanes {
|
||||
if v == lane {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ProjectionBuilder interface {
|
||||
Build() ([]RetrievalDocument, error)
|
||||
}
|
||||
|
||||
func Rebuild(builders ...ProjectionBuilder) ([]RetrievalDocument, error) {
|
||||
var result []RetrievalDocument
|
||||
for _, builder := range builders {
|
||||
docs, err := builder.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, docs...)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package retrieval_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/runtime/retrieval"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestScopeAndVersionFilterBeforeRanking(t *testing.T) {
|
||||
docs := []retrieval.RetrievalDocument{{ID: "wrong-company", PrincipalID: "a", ContextScopeRef: "b", VersionRef: "v", Lane: retrieval.LaneSemantic, Relevance: 100}, {ID: "right", PrincipalID: "a", ContextScopeRef: "a", VersionRef: "v", Lane: retrieval.LaneNegativeExperience, Relevance: 1}, {ID: "wrong-version", PrincipalID: "a", ContextScopeRef: "a", VersionRef: "old", Lane: retrieval.LaneSemantic, Relevance: 100}}
|
||||
got := retrieval.FilterAndRank(docs, retrieval.Query{PrincipalID: "a", ContextScopeRef: "a", VersionRef: "v", Lanes: []retrieval.Lane{retrieval.LaneSemantic, retrieval.LaneNegativeExperience}, Limit: 10})
|
||||
if len(got) != 1 || got[0].ID != "right" {
|
||||
t.Fatalf("got %#v", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package supervisor
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// DurableStore is supervisor-owned external control-plane state. It is not a
|
||||
// candidate Assistant process-local recovery cache.
|
||||
type DurableStore struct{ path string }
|
||||
|
||||
func NewDurableStore(path string) (*DurableStore, error) {
|
||||
if path == "" || !filepath.IsAbs(path) {
|
||||
return nil, errors.New("supervisor state path must be absolute")
|
||||
}
|
||||
return &DurableStore{path: path}, nil
|
||||
}
|
||||
|
||||
func (s *DurableStore) Save(value Lifeboat) error {
|
||||
if s == nil || s.path == "" {
|
||||
return errors.New("supervisor state store is unavailable")
|
||||
}
|
||||
payload, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(s.path), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
temporary, err := os.CreateTemp(filepath.Dir(s.path), ".lifeboat-*.tmp")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
temporaryName := temporary.Name()
|
||||
defer os.Remove(temporaryName)
|
||||
if err = temporary.Chmod(0600); err == nil {
|
||||
_, err = temporary.Write(payload)
|
||||
}
|
||||
if closeErr := temporary.Close(); err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(temporaryName, s.path)
|
||||
}
|
||||
|
||||
func (s *DurableStore) Load() (Lifeboat, error) {
|
||||
if s == nil || s.path == "" {
|
||||
return Lifeboat{}, errors.New("supervisor state store is unavailable")
|
||||
}
|
||||
payload, err := os.ReadFile(s.path)
|
||||
if err != nil {
|
||||
return Lifeboat{}, err
|
||||
}
|
||||
var value Lifeboat
|
||||
if err := json.Unmarshal(payload, &value); err != nil {
|
||||
return Lifeboat{}, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// PersistentLifeboat reloads before every transition and commits after it.
|
||||
type PersistentLifeboat struct{ store *DurableStore }
|
||||
|
||||
func NewPersistentLifeboat(store *DurableStore) *PersistentLifeboat {
|
||||
return &PersistentLifeboat{store: store}
|
||||
}
|
||||
func (p *PersistentLifeboat) transition(fn func(*Lifeboat) bool) (bool, error) {
|
||||
value, err := p.store.Load()
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return false, err
|
||||
}
|
||||
ok := fn(&value)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
if err := p.store.Save(value); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
func (p *PersistentLifeboat) Start(candidate Release) (bool, error) {
|
||||
return p.transition(func(value *Lifeboat) bool { return value.Start(candidate) })
|
||||
}
|
||||
func (p *PersistentLifeboat) Record(observation Observation) (bool, error) {
|
||||
return p.transition(func(value *Lifeboat) bool { return value.Record(observation) })
|
||||
}
|
||||
func (p *PersistentLifeboat) Rollback() (bool, error) {
|
||||
return p.transition(func(value *Lifeboat) bool { return value.Rollback() })
|
||||
}
|
||||
func (p *PersistentLifeboat) Snapshot() (Lifeboat, error) { return p.store.Load() }
|
||||
@@ -0,0 +1,62 @@
|
||||
package supervisor
|
||||
|
||||
type ReleaseState string
|
||||
|
||||
const (
|
||||
ReleaseCandidate ReleaseState = "CANDIDATE"
|
||||
ReleaseHealthy ReleaseState = "HEALTHY"
|
||||
ReleaseFailed ReleaseState = "FAILED"
|
||||
ReleaseRolledBack ReleaseState = "ROLLED_BACK"
|
||||
)
|
||||
|
||||
type Release struct {
|
||||
ID string
|
||||
Version string
|
||||
State ReleaseState
|
||||
LastKnownGood bool
|
||||
}
|
||||
type Observation struct {
|
||||
ReleaseID string
|
||||
Ready bool
|
||||
Reason string
|
||||
ObservedAtUnixMillis int64
|
||||
}
|
||||
type Lifeboat struct {
|
||||
Current Release
|
||||
LastKnownGood Release
|
||||
Observations []Observation
|
||||
}
|
||||
|
||||
func (l *Lifeboat) Start(candidate Release) bool {
|
||||
if candidate.ID == "" || candidate.Version == "" {
|
||||
return false
|
||||
}
|
||||
candidate.State = ReleaseCandidate
|
||||
l.Current = candidate
|
||||
return true
|
||||
}
|
||||
func (l *Lifeboat) Record(o Observation) bool {
|
||||
if o.ReleaseID == "" {
|
||||
return false
|
||||
}
|
||||
l.Observations = append(l.Observations, o)
|
||||
if o.ReleaseID != l.Current.ID {
|
||||
return true
|
||||
}
|
||||
if o.Ready {
|
||||
l.Current.State = ReleaseHealthy
|
||||
l.Current.LastKnownGood = true
|
||||
l.LastKnownGood = l.Current
|
||||
} else {
|
||||
l.Current.State = ReleaseFailed
|
||||
}
|
||||
return true
|
||||
}
|
||||
func (l *Lifeboat) Rollback() bool {
|
||||
if l.LastKnownGood.ID == "" {
|
||||
return false
|
||||
}
|
||||
l.Current = l.LastKnownGood
|
||||
l.Current.State = ReleaseRolledBack
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package supervisor_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/runtime/supervisor"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLifeboatRollsBackCandidateToLastKnownGood(t *testing.T) {
|
||||
l := supervisor.Lifeboat{}
|
||||
l.Start(supervisor.Release{ID: "good", Version: "1"})
|
||||
l.Record(supervisor.Observation{ReleaseID: "good", Ready: true})
|
||||
l.Start(supervisor.Release{ID: "candidate", Version: "2"})
|
||||
l.Record(supervisor.Observation{ReleaseID: "candidate", Ready: false})
|
||||
if !l.Rollback() || l.Current.ID != "good" || l.Current.State != supervisor.ReleaseRolledBack {
|
||||
t.Fatalf("%#v", l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersistentLifeboatRestoresLastKnownGoodAfterCandidateFailure(t *testing.T) {
|
||||
store, err := supervisor.NewDurableStore(filepath.Join(t.TempDir(), "control", "lifeboat.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
first := supervisor.NewPersistentLifeboat(store)
|
||||
if ok, err := first.Start(supervisor.Release{ID: "good", Version: "1"}); err != nil || !ok {
|
||||
t.Fatalf("start good: %v %v", ok, err)
|
||||
}
|
||||
if ok, err := first.Record(supervisor.Observation{ReleaseID: "good", Ready: true, ObservedAtUnixMillis: 1}); err != nil || !ok {
|
||||
t.Fatalf("record good: %v %v", ok, err)
|
||||
}
|
||||
if ok, err := first.Start(supervisor.Release{ID: "candidate", Version: "2"}); err != nil || !ok {
|
||||
t.Fatalf("start candidate: %v %v", ok, err)
|
||||
}
|
||||
if ok, err := first.Record(supervisor.Observation{ReleaseID: "candidate", Ready: false, Reason: "candidate crashed", ObservedAtUnixMillis: 2}); err != nil || !ok {
|
||||
t.Fatalf("record failure: %v %v", ok, err)
|
||||
}
|
||||
second := supervisor.NewPersistentLifeboat(store)
|
||||
if ok, err := second.Rollback(); err != nil || !ok {
|
||||
t.Fatalf("rollback after restart: %v %v", ok, err)
|
||||
}
|
||||
state, err := second.Snapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if state.Current.ID != "good" || state.Current.State != supervisor.ReleaseRolledBack || state.LastKnownGood.ID != "good" {
|
||||
t.Fatalf("state=%+v", state)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package execution_outcome_test
|
||||
|
||||
import (
|
||||
"git.cin.su/m/Assistant/contracts/commit"
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReportedExecutionSuccessIsNotWorldVerification(t *testing.T) {
|
||||
r := commit.ExecutionReceipt{ID: "r", ActionAttemptRef: "a", ExecutorImplementationRef: "impl", RequestDigest: "d", ExecutorResult: commit.ExecutorReportedSuccess, ReconciliationKey: "k", DispatchAtUnixMillis: 1}
|
||||
if !r.Valid() {
|
||||
t.Fatal("receipt invalid")
|
||||
}
|
||||
o := epistemics.WorldOutcomeAssessment{ID: "o", PrincipalID: "p", ActionAttemptRef: "a", State: epistemics.OutcomeUnverified, ConfidenceMillis: 0, AssessedAt: 1}
|
||||
if !o.Valid() {
|
||||
t.Fatal("unverified assessment invalid")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user