Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33365ce7ee | ||
|
|
5017a854ee | ||
|
|
839b35f310 | ||
|
|
96687140ce | ||
|
|
b282038949 | ||
|
|
4de506a2af | ||
|
|
36272b9036 | ||
|
|
a779568622 | ||
|
|
243a11490b | ||
|
|
26207d93ef | ||
|
|
42871f2e47 | ||
|
|
6ea1d4b729 | ||
|
|
c6d1ebf36f | ||
|
|
25d8d6d219 | ||
|
|
6cbef9af41 | ||
|
|
2fe9802e5a |
@@ -25,6 +25,121 @@ var (
|
|||||||
ErrDevelopmentCandidateConflict = errors.New("development candidate conflict")
|
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) {
|
func OpenWork(ctx context.Context, config ContinuityConfig) (*WorkRepository, error) {
|
||||||
pool, err := openPool(ctx, config)
|
pool, err := openPool(ctx, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -533,7 +533,7 @@ func main() {
|
|||||||
log.Printf("interaction principal history loaded: %d utterances", len(recentUserHistory))
|
log.Printf("interaction principal history loaded: %d utterances", len(recentUserHistory))
|
||||||
}
|
}
|
||||||
conversationHistory = append(conversationHistory, recentUserHistory...)
|
conversationHistory = append(conversationHistory, recentUserHistory...)
|
||||||
personalMemory := interactiondomain.CompilePersonalMemory(conversationHistory)
|
gen2Context := interactiondomain.BuildGen2Context(conversationHistory)
|
||||||
if !creativeUserRequest && locationReply == "" {
|
if !creativeUserRequest && locationReply == "" {
|
||||||
if continuation := interactiondomain.ContinueLocalSearch(effectiveTaskMessage, conversationHistory); continuation != "" {
|
if continuation := interactiondomain.ContinueLocalSearch(effectiveTaskMessage, conversationHistory); continuation != "" {
|
||||||
continuationMessage = continuation
|
continuationMessage = continuation
|
||||||
@@ -829,7 +829,7 @@ func main() {
|
|||||||
reply, backed := renderResearchReply(searchQuery, result, researchErr)
|
reply, backed := renderResearchReply(searchQuery, result, researchErr)
|
||||||
if !backed && cognitiveAvailable {
|
if !backed && cognitiveAvailable {
|
||||||
answerCtx, cancelAnswer := context.WithTimeout(r.Context(), 12*time.Second)
|
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,
|
SpeechAct: string(semantic.Interpretation.SpeechAct), CapabilityHints: toCapabilityKinds(semantic.CapabilityHints), RequestedOutcome: semantic.Interpretation.RequestedOutcome, InformationRequestKind: semantic.InformationRequestKind, ConfidenceMillis: semantic.Interpretation.ConfidenceMillis,
|
||||||
})
|
})
|
||||||
cancelAnswer()
|
cancelAnswer()
|
||||||
@@ -869,7 +869,7 @@ func main() {
|
|||||||
canGenerateTextReply := shouldGenerateNonMutatingTextDirective(semantic, routePlan, helpPath, creativeRequest) && planningReply == deterministicReply
|
canGenerateTextReply := shouldGenerateNonMutatingTextDirective(semantic, routePlan, helpPath, creativeRequest) && planningReply == deterministicReply
|
||||||
if (planningReply == "" && needsCognition && semantic.Interpretation.SpeechAct == interaction.SpeechActQuestion || canGenerateTextReply) && !hasChatRoute(routePlan, chatRouteResearch) && !creativeRequest {
|
if (planningReply == "" && needsCognition && semantic.Interpretation.SpeechAct == interaction.SpeechActQuestion || canGenerateTextReply) && !hasChatRoute(routePlan, chatRouteResearch) && !creativeRequest {
|
||||||
answerCtx, cancelAnswer := context.WithTimeout(r.Context(), 12*time.Second)
|
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,
|
SpeechAct: string(semantic.Interpretation.SpeechAct), CapabilityHints: toCapabilityKinds(semantic.CapabilityHints), RequestedOutcome: semantic.Interpretation.RequestedOutcome, InformationRequestKind: semantic.InformationRequestKind, ConfidenceMillis: semantic.Interpretation.ConfidenceMillis,
|
||||||
})
|
})
|
||||||
cancelAnswer()
|
cancelAnswer()
|
||||||
@@ -992,7 +992,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
if strings.TrimSpace(planningReply) != "" {
|
if strings.TrimSpace(planningReply) != "" {
|
||||||
reviewContext, cancelReview := context.WithTimeout(r.Context(), 12*time.Second)
|
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),
|
SpeechAct: string(semantic.Interpretation.SpeechAct),
|
||||||
CapabilityHints: toCapabilityKinds(semantic.CapabilityHints),
|
CapabilityHints: toCapabilityKinds(semantic.CapabilityHints),
|
||||||
RequestedOutcome: semantic.Interpretation.RequestedOutcome,
|
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"))
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -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"}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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 ConflictState string
|
||||||
type EvidenceKind string
|
type EvidenceKind string
|
||||||
type ResearchFindingState string
|
type ResearchFindingState string
|
||||||
|
type ReliabilityState string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FreshnessCurrent FreshnessState = "CURRENT"
|
FreshnessCurrent FreshnessState = "CURRENT"
|
||||||
FreshnessStale FreshnessState = "STALE"
|
FreshnessStale FreshnessState = "STALE"
|
||||||
FreshnessUnknown FreshnessState = "UNKNOWN"
|
FreshnessUnknown FreshnessState = "UNKNOWN"
|
||||||
CoverageComplete CoverageState = "COMPLETE"
|
CoverageComplete CoverageState = "COMPLETE"
|
||||||
CoveragePartial CoverageState = "PARTIAL"
|
CoveragePartial CoverageState = "PARTIAL"
|
||||||
CoverageUnknown CoverageState = "UNKNOWN"
|
CoverageUnknown CoverageState = "UNKNOWN"
|
||||||
ConflictNone ConflictState = "NONE"
|
ConflictNone ConflictState = "NONE"
|
||||||
ConflictPresent ConflictState = "PRESENT"
|
ConflictPresent ConflictState = "PRESENT"
|
||||||
ConflictUnknown ConflictState = "UNKNOWN"
|
ConflictUnknown ConflictState = "UNKNOWN"
|
||||||
EvidenceObserved EvidenceKind = "OBSERVED"
|
EvidenceObserved EvidenceKind = "OBSERVED"
|
||||||
EvidenceAssumed EvidenceKind = "ASSUMED"
|
EvidenceAssumed EvidenceKind = "ASSUMED"
|
||||||
FindingSupported ResearchFindingState = "SUPPORTED"
|
FindingSupported ResearchFindingState = "SUPPORTED"
|
||||||
FindingStale ResearchFindingState = "STALE"
|
FindingStale ResearchFindingState = "STALE"
|
||||||
FindingConflicted ResearchFindingState = "CONFLICTED"
|
FindingConflicted ResearchFindingState = "CONFLICTED"
|
||||||
FindingUnknown ResearchFindingState = "UNKNOWN"
|
FindingUnknown ResearchFindingState = "UNKNOWN"
|
||||||
|
ReliabilityCurrent ReliabilityState = "CURRENT"
|
||||||
|
ReliabilityAging ReliabilityState = "AGING"
|
||||||
|
ReliabilityUnknown ReliabilityState = "UNKNOWN"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResearchPlan struct {
|
type ResearchPlan struct {
|
||||||
@@ -52,6 +56,21 @@ type Source struct {
|
|||||||
Publisher string
|
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 {
|
type SourceSnapshot struct {
|
||||||
ID SourceSnapshotID
|
ID SourceSnapshotID
|
||||||
SourceID SourceID
|
SourceID SourceID
|
||||||
|
|||||||
@@ -6,6 +6,30 @@ type ActorID ids.ID
|
|||||||
type PrincipalID ids.ID
|
type PrincipalID ids.ID
|
||||||
type IdentityID 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
|
// PrincipalBinding keeps the operational actor separate from the interest on
|
||||||
// whose behalf it acts. A Creator is not exempt from this distinction.
|
// whose behalf it acts. A Creator is not exempt from this distinction.
|
||||||
type PrincipalBinding struct {
|
type PrincipalBinding struct {
|
||||||
|
|||||||
@@ -25,6 +25,14 @@ type SemanticApplicability string
|
|||||||
type SemanticCandidateSource string
|
type SemanticCandidateSource string
|
||||||
type SemanticCandidateSelectionState string
|
type SemanticCandidateSelectionState string
|
||||||
type InformationRequestKind string
|
type InformationRequestKind string
|
||||||
|
type ConfigurationCommandKind string
|
||||||
|
type SpeakerAttribution struct {
|
||||||
|
SourceRef string
|
||||||
|
PrincipalCandidate string
|
||||||
|
ConfidenceMillis int64
|
||||||
|
Authenticated bool
|
||||||
|
ObservationRef string
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ChannelWebChat Channel = "WEB_CHAT"
|
ChannelWebChat Channel = "WEB_CHAT"
|
||||||
@@ -75,6 +83,8 @@ const (
|
|||||||
InformationRequestMemoryInventory InformationRequestKind = "MEMORY_INVENTORY"
|
InformationRequestMemoryInventory InformationRequestKind = "MEMORY_INVENTORY"
|
||||||
InformationRequestTimeDifference InformationRequestKind = "TIME_DIFFERENCE"
|
InformationRequestTimeDifference InformationRequestKind = "TIME_DIFFERENCE"
|
||||||
InformationRequestResearchFailureExplanation InformationRequestKind = "RESEARCH_FAILURE_EXPLANATION"
|
InformationRequestResearchFailureExplanation InformationRequestKind = "RESEARCH_FAILURE_EXPLANATION"
|
||||||
|
ConfigurationCommandSet ConfigurationCommandKind = "SET"
|
||||||
|
ConfigurationCommandQuery ConfigurationCommandKind = "QUERY"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conversation struct {
|
type Conversation struct {
|
||||||
@@ -106,6 +116,18 @@ type OriginalHumanIntent struct {
|
|||||||
RecordedAtUnixMillis int64
|
RecordedAtUnixMillis int64
|
||||||
Text string
|
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 {
|
type InteractionRelation struct {
|
||||||
ID InteractionRelationID
|
ID InteractionRelationID
|
||||||
FromUtteranceID HumanUtteranceID
|
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 WorldObjectID ids.StableID
|
||||||
type KindDefinitionID ids.StableID
|
type KindDefinitionID ids.StableID
|
||||||
type WorldAliasID 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 FacetDefinitionID ids.StableID
|
||||||
type RelationDefinitionID ids.StableID
|
type RelationDefinitionID ids.StableID
|
||||||
type ResolutionDecisionID 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-010-context-compiler-v2-with-budgets-coverage-and-iterative-retrieval.json","task_id":"GEN2-010","task_revision":1}
|
{"task_file":"dev/tasks/GEN2-024-multi-person-realms-identity-boundaries-and-contextual-source-trust.json","task_id":"GEN2-024","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,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