feat(gen2): add experience skill reflex lifecycle

This commit is contained in:
m
2026-08-30 21:56:33 +03:00
parent a07aba056d
commit 8ec7c7419f
17 changed files with 523 additions and 32 deletions
+50 -7
View File
@@ -2,6 +2,7 @@ package postgres
import (
"context"
"encoding/json"
"errors"
"fmt"
@@ -305,12 +306,14 @@ func (r *CapabilityRepository) SaveSkillProfile(ctx context.Context, value capab
_, err = tx.Exec(ctx, `INSERT INTO capability.skill_profiles (
skill_profile_id,revision,name,description,applicable_context,implementation_ref,implementation_version,status,
supporting_experience_refs,known_failure_experience_refs,confidence_millis,last_validated_unix_millis,
observed_failure_count,observed_correction_count,recorded_at_unix_millis)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15) ON CONFLICT DO NOTHING`,
observed_failure_count,observed_correction_count,recorded_at_unix_millis,applicability,dependency_refs,validation_evidence_refs,
independent_evaluation_refs,known_failure_conditions,verified_outcome_count,must_not_worsen_satisfied)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22) ON CONFLICT DO NOTHING`,
value.ID, value.Revision, value.Name, value.Description, value.ApplicableContext, value.ImplementationRef,
value.ImplementationVersion, value.Status, value.SupportingExperienceRefs, value.KnownFailureExperienceRefs,
value.ConfidenceMillis, value.LastValidatedUnixMillis, value.ObservedFailureCount, value.ObservedCorrectionCount,
value.RecordedAtUnixMillis)
value.RecordedAtUnixMillis, mustJSON(value.Applicability), value.DependencyRefs, value.ValidationEvidenceRefs,
value.IndependentEvaluationRefs, value.KnownFailureConditions, value.VerifiedOutcomeCount, value.MustNotWorsenSatisfied)
if err != nil {
return err
}
@@ -329,13 +332,16 @@ func (r *CapabilityRepository) LoadSkillProfile(ctx context.Context, id capabili
err := r.pool.QueryRow(ctx, `SELECT
p.skill_profile_id,p.revision,p.name,p.description,p.applicable_context,p.implementation_ref,p.implementation_version,p.status,
p.supporting_experience_refs,p.known_failure_experience_refs,p.confidence_millis,p.last_validated_unix_millis,
p.observed_failure_count,p.observed_correction_count,p.recorded_at_unix_millis
p.observed_failure_count,p.observed_correction_count,p.recorded_at_unix_millis,p.applicability,p.dependency_refs,
p.validation_evidence_refs,p.independent_evaluation_refs,p.known_failure_conditions,p.verified_outcome_count,p.must_not_worsen_satisfied
FROM capability.skill_profile_heads h
JOIN capability.skill_profiles p ON p.skill_profile_id=h.skill_profile_id AND p.revision=h.revision
WHERE p.skill_profile_id=$1`, id).Scan(&value.ID, &value.Revision, &value.Name, &value.Description, &value.ApplicableContext,
&value.ImplementationRef, &value.ImplementationVersion, &value.Status, &value.SupportingExperienceRefs,
&value.KnownFailureExperienceRefs, &value.ConfidenceMillis, &value.LastValidatedUnixMillis,
&value.ObservedFailureCount, &value.ObservedCorrectionCount, &value.RecordedAtUnixMillis)
&value.ObservedFailureCount, &value.ObservedCorrectionCount, &value.RecordedAtUnixMillis, newJSONTarget(&value.Applicability),
&value.DependencyRefs, &value.ValidationEvidenceRefs, &value.IndependentEvaluationRefs, &value.KnownFailureConditions,
&value.VerifiedOutcomeCount, &value.MustNotWorsenSatisfied)
if err != nil {
return capability.SkillProfile{}, fmt.Errorf("load skill profile: %w", err)
}
@@ -349,7 +355,8 @@ func (r *CapabilityRepository) RecentSkillProfiles(ctx context.Context, limit in
rows, err := r.pool.Query(ctx, `SELECT
p.skill_profile_id,p.revision,p.name,p.description,p.applicable_context,p.implementation_ref,p.implementation_version,p.status,
p.supporting_experience_refs,p.known_failure_experience_refs,p.confidence_millis,p.last_validated_unix_millis,
p.observed_failure_count,p.observed_correction_count,p.recorded_at_unix_millis
p.observed_failure_count,p.observed_correction_count,p.recorded_at_unix_millis,p.applicability,p.dependency_refs,
p.validation_evidence_refs,p.independent_evaluation_refs,p.known_failure_conditions,p.verified_outcome_count,p.must_not_worsen_satisfied
FROM capability.skill_profile_heads h
JOIN capability.skill_profiles p ON p.skill_profile_id=h.skill_profile_id AND p.revision=h.revision
ORDER BY h.updated_at DESC, p.skill_profile_id LIMIT $1`, limit)
@@ -363,7 +370,9 @@ func (r *CapabilityRepository) RecentSkillProfiles(ctx context.Context, limit in
if err := rows.Scan(&value.ID, &value.Revision, &value.Name, &value.Description, &value.ApplicableContext,
&value.ImplementationRef, &value.ImplementationVersion, &value.Status, &value.SupportingExperienceRefs,
&value.KnownFailureExperienceRefs, &value.ConfidenceMillis, &value.LastValidatedUnixMillis,
&value.ObservedFailureCount, &value.ObservedCorrectionCount, &value.RecordedAtUnixMillis); err != nil {
&value.ObservedFailureCount, &value.ObservedCorrectionCount, &value.RecordedAtUnixMillis, newJSONTarget(&value.Applicability),
&value.DependencyRefs, &value.ValidationEvidenceRefs, &value.IndependentEvaluationRefs, &value.KnownFailureConditions,
&value.VerifiedOutcomeCount, &value.MustNotWorsenSatisfied); err != nil {
return nil, err
}
result = append(result, value)
@@ -371,6 +380,40 @@ func (r *CapabilityRepository) RecentSkillProfiles(ctx context.Context, limit in
return result, rows.Err()
}
func (r *CapabilityRepository) SaveReflexRevision(ctx context.Context, reflex capability.Reflex, value capability.ReflexRevision) error {
_, err := r.pool.Exec(ctx, `INSERT INTO capability.reflex_revisions (
reflex_id,revision,principal_id,source_skill_id,source_skill_revision,kind,applicability,implementation_ref,implementation_version,artifact_digest,
dependency_refs,validation_evidence_refs,known_failure_experience_refs,shadow_policy,revalidation_policy,last_validated_at,lifecycle,
supersedes_revision,execution_count,failure_count,divergence_count,recorded_at)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22) ON CONFLICT DO NOTHING`,
value.ReflexID, value.Revision, reflex.PrincipalID, reflex.SourceSkillRef, value.SourceSkillRevision, value.Kind, mustJSON(value.Applicability), value.ImplementationRef, value.ImplementationVersion, value.ArtifactDigest,
value.DependencyRefs, value.ValidationEvidenceRefs, value.KnownFailureExperienceRefs, mustJSON(value.Shadow), mustJSON(value.Revalidation), value.LastValidatedAt, value.Lifecycle,
value.SupersedesRevision, value.ExecutionCount, value.FailureCount, value.DivergenceCount, value.RecordedAt)
if err != nil {
return err
}
_, err = r.pool.Exec(ctx, `INSERT INTO capability.reflex_heads(reflex_id,revision,lifecycle) VALUES($1,$2,$3)
ON CONFLICT(reflex_id) DO UPDATE SET revision=EXCLUDED.revision,lifecycle=EXCLUDED.lifecycle,updated_at=CURRENT_TIMESTAMP
WHERE capability.reflex_heads.revision < EXCLUDED.revision`, value.ReflexID, value.Revision, value.Lifecycle)
return err
}
func mustJSON(value any) []byte { encoded, _ := json.Marshal(value); return encoded }
type jsonTarget struct{ target any }
func newJSONTarget(target any) *jsonTarget { return &jsonTarget{target: target} }
func (v *jsonTarget) Scan(source any) error {
if source == nil {
return nil
}
bytes, ok := source.([]byte)
if !ok {
bytes = []byte(fmt.Sprint(source))
}
return json.Unmarshal(bytes, v.target)
}
func (r *CapabilityRepository) LoadBinding(ctx context.Context, id capability.CapabilityBindingID, revision int64) (capability.Binding, error) {
var binding capability.Binding
err := r.pool.QueryRow(ctx, `SELECT binding_id,capability_id,owner_principal_id,revision,state,https_origin,delivery_path,receipt_path,
+79
View File
@@ -0,0 +1,79 @@
package postgres
import (
"context"
"encoding/json"
selfcontract "git.cin.su/m/Assistant/contracts/self"
"github.com/jackc/pgx/v5/pgxpool"
)
// SelfRepository persists only SELF-owned derived assessments and ledger
// evidence. It never evaluates, promotes or changes Capability state.
type SelfRepository struct{ pool *pgxpool.Pool }
func OpenSelf(ctx context.Context, config ContinuityConfig) (*SelfRepository, error) {
pool, err := openPool(ctx, config)
if err != nil {
return nil, err
}
return &SelfRepository{pool: pool}, nil
}
func (r *SelfRepository) Close() { r.pool.Close() }
func (r *SelfRepository) SaveImprovementNeed(ctx context.Context, value selfcontract.ImprovementNeed) error {
subject, err := json.Marshal(value.SubjectRef)
if err != nil {
return err
}
evidence, err := json.Marshal(value.EvidenceRefs)
if err != nil {
return err
}
_, err = r.pool.Exec(ctx, `INSERT INTO self.improvement_needs(improvement_need_id,principal_id,subject_ref,problem_summary,evidence_refs,expected_benefit,priority,state,created_at_unix_millis)
VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9) ON CONFLICT DO NOTHING`, value.ID, value.PrincipalID, subject, value.ProblemSummary, evidence, value.ExpectedBenefit, value.Priority, value.State, value.CreatedAt)
return err
}
func (r *SelfRepository) SaveLearningLedger(ctx context.Context, value selfcontract.LearningLedgerEntry) error {
evidence, err := json.Marshal(value.EvidenceRefs)
if err != nil {
return err
}
improvement, err := json.Marshal(value.ImprovementNeedRef)
if err != nil {
return err
}
proposed, err := json.Marshal(value.ProposedChangeRef)
if err != nil {
return err
}
evaluations, err := json.Marshal(value.ActualEvaluationRefs)
if err != nil {
return err
}
gate, err := json.Marshal(value.DevelopmentGateRef)
if err != nil {
return err
}
before, err := json.Marshal(value.BeforeVersionRefs)
if err != nil {
return err
}
after, err := json.Marshal(value.AfterVersionRefs)
if err != nil {
return err
}
sideEffects, err := json.Marshal(value.SideEffects)
if err != nil {
return err
}
rollback, err := json.Marshal(value.RollbackRef)
if err != nil {
return err
}
_, err = r.pool.Exec(ctx, `INSERT INTO self.learning_ledger(entry_id,principal_id,improvement_need_ref,observed_problem,evidence_refs,diagnosis,hypothesis,proposed_change_ref,expected_improvement,evaluation_refs,development_gate_ref,before_version_refs,after_version_refs,decision,side_effects,rollback_ref,recorded_at_unix_millis)
VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17) ON CONFLICT DO NOTHING`, value.ID, value.PrincipalID, improvement, value.ObservedProblem, evidence, value.Diagnosis, value.Hypothesis, proposed, value.ExpectedImprovement, evaluations, gate, before, after, value.Decision, sideEffects, rollback, value.RecordedAt)
return err
}
+18 -3
View File
@@ -27,15 +27,30 @@ type StrategyDecision struct {
Escalate bool
}
// StrategyEligibility is supplied by Capability lifecycle resolution. It keeps
// a requested cheap route from becoming an entitlement to use stale evidence.
type StrategyEligibility struct {
RuleEligible bool
ReflexEligible bool
SkillEligible bool
}
func (r StrategyRequest) Select() StrategyDecision {
return r.SelectEligible(StrategyEligibility{RuleEligible: true, ReflexEligible: true, SkillEligible: true})
}
func (r StrategyRequest) SelectEligible(eligible StrategyEligibility) 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 {
if eligible.RuleEligible && r.RequiredQuality <= 500 && r.CostBudgetMicros > 0 {
return StrategyDecision{Path: PathRule, Reason: "bounded low-risk path"}
}
if r.PreferLowLatency {
if eligible.ReflexEligible && r.PreferLowLatency {
return StrategyDecision{Path: PathReflex, Reason: "latency preference with later escalation"}
}
return StrategyDecision{Path: PathSkill, Reason: "validated reusable strategy"}
if eligible.SkillEligible {
return StrategyDecision{Path: PathSkill, Reason: "validated reusable strategy"}
}
return StrategyDecision{Path: PathSmallLocal, Reason: "no eligible reflex or skill; escalate to reasoning", Escalate: true}
}
+16
View File
@@ -10,6 +10,7 @@ const (
ReflexActive ReflexLifecycle = "ACTIVE"
ReflexDegraded ReflexLifecycle = "DEGRADED"
ReflexStale ReflexLifecycle = "STALE"
ReflexRevalidating ReflexLifecycle = "REVALIDATING"
ReflexSuspended ReflexLifecycle = "SUSPENDED"
ReflexRetired ReflexLifecycle = "RETIRED"
ReflexDeterministicCode ReflexKind = "DETERMINISTIC_CODE"
@@ -58,4 +59,19 @@ type ReflexRevision struct {
LastValidatedAt int64
Lifecycle ReflexLifecycle
RecordedAt int64
SupersedesRevision int64
ExecutionCount int64
FailureCount int64
DivergenceCount int64
}
// ShadowComparison is evidence produced by a bounded shadow sample. It is not
// permission to rewrite a Reflex; SELF/governed evaluation decide next steps.
type ShadowComparison struct {
ReflexID ReflexID
ReflexRevision int64
EvidenceRefs []SkillEvidenceRef
VerifierPassed bool
Diverged bool
ObservedAt int64
}
+10
View File
@@ -70,6 +70,7 @@ const (
SkillCandidate SkillLifecycleStatus = "CANDIDATE"
SkillEvaluating SkillLifecycleStatus = "EVALUATING"
SkillValidated SkillLifecycleStatus = "VALIDATED"
SkillActive SkillLifecycleStatus = "ACTIVE"
SkillDegraded SkillLifecycleStatus = "DEGRADED"
SkillSuspended SkillLifecycleStatus = "SUSPENDED"
SkillRetired SkillLifecycleStatus = "RETIRED"
@@ -202,6 +203,15 @@ type SkillProfile struct {
ObservedFailureCount int64
ObservedCorrectionCount int64
RecordedAtUnixMillis int64
// The fields below are the additive Gen2 lifecycle view. The older fields
// remain a compatibility projection; they are not another Skill registry.
Applicability ApplicabilitySpec
DependencyRefs []string
ValidationEvidenceRefs []SkillEvidenceRef
IndependentEvaluationRefs []SkillEvidenceRef
KnownFailureConditions []string
VerifiedOutcomeCount int64
MustNotWorsenSatisfied bool
}
type SkillOutcomeObservation struct {
@@ -0,0 +1,79 @@
BEGIN;
SET ROLE capability_role;
ALTER TABLE capability.skill_profiles
ADD COLUMN IF NOT EXISTS applicability JSONB NOT NULL DEFAULT '{}'::jsonb,
ADD COLUMN IF NOT EXISTS dependency_refs TEXT[] NOT NULL DEFAULT '{}',
ADD COLUMN IF NOT EXISTS validation_evidence_refs TEXT[] NOT NULL DEFAULT '{}',
ADD COLUMN IF NOT EXISTS independent_evaluation_refs TEXT[] NOT NULL DEFAULT '{}',
ADD COLUMN IF NOT EXISTS known_failure_conditions TEXT[] NOT NULL DEFAULT '{}',
ADD COLUMN IF NOT EXISTS verified_outcome_count BIGINT NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS must_not_worsen_satisfied BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE capability.skill_profile_heads DROP CONSTRAINT IF EXISTS skill_profile_heads_status_check;
ALTER TABLE capability.skill_profile_heads
ADD CONSTRAINT skill_profile_heads_status_check CHECK (status IN ('CANDIDATE','EVALUATING','VALIDATED','ACTIVE','DEGRADED','SUSPENDED','RETIRED'));
CREATE TABLE IF NOT EXISTS capability.reflex_revisions (
reflex_id TEXT NOT NULL,
revision BIGINT NOT NULL CHECK (revision > 0),
principal_id TEXT NOT NULL,
source_skill_id TEXT NOT NULL,
source_skill_revision BIGINT NOT NULL CHECK (source_skill_revision > 0),
kind TEXT NOT NULL,
applicability JSONB NOT NULL,
implementation_ref TEXT NOT NULL,
implementation_version TEXT NOT NULL DEFAULT '',
artifact_digest TEXT NOT NULL DEFAULT '',
dependency_refs TEXT[] NOT NULL DEFAULT '{}',
validation_evidence_refs TEXT[] NOT NULL DEFAULT '{}',
known_failure_experience_refs TEXT[] NOT NULL DEFAULT '{}',
shadow_policy JSONB NOT NULL DEFAULT '{}'::jsonb,
revalidation_policy JSONB NOT NULL DEFAULT '{}'::jsonb,
last_validated_at BIGINT NOT NULL DEFAULT 0,
lifecycle TEXT NOT NULL,
supersedes_revision BIGINT NOT NULL DEFAULT 0,
execution_count BIGINT NOT NULL DEFAULT 0,
failure_count BIGINT NOT NULL DEFAULT 0,
divergence_count BIGINT NOT NULL DEFAULT 0,
recorded_at BIGINT NOT NULL CHECK (recorded_at > 0),
PRIMARY KEY (reflex_id, revision),
CHECK (lifecycle IN ('CANDIDATE','VALIDATING','ACTIVE','DEGRADED','STALE','REVALIDATING','SUSPENDED','RETIRED'))
);
CREATE TABLE IF NOT EXISTS capability.reflex_heads (
reflex_id TEXT PRIMARY KEY,
revision BIGINT NOT NULL,
lifecycle TEXT NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
GRANT SELECT, INSERT, UPDATE ON capability.reflex_revisions, capability.reflex_heads TO capability_role;
RESET ROLE;
SET ROLE self_role;
CREATE TABLE IF NOT EXISTS self.improvement_needs (
improvement_need_id TEXT PRIMARY KEY,
principal_id TEXT NOT NULL,
subject_ref JSONB NOT NULL,
problem_summary TEXT NOT NULL,
evidence_refs JSONB NOT NULL,
expected_benefit TEXT NOT NULL,
priority INTEGER NOT NULL,
state TEXT NOT NULL,
created_at_unix_millis BIGINT NOT NULL
);
ALTER TABLE self.learning_ledger
ADD COLUMN IF NOT EXISTS improvement_need_ref JSONB,
ADD COLUMN IF NOT EXISTS proposed_change_ref JSONB,
ADD COLUMN IF NOT EXISTS expected_improvement TEXT NOT NULL DEFAULT '',
ADD COLUMN IF NOT EXISTS evaluation_refs JSONB NOT NULL DEFAULT '[]'::jsonb,
ADD COLUMN IF NOT EXISTS development_gate_ref JSONB,
ADD COLUMN IF NOT EXISTS before_version_refs JSONB NOT NULL DEFAULT '[]'::jsonb,
ADD COLUMN IF NOT EXISTS after_version_refs JSONB NOT NULL DEFAULT '[]'::jsonb,
ADD COLUMN IF NOT EXISTS side_effects JSONB NOT NULL DEFAULT '[]'::jsonb,
ADD COLUMN IF NOT EXISTS rollback_ref JSONB;
GRANT SELECT, INSERT, UPDATE ON self.improvement_needs, self.learning_ledger TO self_role;
RESET ROLE;
COMMIT;
+1 -1
View File
@@ -1 +1 @@
{"task_file":"dev/tasks/GEN2-EVOLVE-002-iterative-retrieval-and-cognitive-memory-continuation.json","task_id":"GEN2-EVOLVE-002","task_revision":2}
{"task_file":"dev/tasks/GEN2-EVOLVE-003-experience-skill-reflex-learning-lifecycle.json","task_id":"GEN2-EVOLVE-003","task_revision":3}
@@ -0,0 +1,39 @@
{
"task_id": "GEN2-EVOLVE-003",
"task_revision": 3,
"architecture_revision": "ARCH-GEN2-000",
"base_repo_revision": "gen2-integration",
"risk_tier": "E",
"intent": "Implement the bounded evidence-governed Experience to Skill to Reflex learning lifecycle using existing Epistemics, Capability and SELF owners: candidate, independent validation, applicable routing, bounded shadow/revalidation, downgrade/retirement and Learning Ledger evidence, without autonomous production mutation.",
"requirement_ids": ["REQ-GEN2-EVOLVE-003-EXPERIENCE-SKILL-REFLEX"],
"allowed_change_scope": {
"modules": ["capability", "self", "core-runtime learning", "Gen2 evidence and governance"],
"paths": [
"contracts/capability/skill_v2.go", "contracts/capability/reflex_v2.go", "contracts/capability/resolution.go", "contracts/capability/intelligence.go", "contracts/self/types.go",
"modules/capability/domain/skill.go", "modules/capability/domain/skill_test.go", "modules/capability/domain/reflex.go", "modules/capability/domain/reflex_test.go", "modules/capability/domain/intelligence_test.go",
"modules/self/domain/self.go", "modules/self/domain/self_test.go",
"adapters/postgres/capability_repository.go", "adapters/postgres/self_repository.go", "adapters/postgres/capability_repository_test.go", "adapters/postgres/self_repository_test.go",
"database/migrations/054_experience_skill_reflex_learning_lifecycle.sql",
"cmd/core-runtime/learning.go", "cmd/core-runtime/learning_test.go",
"dev/ACTIVE_TASK.json", "dev/tasks/GEN2-EVOLVE-003-experience-skill-reflex-learning-lifecycle.json", "docs/evidence/**", "docs/PROJECT_STATUS.md"
]
},
"forbidden_change_scope": {
"paths": ["architecture/**", "security/**", "go.mod", "go.sum", "deploy/**", "cmd/creator-web/**", "cmd/cognitive-worker/**", "adapters/models/**", "modules/epistemics/**", "adapters/postgres/epistemics_repository.go", "PersonalMemory write paths", "GEN2-028", "GEN2-029", "GEN2-030", "Gen3/**"]
},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 1},
"contracts": {
"consume": ["existing Epistemics ExperienceRecord/ReflectionCandidate", "existing Capability Skill V2/Reflex V2", "existing SELF LearningLedger/ImprovementNeed", "existing Intelligence Strategy"],
"may_change": ["Capability and SELF typed lifecycle/evidence fields necessary for bounded candidate, validation, routing and revalidation semantics only"]
},
"semantic_owner": "Epistemics remains canonical owner of Experience and Outcome evidence; Capability remains sole canonical owner of Skill and Reflex lifecycle; SELF remains canonical owner of ImprovementNeed, competence/weakness assessments and Learning Ledger. Core Runtime only records evidence through owner repositories and never self-promotes production behavior.",
"canonical_state": ["Experience/Outcome evidence is referenced, never copied as a new source of truth", "Capability writes Skill/Reflex revisions and heads; SELF writes Learning Ledger and ImprovementNeed", "candidate, validation, shadow and revalidation never grant independent production mutation authority", "no new Memory owner, parallel Skill registry or cross-owner SQL join"],
"side_effects_and_privileges": ["No external mutation may bypass Authority -> Commit -> Executor -> verified Outcome.", "SELF may observe, evaluate and propose only; production promotion remains behind the existing governed development gates and independent evaluation.", "No provider invocation, privilege expansion, UI/browser change or V1 retirement is authorized."],
"persistence_and_migrations": ["054 is the next verified global migration number after 053; it is additive and uses existing capability_role and self_role only", "revision 3 sets devctl's database-surface compatibility delta to one solely because devctl requires it for any migration; no database owner role is created", "persist Skill/Reflex revision/head and SELF learning evidence needed for restart-safe lifecycle state; existing historical records remain untouched", "rollback disables/refuses non-active Reflex selection and returns to existing Skill/reasoning path without deleting evidence"],
"state_transitions": ["verified positive Experience with a bounded pattern and provenance -> Candidate Skill", "independent validation/replay evidence and must-not-worsen criteria -> Validated Skill -> Active eligible routing", "frequent stable Active Skill with deterministic bounded implementation and verifier -> Candidate Reflex -> Validated/Active Reflex", "dependency change, stale evidence, correction, failure trend or shadow divergence -> Stale/Suspicious -> revalidation request -> Active, rewrite proposal or Retired", "Reflex decline/failure -> Skill -> reasoning; no execution can bypass Authority/Commit/Executor"],
"failure_retry_restart": ["one success never promotes Candidate directly to Active Skill or Reflex", "shadow is sampled/bounded and never delays ordinary low-risk response; divergence records evidence and ImprovementNeed rather than rewriting Reflex", "revalidation is bounded and never retries endlessly; real failures remain fail-closed and escalate", "durable revisions, validation evidence and Ledger survive restart; no direct provider invocation or process-local canonical state"],
"assumptions": ["task branch starts at gen2-integration a07aba0", "UI/browser acceptance remains deferred by owner; V1 compatibility remains retained", "a verified inspection found 054 free, existing SkillProfile persistence, Reflex V2 contract without durable lifecycle persistence, and SELF typed ledger without a repository", "revision 2 adds only contracts/capability/resolution.go because the existing canonical SkillProfile is declared there and its durable evidence fields must be additive rather than creating a parallel Skill registry", "revision 3 satisfies devctl's migration compatibility gate without creating any role or privilege", "automatic arbitrary production code generation is not in scope; candidate executable strategies must use an existing bounded implementation reference and governed development remains the only production-promotion route"],
"acceptance_scenarios": ["EVOLVE-003-SCN-01 verified outcome and pattern create Candidate Skill with source Experience/Outcome references", "EVOLVE-003-SCN-02 independent replay/evaluation evidence promotes Skill only after repeatability and must-not-worsen checks", "EVOLVE-003-SCN-03 applicable fresh Active Skill is routed; inapplicable, stale or weak Skill escalates to reasoning", "EVOLVE-003-SCN-04 sufficient stable evidence creates Candidate Reflex; verified bounded Reflex becomes Active and retains fallback", "EVOLVE-003-SCN-05 dependency change, correction, failure trend and stale validation trigger bounded revalidation/downgrade rather than silent use", "EVOLVE-003-SCN-06 sampled shadow divergence records ImprovementNeed/LearningLedger evidence and does not rewrite production Reflex", "EVOLVE-003-SCN-07 user correction links the affected Skill/Reflex revision and preserves conditional known failure evidence", "EVOLVE-003-SCN-08 retired Reflex is excluded from routing while all evidence/history remains queryable"],
"required_tests": ["FAST: targeted go test and go vet for changed capability, self, core-runtime and postgres packages", "FAST: devctl preflight, check-diff, independent governance, architecture check, git diff --check", "MILESTONE: focused lifecycle/persistence/restart and escalation tests only; no browser, production canary or FULL suite"],
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_CONFLICT", "REQUIRES_MIGRATION_DECISION"]
}
+2
View File
@@ -2,6 +2,8 @@
## Gen2 M6 checkpoint — 2026-08-30
- `GEN2-EVOLVE-003 = MILESTONE_ACCEPTED`: Experience evidence now enters an evidence-governed Candidate Skill → independent validation → Active Skill → bounded Candidate Reflex lifecycle. Reflex divergence/revalidation records SELF proposals and Learning Ledger evidence; it cannot autonomously rewrite or promote production. Evidence: `docs/evidence/gen2-evolve-003-milestone-2026-08-30.md`. UI and V1 compatibility remain unchanged.
- `GEN2-EVOLVE-002 = MILESTONE_ACCEPTED`: one bounded typed cognitive memory-continuation round is available through Core and the existing retrieval fabric. Evidence: `docs/evidence/gen2-evolve-002-milestone-2026-08-30.md`. UI and V1 compatibility remain unchanged.
- `GEN2-EVOLVE-001 = MILESTONE_ACCEPTED`: bounded adaptive retrieval adds task-relative freshness, computed salience, positive/negative/contradiction experience lanes and provenance-preserving consolidation candidates. Evidence: `docs/evidence/gen2-evolve-001-milestone-2026-08-30.md`. No V1 path was retired and UI/browser acceptance remains deferred by owner.
@@ -0,0 +1,16 @@
# GEN2-EVOLVE-003 — Experience to Skill to Reflex Learning Lifecycle
- Task/revision: `GEN2-EVOLVE-003 r3`; base `a07aba0`.
- Canonical owners remain unchanged: Epistemics owns Experience/Outcome evidence; Capability owns the single SkillProfile lifecycle and Reflex revisions; SELF owns ImprovementNeed and Learning Ledger. Evidence is referenced by stable refs, not copied into another memory or Skill registry.
- Skill lifecycle: verified positive evidence produces `CANDIDATE`; at least two verified outcomes plus an independent evaluation and must-not-worsen evidence are required for `VALIDATED`; a separate explicit transition makes it `ACTIVE`.
- Routing: an ineligible Reflex falls through to an eligible Skill and then Local reasoning. A degraded, stale, suspended, retired or insufficient-confidence Skill cannot be fast-applied.
- Reflex lifecycle: only an Active Skill with three verified outcomes and a bounded deterministic/rule/workflow implementation can become a Candidate. Independent evaluation promotes it to Active. Sampled shadow divergence marks it `STALE` and creates only a SELF revalidation proposal; it cannot rewrite or promote production code.
- Revalidation triggers cover stale lifecycle, dependency/version change, correction count and validation age. Retired Reflex revisions remain durable history and cannot be selected.
- Persistence: additive migration `054` extends the existing Capability SkillProfile record, adds Capability-owned Reflex revision/head tables, and adds SELF ImprovementNeed/Learning Ledger evidence fields using existing roles. Rollback excludes a non-Active Reflex and falls back to Skill/reasoning; no evidence is deleted.
- Gap intentionally retained: automatic generation of executable production Reflex code is not implemented. It requires a separately authorized governed-development task with independent evaluation and promotion gates.
## Verification
MILESTONE: focused `go test`/`go vet` for Capability domain, SELF domain, PostgreSQL adapter, Core Runtime and Capability contracts; `devctl preflight`, `check-diff`, `independent`, `check-architecture`, migration consistency and `git diff --check`.
Focused regressions cover no one-run promotion, independent validation, Reflex candidate requirements, shadow divergence/revalidation rather than rewrite, Skill-to-reasoning escalation, SELF proposal-only behavior, and historical evidence preservation.
@@ -13,3 +13,13 @@ func TestStrategyUsesRiskAndCostSignals(t *testing.T) {
t.Fatalf("%#v", got)
}
}
func TestUnavailableReflexFallsBackThroughSkillToReasoning(t *testing.T) {
request := capability.StrategyRequest{PreferLowLatency: true, RequiredQuality: 700}
if got := request.SelectEligible(capability.StrategyEligibility{SkillEligible: true}); got.Path != capability.PathSkill {
t.Fatalf("expected Skill fallback, got %#v", got)
}
if got := request.SelectEligible(capability.StrategyEligibility{}); got.Path != capability.PathSmallLocal || !got.Escalate {
t.Fatalf("expected reasoning fallback, got %#v", got)
}
}
+73 -1
View File
@@ -1,19 +1,91 @@
package domain
import (
"errors"
"fmt"
"git.cin.su/m/Assistant/contracts/capability"
)
var ErrInsufficientReflexEvidence = errors.New("insufficient reflex evidence")
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 {
if (v.Lifecycle == capability.ReflexActive || v.Lifecycle == capability.ReflexValidating) && len(v.ValidationEvidenceRefs) == 0 {
return fmt.Errorf("active reflex requires evidence")
}
return nil
}
// CandidateReflex admits only a bounded, deterministic implementation from an
// already active Skill with multiple verified results. It never activates it.
func CandidateReflex(skill capability.SkillProfile, value capability.ReflexRevision, verifierEvidence capability.SkillEvidenceRef, recordedAt int64) (capability.ReflexRevision, error) {
if err := ValidateSkillProfile(skill); err != nil || skill.Status != capability.SkillActive || skill.VerifiedOutcomeCount < 3 || verifierEvidence == "" || recordedAt < 1 {
return capability.ReflexRevision{}, ErrInsufficientReflexEvidence
}
if value.Kind != capability.ReflexDeterministicCode && value.Kind != capability.ReflexRule && value.Kind != capability.ReflexWorkflow && value.Kind != capability.ReflexBoundedAdapter {
return capability.ReflexRevision{}, ErrInsufficientReflexEvidence
}
value.SourceSkillRevision = skill.Revision
value.Lifecycle = capability.ReflexCandidate
value.ValidationEvidenceRefs = appendEvidence(value.ValidationEvidenceRefs, verifierEvidence)
value.LastValidatedAt = 0
value.RecordedAt = recordedAt
if err := ValidateReflexRevision(value); err != nil {
return capability.ReflexRevision{}, err
}
return value, nil
}
func ValidateAndActivateReflex(value capability.ReflexRevision, independentEvidence capability.SkillEvidenceRef, mustNotWorsen bool, recordedAt int64) (capability.ReflexRevision, error) {
if err := ValidateReflexRevision(value); err != nil || value.Lifecycle != capability.ReflexCandidate || independentEvidence == "" || !mustNotWorsen || recordedAt < 1 {
return capability.ReflexRevision{}, ErrInsufficientReflexEvidence
}
next := value
next.Revision++
next.Lifecycle = capability.ReflexActive
next.ValidationEvidenceRefs = appendEvidence(next.ValidationEvidenceRefs, independentEvidence)
next.LastValidatedAt = recordedAt
next.RecordedAt = recordedAt
return next, nil
}
// ObserveShadow keeps normal execution non-blocking. Divergence marks the
// revision stale for governed revalidation; it cannot replace implementation.
func ObserveShadow(value capability.ReflexRevision, comparison capability.ShadowComparison) (capability.ReflexRevision, bool, error) {
if err := ValidateReflexRevision(value); err != nil || comparison.ReflexID != value.ReflexID || comparison.ReflexRevision != value.Revision || comparison.ObservedAt < 1 {
return capability.ReflexRevision{}, false, ErrInsufficientReflexEvidence
}
next := value
next.ExecutionCount++
if comparison.Diverged || !comparison.VerifierPassed {
next.DivergenceCount++
next.Lifecycle = capability.ReflexStale
next.RecordedAt = comparison.ObservedAt
return next, true, nil
}
return next, false, nil
}
func RevalidationRequired(value capability.ReflexRevision, dependencyChanged bool, correctionCount int64, now int64) bool {
if value.Lifecycle == capability.ReflexRetired || value.Lifecycle == capability.ReflexStale || value.Lifecycle == capability.ReflexDegraded {
return true
}
if dependencyChanged || (value.Revalidation.RevalidateAfterCorrectionCount != nil && correctionCount >= *value.Revalidation.RevalidateAfterCorrectionCount) {
return true
}
return value.Revalidation.MaxAgeMillis != nil && value.LastValidatedAt > 0 && now-value.LastValidatedAt >= *value.Revalidation.MaxAgeMillis
}
func appendEvidence(values []capability.SkillEvidenceRef, value capability.SkillEvidenceRef) []capability.SkillEvidenceRef {
for _, existing := range values {
if existing == value {
return values
}
}
return append(values, value)
}
func ReflexFallback(d capability.FastPathDecision) string {
if d.Decision == capability.FastPathApplied {
return "reflex"
+16
View File
@@ -19,3 +19,19 @@ func TestReflexActiveRequiresEvidenceAndFallbacks(t *testing.T) {
t.Fatal("missing escalation")
}
}
func TestReflexRequiresStableSkillAndShadowDivergenceOnlyRequestsReview(t *testing.T) {
skill := capability.SkillProfile{ID: "s", Revision: 4, Name: "bounded", Description: "bounded", Status: capability.SkillActive, ConfidenceMillis: 900, RecordedAtUnixMillis: 1, VerifiedOutcomeCount: 3}
candidate, err := domain.CandidateReflex(skill, capability.ReflexRevision{ReflexID: "r", Revision: 1, Kind: capability.ReflexRule, ImplementationRef: "rule", RecordedAt: 1}, "verifier:success", 2)
if err != nil || candidate.Lifecycle != capability.ReflexCandidate {
t.Fatalf("candidate=%#v err=%v", candidate, err)
}
active, err := domain.ValidateAndActivateReflex(candidate, "evaluation:independent", true, 3)
if err != nil || active.Lifecycle != capability.ReflexActive {
t.Fatalf("active=%#v err=%v", active, err)
}
next, review, err := domain.ObserveShadow(active, capability.ShadowComparison{ReflexID: "r", ReflexRevision: active.Revision, EvidenceRefs: []capability.SkillEvidenceRef{"shadow:1"}, VerifierPassed: false, Diverged: true, ObservedAt: 4})
if err != nil || !review || next.Lifecycle != capability.ReflexStale {
t.Fatalf("next=%#v review=%t err=%v", next, review, err)
}
}
+51 -8
View File
@@ -9,6 +9,7 @@ import (
var ErrInvalidSkillProfile = errors.New("invalid skill profile")
var ErrInvalidFastPathInput = errors.New("invalid fast path input")
var ErrInsufficientSkillEvidence = errors.New("insufficient independent skill evidence")
func ValidateSkillProfile(value capability.SkillProfile) error {
if value.ID == "" || value.Revision < 1 || strings.TrimSpace(value.Name) == "" ||
@@ -17,13 +18,59 @@ func ValidateSkillProfile(value capability.SkillProfile) error {
return ErrInvalidSkillProfile
}
switch value.Status {
case capability.SkillCandidate, capability.SkillEvaluating, capability.SkillValidated, capability.SkillDegraded, capability.SkillSuspended, capability.SkillRetired:
case capability.SkillCandidate, capability.SkillEvaluating, capability.SkillValidated, capability.SkillActive, capability.SkillDegraded, capability.SkillSuspended, capability.SkillRetired:
default:
return ErrInvalidSkillProfile
}
return nil
}
// CandidateFromVerifiedExperience starts a reusable approach but deliberately
// does not validate or activate it. Evidence stays in Epistemics by reference.
func CandidateFromVerifiedExperience(profile capability.SkillProfile, experience capability.SkillEvidenceRef, verified bool, recordedAt int64) (capability.SkillProfile, error) {
if err := ValidateSkillProfile(profile); err != nil || experience == "" || !verified || recordedAt < 1 {
return capability.SkillProfile{}, ErrInvalidSkillProfile
}
next := profile
next.Revision++
next.Status = capability.SkillCandidate
next.SupportingExperienceRefs = appendEvidenceRef(next.SupportingExperienceRefs, experience)
next.VerifiedOutcomeCount++
next.RecordedAtUnixMillis = recordedAt
return next, nil
}
// ValidateSkill requires repeatable verified outcomes plus evidence produced by
// an evaluator other than the candidate creator. It never activates a Skill.
func ValidateSkill(profile capability.SkillProfile, evaluation capability.SkillEvidenceRef, independent bool, mustNotWorsen bool, recordedAt int64) (capability.SkillProfile, error) {
if err := ValidateSkillProfile(profile); err != nil || evaluation == "" || !independent || !mustNotWorsen || recordedAt < 1 {
return capability.SkillProfile{}, ErrInsufficientSkillEvidence
}
if profile.VerifiedOutcomeCount < 2 || len(profile.SupportingExperienceRefs) < 2 {
return capability.SkillProfile{}, ErrInsufficientSkillEvidence
}
next := profile
next.Revision++
next.Status = capability.SkillValidated
next.ValidationEvidenceRefs = appendEvidenceRef(next.ValidationEvidenceRefs, evaluation)
next.IndependentEvaluationRefs = appendEvidenceRef(next.IndependentEvaluationRefs, evaluation)
next.MustNotWorsenSatisfied = true
next.LastValidatedUnixMillis = recordedAt
next.RecordedAtUnixMillis = recordedAt
return next, nil
}
func ActivateSkill(profile capability.SkillProfile, recordedAt int64) (capability.SkillProfile, error) {
if err := ValidateSkillProfile(profile); err != nil || profile.Status != capability.SkillValidated || len(profile.IndependentEvaluationRefs) == 0 || !profile.MustNotWorsenSatisfied || recordedAt < 1 {
return capability.SkillProfile{}, ErrInsufficientSkillEvidence
}
next := profile
next.Revision++
next.Status = capability.SkillActive
next.RecordedAtUnixMillis = recordedAt
return next, nil
}
func ObserveSkillOutcome(profile capability.SkillProfile, observation capability.SkillOutcomeObservation) (capability.SkillProfile, error) {
if err := ValidateSkillProfile(profile); err != nil {
return capability.SkillProfile{}, err
@@ -37,10 +84,6 @@ func ObserveSkillOutcome(profile capability.SkillProfile, observation capability
switch observation.Outcome {
case capability.UsageSucceeded:
next.SupportingExperienceRefs = appendEvidenceRef(next.SupportingExperienceRefs, observation.ExperienceRef)
if next.Status == capability.SkillCandidate || next.Status == capability.SkillEvaluating {
next.Status = capability.SkillValidated
}
next.LastValidatedUnixMillis = observation.ObservedAtUnixMillis
next.ConfidenceMillis = clampConfidence(next.ConfidenceMillis + 25)
case capability.UsageFailed:
next.KnownFailureExperienceRefs = appendEvidenceRef(next.KnownFailureExperienceRefs, observation.ExperienceRef)
@@ -57,7 +100,7 @@ func ObserveSkillOutcome(profile capability.SkillProfile, observation capability
next.KnownFailureExperienceRefs = appendEvidenceRef(next.KnownFailureExperienceRefs, observation.ExperienceRef)
next.ConfidenceMillis = clampConfidence(next.ConfidenceMillis - 200)
}
if next.Status == capability.SkillValidated && (next.ObservedFailureCount >= 3 || next.ObservedCorrectionCount >= 2 || next.ConfidenceMillis < 500) {
if (next.Status == capability.SkillValidated || next.Status == capability.SkillActive) && (next.ObservedFailureCount >= 3 || next.ObservedCorrectionCount >= 2 || next.ConfidenceMillis < 500) {
next.Status = capability.SkillDegraded
}
if next.Status == capability.SkillDegraded && next.ConfidenceMillis < 250 {
@@ -80,7 +123,7 @@ func DecideFastPath(profile capability.SkillProfile, supported bool, confidenceM
ObservedAtUnixMillis: observedAtUnixMillis,
}
switch {
case profile.Status == capability.SkillSuspended || profile.Status == capability.SkillRetired:
case profile.Status == capability.SkillSuspended || profile.Status == capability.SkillRetired || profile.Status == capability.SkillDegraded:
return decline(decision, capability.FastPathStale, "skill profile is not eligible for fast-path use"), nil
case environmentVersionChanged:
return decline(decision, capability.FastPathStale, "environment or implementation version changed since validation"), nil
@@ -90,7 +133,7 @@ func DecideFastPath(profile capability.SkillProfile, supported bool, confidenceM
return decline(decision, capability.FastPathOutOfDistribution, "input is outside supported semantics"), nil
case confidenceMillis < 700 || profile.ConfidenceMillis < 700:
return decline(decision, capability.FastPathLowConfidence, "confidence is too low for optimized path"), nil
case profile.Status != capability.SkillValidated:
case profile.Status != capability.SkillActive:
return decline(decision, capability.FastPathNotApplicable, "skill is not validated"), nil
default:
return decision, nil
+37 -12
View File
@@ -34,7 +34,7 @@ func TestFastPathDeclinesUnsupportedInputAndEscalatesToThinking(t *testing.T) {
}
}
func TestValidatedSkillCanApplyFastPathWhenFreshAndConfident(t *testing.T) {
func TestActiveSkillCanApplyFastPathWhenFreshAndConfident(t *testing.T) {
decision, err := DecideFastPath(skillProfile(), true, 950, false, false, 2000)
if err != nil {
t.Fatal(err)
@@ -58,18 +58,43 @@ func TestUnknownAndSkippedOutcomesDoNotBecomeSkillFailures(t *testing.T) {
}
}
func TestCandidateNeedsIndependentRepeatableValidationBeforeActive(t *testing.T) {
profile := skillProfile()
profile.Status = capability.SkillCandidate
profile.VerifiedOutcomeCount = 1
profile.SupportingExperienceRefs = []capability.SkillEvidenceRef{"experience:1"}
if _, err := ValidateSkill(profile, "evaluation:independent", true, true, 2000); err == nil {
t.Fatal("one success promoted a skill")
}
profile.VerifiedOutcomeCount = 2
profile.SupportingExperienceRefs = append(profile.SupportingExperienceRefs, "experience:2")
validated, err := ValidateSkill(profile, "evaluation:independent", true, true, 2000)
if err != nil || validated.Status != capability.SkillValidated {
t.Fatalf("validation=%#v err=%v", validated, err)
}
active, err := ActivateSkill(validated, 2100)
if err != nil || active.Status != capability.SkillActive {
t.Fatalf("activation=%#v err=%v", active, err)
}
}
func skillProfile() capability.SkillProfile {
return capability.SkillProfile{
ID: "skill-obvious-reminder-response-v1",
Revision: 1,
Name: "Resolve obvious reminder continuation",
Description: "Use a recent unambiguous reminder/attention target for complete/snooze/cancel without cognition.",
ApplicableContext: []string{"continuity:reminder-response"},
ImplementationRef: "continuity:human-response-target-resolver-v1",
ImplementationVersion: "1",
Status: capability.SkillValidated,
ConfidenceMillis: 850,
LastValidatedUnixMillis: 1000,
RecordedAtUnixMillis: 1000,
ID: "skill-obvious-reminder-response-v1",
Revision: 1,
Name: "Resolve obvious reminder continuation",
Description: "Use a recent unambiguous reminder/attention target for complete/snooze/cancel without cognition.",
ApplicableContext: []string{"continuity:reminder-response"},
ImplementationRef: "continuity:human-response-target-resolver-v1",
ImplementationVersion: "1",
Status: capability.SkillActive,
ConfidenceMillis: 850,
LastValidatedUnixMillis: 1000,
RecordedAtUnixMillis: 1000,
SupportingExperienceRefs: []capability.SkillEvidenceRef{"experience:1", "experience:2"},
IndependentEvaluationRefs: []capability.SkillEvidenceRef{"evaluation:independent"},
ValidationEvidenceRefs: []capability.SkillEvidenceRef{"evaluation:independent"},
VerifiedOutcomeCount: 2,
MustNotWorsenSatisfied: true,
}
}
+17
View File
@@ -1,8 +1,25 @@
package domain
import (
"fmt"
"git.cin.su/m/Assistant/contracts/self"
"git.cin.su/m/Assistant/platform/ids"
)
func ValidateCompetence(v self.CompetenceAssessment) error { return v.Validate() }
func ValidateLearning(v self.LearningLedgerEntry) error { return v.Validate() }
// ImprovementForReflexDivergence records a proposed review only. SELF never
// promotes, rewrites or trusts a Reflex from this observation.
func ImprovementForReflexDivergence(principal string, reflexRef self.SemanticReference, evidence []self.SemanticReference, at int64) (self.ImprovementNeed, self.LearningLedgerEntry, error) {
if principal == "" || !reflexRef.Valid() || len(evidence) == 0 || at < 1 {
return self.ImprovementNeed{}, self.LearningLedgerEntry{}, fmt.Errorf("reflex improvement lacks evidence")
}
need := self.ImprovementNeed{ID: self.AssessmentID("improvement:" + string(reflexRef.StableID) + ":" + fmt.Sprint(at)), PrincipalID: principal, SubjectRef: reflexRef, ProblemSummary: "shadow divergence requires governed reflex revalidation", EvidenceRefs: evidence, ExpectedBenefit: "restore verified bounded behavior or retire the reflex", Priority: 1, State: "OPEN", CreatedAt: at}
ref := self.SemanticReference{OwnerNamespace: "self", SemanticKind: "improvement_need", StableID: ids.StableID(need.ID)}
ledger := self.LearningLedgerEntry{ID: self.LearningLedgerEntryID("ledger:" + string(reflexRef.StableID) + ":" + fmt.Sprint(at)), PrincipalID: principal, ImprovementNeedRef: &ref, ObservedProblem: need.ProblemSummary, EvidenceRefs: evidence, Diagnosis: "bounded shadow comparison diverged or verifier rejected result", Hypothesis: "implementation or applicability no longer matches evidence", ProposedChangeRef: reflexRef, ExpectedImprovement: need.ExpectedBenefit, Decision: "PROPOSE_REVALIDATION", RecordedAt: at}
if err := ledger.Validate(); err != nil {
return self.ImprovementNeed{}, self.LearningLedgerEntry{}, err
}
return need, ledger, nil
}
+9
View File
@@ -16,3 +16,12 @@ func TestCompetenceCannotBeSelfDeclaredWithoutEvidence(t *testing.T) {
t.Fatal(err)
}
}
func TestShadowDivergenceCreatesProposalNotPromotion(t *testing.T) {
reflex := selfcontract.SemanticReference{OwnerNamespace: "capability", SemanticKind: "reflex", StableID: "r"}
evidence := []selfcontract.SemanticReference{{OwnerNamespace: "epistemics", SemanticKind: "experience", StableID: "e"}}
need, ledger, err := domain.ImprovementForReflexDivergence("p", reflex, evidence, 1)
if err != nil || need.State != "OPEN" || ledger.Decision != "PROPOSE_REVALIDATION" {
t.Fatalf("need=%#v ledger=%#v err=%v", need, ledger, err)
}
}