Compare commits

..
Author SHA1 Message Date
m 9a84ac942c DEV-000064 capability resolution foundation
CI / test (push) Successful in 15s
CI / test (pull_request) Successful in 14s
2026-08-17 15:51:25 +03:00
m fe531ab56f Merge DEV-000063 final production status
CI / test (push) Successful in 13s
Status-only completion record for DEV-000063.
2026-08-17 12:19:09 +00:00
m d8f994e07e DEV-000063 record completed production status
CI / test (push) Successful in 14s
CI / test (pull_request) Successful in 14s
2026-08-17 15:18:11 +03:00
m 9d50a3ec63 Merge DEV-000063 speech act follow-up
CI / test (push) Successful in 14s
Speech act classifier follow-up for DEV-000063.
2026-08-17 12:15:55 +00:00
m 2d142cdfdb DEV-000063 classify reminder and capability directives
CI / test (push) Successful in 16s
CI / test (pull_request) Successful in 19s
2026-08-17 15:14:56 +03:00
m 06b6cc3f9d Merge DEV-000063 Intent to Work Planning Foundation
CI / test (push) Successful in 20s
Promote DEV-000063 rev.1 to main.
2026-08-17 12:11:45 +00:00
29 changed files with 1632 additions and 18 deletions
+112
View File
@@ -14,6 +14,7 @@ import (
var ErrCapabilityHealthNotFound = errors.New("capability health not found")
var ErrCapabilityBindingNotFound = errors.New("capability binding not found")
var ErrCapabilityBindingConflict = errors.New("capability binding conflict")
var ErrCapabilityImplementationNotFound = errors.New("capability implementation not found")
type CapabilityRepository struct{ pool *pgxpool.Pool }
@@ -106,6 +107,117 @@ func (r *CapabilityRepository) SaveBinding(ctx context.Context, binding capabili
return tx.Commit(ctx)
}
func (r *CapabilityRepository) SaveCapabilityImplementation(ctx context.Context, value capability.CapabilityImplementation) error {
tx, err := r.pool.Begin(ctx)
if err != nil {
return err
}
defer func() { _ = tx.Rollback(ctx) }()
_, err = tx.Exec(ctx, `INSERT INTO capability.capability_implementations (
implementation_id,capability_id,capability_kind,owner_principal_id,revision,implementation_kind,target_ref,credential_ref,
qualification,activation,health,authority_policy_ref,data_constraints,cost_score_millis,latency_score_millis,reliability_millis,observed_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.CapabilityID, value.CapabilityKind, value.OwnerPrincipalID, value.Revision, value.Kind, value.TargetRef,
value.CredentialRef, value.Qualification, value.Activation, value.Health, value.AuthorityPolicyRef, value.DataConstraints,
value.CostScoreMillis, value.LatencyScoreMillis, value.ReliabilityMillis, value.ObservedAtUnixMillis)
if err != nil {
return err
}
_, err = tx.Exec(ctx, `INSERT INTO capability.capability_implementation_heads(implementation_id,revision,capability_kind,owner_principal_id)
VALUES ($1,$2,$3,$4)
ON CONFLICT (implementation_id) DO UPDATE SET revision=EXCLUDED.revision,capability_kind=EXCLUDED.capability_kind,owner_principal_id=EXCLUDED.owner_principal_id,updated_at=CURRENT_TIMESTAMP
WHERE capability.capability_implementation_heads.revision < EXCLUDED.revision`, value.ID, value.Revision, value.CapabilityKind, value.OwnerPrincipalID)
if err != nil {
return err
}
return tx.Commit(ctx)
}
func (r *CapabilityRepository) LoadCapabilityImplementations(ctx context.Context, kind capability.CapabilityKind, owner identity.PrincipalID) ([]capability.CapabilityImplementation, error) {
rows, err := r.pool.Query(ctx, `SELECT
i.implementation_id,i.capability_id,i.capability_kind,i.owner_principal_id,i.revision,i.implementation_kind,i.target_ref,COALESCE(i.credential_ref,''),
i.qualification,i.activation,i.health,i.authority_policy_ref,i.data_constraints,i.cost_score_millis,i.latency_score_millis,i.reliability_millis,i.observed_at_unix_millis
FROM capability.capability_implementation_heads h
JOIN capability.capability_implementations i ON i.implementation_id=h.implementation_id AND i.revision=h.revision
WHERE i.capability_kind=$1 AND i.owner_principal_id=$2
ORDER BY i.implementation_id`, kind, owner)
if err != nil {
return nil, err
}
defer rows.Close()
result := make([]capability.CapabilityImplementation, 0)
for rows.Next() {
var value capability.CapabilityImplementation
if err = rows.Scan(&value.ID, &value.CapabilityID, &value.CapabilityKind, &value.OwnerPrincipalID, &value.Revision, &value.Kind, &value.TargetRef,
&value.CredentialRef, &value.Qualification, &value.Activation, &value.Health, &value.AuthorityPolicyRef, &value.DataConstraints,
&value.CostScoreMillis, &value.LatencyScoreMillis, &value.ReliabilityMillis, &value.ObservedAtUnixMillis); err != nil {
return nil, err
}
result = append(result, value)
}
return result, rows.Err()
}
func (r *CapabilityRepository) AvailableCapabilityKinds(ctx context.Context, owner identity.PrincipalID) ([]capability.CapabilityKind, error) {
rows, err := r.pool.Query(ctx, `SELECT DISTINCT i.capability_kind
FROM capability.capability_implementation_heads h
JOIN capability.capability_implementations i ON i.implementation_id=h.implementation_id AND i.revision=h.revision
WHERE i.owner_principal_id=$1 AND i.qualification=$2 AND i.activation=$3 AND i.health=$4
ORDER BY i.capability_kind`, owner, capability.QualificationValidated, capability.ActivationActive, capability.HealthAvailable)
if err != nil {
return nil, err
}
defer rows.Close()
result := make([]capability.CapabilityKind, 0)
for rows.Next() {
var kind capability.CapabilityKind
if err := rows.Scan(&kind); err != nil {
return nil, err
}
result = append(result, kind)
}
return result, rows.Err()
}
func (r *CapabilityRepository) SaveCapabilityGap(ctx context.Context, value capability.CapabilityGap) error {
strategies := make([]string, 0, len(value.Strategies))
for _, strategy := range value.Strategies {
strategies = append(strategies, string(strategy.Kind))
}
_, err := r.pool.Exec(ctx, `INSERT INTO capability.capability_gaps (
gap_id,requirement_id,capability_kind,capability_id,owner_principal_id,original_intent_id,source_utterance_id,
reason,detail,strategies,recorded_at_unix_millis)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) ON CONFLICT DO NOTHING`, value.ID, value.RequirementID,
value.CapabilityKind, value.CapabilityID, value.OwnerPrincipalID, value.OriginalIntentID, value.SourceUtteranceID,
value.Reason, value.Detail, strategies, value.RecordedAtUnixMillis)
return err
}
func (r *CapabilityRepository) LatestCapabilityGap(ctx context.Context, kind capability.CapabilityKind, owner identity.PrincipalID) (capability.CapabilityGap, error) {
var value capability.CapabilityGap
var strategies []string
err := r.pool.QueryRow(ctx, `SELECT gap_id,requirement_id,capability_kind,capability_id,owner_principal_id,original_intent_id,source_utterance_id,reason,detail,strategies,recorded_at_unix_millis
FROM capability.capability_gaps WHERE capability_kind=$1 AND owner_principal_id=$2 ORDER BY recorded_at_unix_millis DESC, gap_id DESC LIMIT 1`, kind, owner).
Scan(&value.ID, &value.RequirementID, &value.CapabilityKind, &value.CapabilityID, &value.OwnerPrincipalID, &value.OriginalIntentID, &value.SourceUtteranceID, &value.Reason, &value.Detail, &strategies, &value.RecordedAtUnixMillis)
if err != nil {
return capability.CapabilityGap{}, err
}
for _, strategy := range strategies {
value.Strategies = append(value.Strategies, capability.AcquisitionStrategyCandidate{Kind: capability.AcquisitionStrategyKind(strategy)})
}
return value, nil
}
func (r *CapabilityRepository) SaveCapabilityUsageObservation(ctx context.Context, value capability.CapabilityUsageObservation) error {
_, err := r.pool.Exec(ctx, `INSERT INTO capability.capability_usage_observations (
observation_id,implementation_id,implementation_revision,capability_id,capability_kind,owner_principal_id,
original_intent_id,source_utterance_id,outcome,outcome_ref,latency_millis,observed_at_unix_millis)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) ON CONFLICT DO NOTHING`, value.ID, value.ImplementationID,
value.ImplementationRevision, value.CapabilityID, value.CapabilityKind, value.OwnerPrincipalID, value.OriginalIntentID,
value.SourceUtteranceID, value.Outcome, value.OutcomeRef, value.LatencyMillis, value.ObservedAtUnixMillis)
return err
}
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,
+6 -6
View File
@@ -34,12 +34,12 @@ func OpenContinuity(ctx context.Context, config ContinuityConfig) (*ContinuityRe
}
func (r *ContinuityRepository) Close() { r.pool.Close() }
func (r *ContinuityRepository) Save(ctx context.Context, item continuity.ProspectiveItem) error {
_, err := r.pool.Exec(ctx, `INSERT INTO continuity.prospective_items (prospective_item_id, commitment_id, owner_principal_id, scope_id, goal_id, trigger_at_unix_millis, state, revision) VALUES ($1,$2,$3,$4,$5,$6,$7,$8) ON CONFLICT (prospective_item_id) DO UPDATE SET state=EXCLUDED.state, revision=EXCLUDED.revision`, item.ID, item.CommitmentID, item.OwnerPrincipalID, item.ScopeID, item.GoalID, item.TriggerAtUnixMillis, item.State, item.Revision)
_, err := r.pool.Exec(ctx, `INSERT INTO continuity.prospective_items (prospective_item_id, commitment_id, owner_principal_id, scope_id, goal_id, source_intent_id, source_utterance_id, summary, trigger_at_unix_millis, state, revision) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) ON CONFLICT (prospective_item_id) DO UPDATE SET state=EXCLUDED.state, revision=EXCLUDED.revision`, item.ID, item.CommitmentID, item.OwnerPrincipalID, item.ScopeID, item.GoalID, item.SourceIntentID, item.SourceUtteranceID, item.Summary, item.TriggerAtUnixMillis, item.State, item.Revision)
return err
}
func (r *ContinuityRepository) Load(ctx context.Context, id continuity.ProspectiveItemID) (continuity.ProspectiveItem, error) {
var item continuity.ProspectiveItem
err := r.pool.QueryRow(ctx, `SELECT prospective_item_id, commitment_id, owner_principal_id, scope_id, goal_id, trigger_at_unix_millis, state, revision FROM continuity.prospective_items WHERE prospective_item_id=$1`, id).Scan(&item.ID, &item.CommitmentID, &item.OwnerPrincipalID, &item.ScopeID, &item.GoalID, &item.TriggerAtUnixMillis, &item.State, &item.Revision)
err := r.pool.QueryRow(ctx, `SELECT prospective_item_id, commitment_id, owner_principal_id, scope_id, goal_id, source_intent_id, source_utterance_id, summary, trigger_at_unix_millis, state, revision FROM continuity.prospective_items WHERE prospective_item_id=$1`, id).Scan(&item.ID, &item.CommitmentID, &item.OwnerPrincipalID, &item.ScopeID, &item.GoalID, &item.SourceIntentID, &item.SourceUtteranceID, &item.Summary, &item.TriggerAtUnixMillis, &item.State, &item.Revision)
if err != nil {
return continuity.ProspectiveItem{}, fmt.Errorf("load prospective item: %w", err)
}
@@ -47,7 +47,7 @@ func (r *ContinuityRepository) Load(ctx context.Context, id continuity.Prospecti
}
func (r *ContinuityRepository) ByOwner(ctx context.Context, owner identity.PrincipalID, limit int) ([]continuity.ProspectiveItem, error) {
rows, err := r.pool.Query(ctx, `SELECT prospective_item_id, commitment_id, owner_principal_id, scope_id, goal_id, trigger_at_unix_millis, state, revision FROM continuity.prospective_items WHERE owner_principal_id=$1 ORDER BY trigger_at_unix_millis, prospective_item_id LIMIT $2`, owner, limit)
rows, err := r.pool.Query(ctx, `SELECT prospective_item_id, commitment_id, owner_principal_id, scope_id, goal_id, source_intent_id, source_utterance_id, summary, trigger_at_unix_millis, state, revision FROM continuity.prospective_items WHERE owner_principal_id=$1 ORDER BY trigger_at_unix_millis, prospective_item_id LIMIT $2`, owner, limit)
if err != nil {
return nil, fmt.Errorf("list prospective items by owner: %w", err)
}
@@ -55,7 +55,7 @@ func (r *ContinuityRepository) ByOwner(ctx context.Context, owner identity.Princ
items := make([]continuity.ProspectiveItem, 0)
for rows.Next() {
var item continuity.ProspectiveItem
if err := rows.Scan(&item.ID, &item.CommitmentID, &item.OwnerPrincipalID, &item.ScopeID, &item.GoalID, &item.TriggerAtUnixMillis, &item.State, &item.Revision); err != nil {
if err := rows.Scan(&item.ID, &item.CommitmentID, &item.OwnerPrincipalID, &item.ScopeID, &item.GoalID, &item.SourceIntentID, &item.SourceUtteranceID, &item.Summary, &item.TriggerAtUnixMillis, &item.State, &item.Revision); err != nil {
return nil, fmt.Errorf("scan prospective item: %w", err)
}
items = append(items, item)
@@ -95,10 +95,10 @@ func (r *ContinuityRepository) SatisfyDelivered(ctx context.Context, command con
err = tx.QueryRow(ctx, `UPDATE continuity.prospective_items
SET state=$1, revision=revision+1
WHERE prospective_item_id=$2 AND owner_principal_id=$3 AND state=$4 AND revision=$5
RETURNING prospective_item_id,commitment_id,owner_principal_id,scope_id,goal_id,trigger_at_unix_millis,state,revision`,
RETURNING prospective_item_id,commitment_id,owner_principal_id,scope_id,goal_id,source_intent_id,source_utterance_id,summary,trigger_at_unix_millis,state,revision`,
continuity.ProspectiveSatisfied, command.Subject.ProspectiveItemID, command.OwnerPrincipalID,
continuity.ProspectiveDelivered, command.Subject.ExpectedRevision).Scan(
&item.ID, &item.CommitmentID, &item.OwnerPrincipalID, &item.ScopeID, &item.GoalID,
&item.ID, &item.CommitmentID, &item.OwnerPrincipalID, &item.ScopeID, &item.GoalID, &item.SourceIntentID, &item.SourceUtteranceID, &item.Summary,
&item.TriggerAtUnixMillis, &item.State, &item.Revision)
if errors.Is(err, pgx.ErrNoRows) {
return continuity.ActionReceipt{}, diagnoseProspectiveConflict(ctx, tx, command)
@@ -0,0 +1,33 @@
# ADR-0013 — Capability Resolution, Selection and Gap Handling Foundation
Status: ACCEPTED
Date: 2026-08-17
## Context
DEV-000063 introduced Work-owned `CapabilityRequirement` without selecting or executing concrete implementations. The system now needs a provider-neutral way to answer which real capabilities can satisfy a requirement, whether they are operationally usable, whether policy permits them, and what typed gap remains when no implementation is eligible.
The Core Semantic Model already assigns Capability registry/bindings ownership to the Capability module. Cognitive Registry is a specialized capability registry for model/provider/model-binding semantics. This ADR must not create a parallel cognitive source of truth.
## Decision
1. Keep `capability` as the canonical owner for capability descriptors, implementation registrations, implementation health/availability, resolution results, typed gaps, acquisition strategy candidates and generic capability usage observations.
2. Represent a capability separately from an implementation:
- `Capability` / `CapabilityRef`: what the system can obtain or do.
- `CapabilityImplementation`: a concrete adapter, provider, runtime, agent or internal function that may satisfy that capability.
3. Capability identity is typed but extensible. Known `CapabilityKind` constants are allowed for current semantics, but the registry stores string capability kinds and stable capability IDs without imposing a closed universe.
4. Resolver V1 returns `AVAILABLE`, `PARTIALLY_AVAILABLE`, `UNAVAILABLE` or `UNKNOWN` with eligible implementations and typed exclusion/gap reasons.
5. Selection V1 is deterministic and evidence-weighted. It must rank eligible implementations by typed evidence and stable IDs, not by hardcoded provider/adapter names.
6. CredentialRef is allowed as implementation metadata, but credential existence never means capability availability or Authority. Secret values remain outside canonical state and are resolved only by trusted runtimes.
7. Cognitive systems remain integrated through the existing Cognitive Registry and Router. General capability resolution may express the conceptual relation that cognition is a capability, but cognitive invocation continues through the existing gateway/router.
8. Reminder scheduling is the first concrete production capability for this foundation. It creates Continuity-owned `ProspectiveItem` state through the Continuity runtime boundary. It does not create durable Work for simple reminders.
9. Unsupported email sending creates a Capability-owned `CapabilityGap` correlated to the original human intent. It must not claim execution and must not become an automatic Codex/self-extension task.
## Consequences
- `capability` gains generic implementation, resolution, gap and usage-observation contracts/tables.
- `continuity` gains a narrow authenticated creator reminder-registration endpoint that owns creation of `ProspectiveItem` rows.
- `/chat` can schedule a simple reminder through capability resolution and can persist a typed gap for unavailable capabilities.
- No cognitive binding is promoted to `ACTIVE`.
- No new canonical owner, broad plugin marketplace, autonomous adapter installation or self-modifying loop is introduced.
+7 -1
View File
@@ -114,7 +114,13 @@
"owns": [
"Capability",
"CapabilityBinding",
"CapabilityHealth"
"CapabilityHealth",
"CapabilityImplementation",
"CapabilityResolution",
"CapabilitySelection",
"CapabilityGap",
"AcquisitionStrategyCandidate",
"CapabilityUsageObservation"
],
"database_schema": "capability",
"boundary_class": "remote_capable",
+1 -1
View File
@@ -48,7 +48,7 @@ modules:
capability:
id: mod.capability
owns: [Capability, CapabilityBinding, CapabilityHealth]
owns: [Capability, CapabilityBinding, CapabilityHealth, CapabilityImplementation, CapabilityResolution, CapabilitySelection, CapabilityGap, AcquisitionStrategyCandidate, CapabilityUsageObservation]
database_schema: capability
boundary_class: remote_capable
may_depend_on: [contracts.identity, contracts.authority, contracts.artifact]
+1
View File
@@ -47,6 +47,7 @@ func main() {
}
w.WriteHeader(http.StatusCreated)
})
registerCreatorReminderRoute(mux, identityClient, repository)
mux.HandleFunc("GET /v1/prospective-items/{id}", func(w http.ResponseWriter, r *http.Request) {
item, err := repository.Load(r.Context(), continuity.ProspectiveItemID(r.PathValue("id")))
if err != nil {
+55
View File
@@ -0,0 +1,55 @@
package main
import (
"context"
"encoding/json"
"errors"
"net/http"
"git.cin.su/m/Assistant/contracts/continuity"
continuitydomain "git.cin.su/m/Assistant/modules/continuity/domain"
"git.cin.su/m/Assistant/platform/ids"
)
type reminderRepository interface {
Save(context.Context, continuity.ProspectiveItem) error
}
func registerCreatorReminderRoute(mux *http.ServeMux, identityClient creatorIdentityClient, repository reminderRepository) {
mux.HandleFunc("POST /v1/creator/reminders", func(w http.ResponseWriter, r *http.Request) {
session, err := identityClient.Session(r.Context(), r.Header.Get("Authorization"))
if errors.Is(err, errCreatorSessionDenied) {
http.Error(w, "creator session denied", http.StatusUnauthorized)
return
}
if err != nil {
http.Error(w, "identity service unavailable", http.StatusServiceUnavailable)
return
}
var request continuity.ScheduleReminderRequest
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 32<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil {
http.Error(w, "invalid reminder", http.StatusBadRequest)
return
}
request.OwnerPrincipalID = session.PrincipalID
id, err := ids.New()
if err != nil {
http.Error(w, "reminder unavailable", http.StatusServiceUnavailable)
return
}
item, err := continuitydomain.BuildReminder(request, continuity.ProspectiveItemID(id))
if err != nil {
http.Error(w, "invalid reminder", http.StatusBadRequest)
return
}
if err := repository.Save(r.Context(), item); err != nil {
http.Error(w, "save reminder", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(continuity.ScheduleReminderResult{Item: item})
})
}
+46
View File
@@ -0,0 +1,46 @@
package main
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"git.cin.su/m/Assistant/contracts/continuity"
"git.cin.su/m/Assistant/contracts/identity"
)
func TestCreatorReminderEndpointBuildsOwnedProspectiveItem(t *testing.T) {
core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/creator/sessions/current" || r.Header.Get("Authorization") != "Bearer ok" {
t.Fatal("unexpected identity validation")
}
_ = json.NewEncoder(w).Encode(identity.CreatorSession{ActorID: "actor-1", PrincipalID: "principal-1"})
}))
defer core.Close()
identityClient, err := newCreatorIdentityClient(core.URL, core.Client())
if err != nil {
t.Fatal(err)
}
repository := &fakeReminderRepository{}
mux := http.NewServeMux()
registerCreatorReminderRoute(mux, identityClient, repository)
request := httptest.NewRequest(http.MethodPost, "/v1/creator/reminders", strings.NewReader(`{"ScopeID":"scope-1","SourceIntentID":"intent-1","SourceUtteranceID":"utterance-1","Summary":"позвонить врачу","TriggerAtUnixMillis":2000,"RequestedAtUnixMillis":1000}`))
request.Header.Set("Authorization", "Bearer ok")
recorder := httptest.NewRecorder()
mux.ServeHTTP(recorder, request)
if recorder.Code != http.StatusCreated || repository.saved.OwnerPrincipalID != "principal-1" || repository.saved.Summary != "позвонить врачу" || repository.saved.State != continuity.ProspectiveArmed {
t.Fatalf("status=%d saved=%#v body=%s", recorder.Code, repository.saved, recorder.Body.String())
}
}
type fakeReminderRepository struct {
saved continuity.ProspectiveItem
}
func (f *fakeReminderRepository) Save(_ context.Context, item continuity.ProspectiveItem) error {
f.saved = item
return nil
}
+194
View File
@@ -0,0 +1,194 @@
package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
"git.cin.su/m/Assistant/adapters/postgres"
"git.cin.su/m/Assistant/contracts/authority"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/continuity"
"git.cin.su/m/Assistant/contracts/identity"
"git.cin.su/m/Assistant/contracts/interaction"
capabilitydomain "git.cin.su/m/Assistant/modules/capability/domain"
"git.cin.su/m/Assistant/platform/ids"
)
type continuityReminderClient struct {
baseURL *url.URL
client *http.Client
}
func newContinuityReminderClient(rawURL string, client *http.Client) (continuityReminderClient, error) {
base, err := url.Parse(rawURL)
if err != nil || base.Scheme == "" || base.Host == "" {
return continuityReminderClient{}, fmt.Errorf("invalid Continuity Runtime URL")
}
if client == nil {
client = http.DefaultClient
}
return continuityReminderClient{baseURL: base, client: client}, nil
}
func (c continuityReminderClient) Schedule(ctx context.Context, authorization string, request continuity.ScheduleReminderRequest) (continuity.ScheduleReminderResult, error) {
target := *c.baseURL
target.Path = "/v1/creator/reminders"
payload, err := json.Marshal(request)
if err != nil {
return continuity.ScheduleReminderResult{}, err
}
upstream, err := http.NewRequestWithContext(ctx, http.MethodPost, target.String(), bytes.NewReader(payload))
if err != nil {
return continuity.ScheduleReminderResult{}, err
}
upstream.Header.Set("Content-Type", "application/json")
upstream.Header.Set("Authorization", authorization)
response, err := c.client.Do(upstream)
if err != nil {
return continuity.ScheduleReminderResult{}, err
}
defer response.Body.Close()
if response.StatusCode != http.StatusCreated {
return continuity.ScheduleReminderResult{}, fmt.Errorf("continuity reminder status %d", response.StatusCode)
}
var result continuity.ScheduleReminderResult
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
return continuity.ScheduleReminderResult{}, err
}
if result.Item.ID == "" || result.Item.State != continuity.ProspectiveArmed {
return continuity.ScheduleReminderResult{}, fmt.Errorf("invalid reminder result")
}
return result, nil
}
func resolveChatCapability(ctx context.Context, repository *postgres.CapabilityRepository, kind capability.CapabilityKind, session identity.CreatorSession, intent interaction.OriginalHumanIntent, utterance interaction.HumanUtterance, now time.Time) (capability.CapabilityResolution, error) {
requirementID, err := ids.New()
if err != nil {
return capability.CapabilityResolution{}, err
}
requirement := capability.CapabilityResolutionRequirement{
RequirementID: capability.RequirementRef(requirementID),
CapabilityKind: kind,
OwnerPrincipalID: session.PrincipalID,
ActorID: session.ActorID,
OriginalIntentID: capability.OriginalIntentRef(intent.ID),
SourceUtteranceID: capability.SourceUtteranceRef(utterance.ID),
RequiredConstraints: []capability.DataConstraint{capability.DataConstraintLocalOnly},
AuthorityContextRef: capability.AuthorityPolicyCreatorDirectiveV1,
ObservedAtUnixMillis: now.UnixMilli(),
}
candidates, err := repository.LoadCapabilityImplementations(ctx, kind, session.PrincipalID)
if err != nil {
return capability.CapabilityResolution{}, err
}
resolution, err := capabilitydomain.ResolveCapability(requirement, candidates)
if errors.Is(err, capabilitydomain.ErrNoCapabilityImplementation) && resolution.Gap != nil {
if saveErr := repository.SaveCapabilityGap(ctx, *resolution.Gap); saveErr != nil {
return capability.CapabilityResolution{}, saveErr
}
return resolution, err
}
return resolution, err
}
func observeCapabilityUsage(ctx context.Context, repository *postgres.CapabilityRepository, selected capability.ResolvedCapabilityImplementation, requirement capability.CapabilityResolutionRequirement, outcome capability.UsageOutcomeClass, outcomeRef string, started time.Time, now time.Time) {
id, err := ids.New()
if err != nil {
return
}
latency := now.Sub(started).Milliseconds()
observation := capability.CapabilityUsageObservation{
ID: capability.CapabilityUsageObservationID(id),
ImplementationID: selected.Implementation.ID,
ImplementationRevision: selected.Implementation.Revision,
CapabilityID: selected.Implementation.CapabilityID,
CapabilityKind: selected.Implementation.CapabilityKind,
OwnerPrincipalID: selected.Implementation.OwnerPrincipalID,
OriginalIntentID: requirement.OriginalIntentID,
SourceUtteranceID: requirement.SourceUtteranceID,
Outcome: outcome,
OutcomeRef: outcomeRef,
LatencyMillis: &latency,
ObservedAtUnixMillis: now.UnixMilli(),
}
_ = repository.SaveCapabilityUsageObservation(ctx, observation)
}
func buildReminderRequest(message string, session identity.CreatorSession, intent interaction.OriginalHumanIntent, utterance interaction.HumanUtterance, now time.Time) (continuity.ScheduleReminderRequest, error) {
trigger, err := parseReminderTime(message, now)
if err != nil {
return continuity.ScheduleReminderRequest{}, err
}
summary := reminderSummary(message)
if summary == "" {
summary = strings.TrimSpace(message)
}
return continuity.ScheduleReminderRequest{
OwnerPrincipalID: session.PrincipalID,
ScopeID: authority.ScopeID("domovoy-global-scope"),
SourceIntentID: string(intent.ID),
SourceUtteranceID: string(utterance.ID),
Summary: summary,
TriggerAtUnixMillis: trigger.UnixMilli(),
RequestedAtUnixMillis: now.UnixMilli(),
}, nil
}
func parseReminderTime(message string, now time.Time) (time.Time, error) {
lower := strings.ToLower(message)
day := now
if strings.Contains(lower, "послезавтра") {
day = now.AddDate(0, 0, 2)
} else if strings.Contains(lower, "завтра") {
day = now.AddDate(0, 0, 1)
}
hour, minute := 9, 0
re := regexp.MustCompile(`(?:в|на)\s+([0-2]?\d)(?::([0-5]\d))?`)
if match := re.FindStringSubmatch(lower); len(match) > 0 {
parsedHour, err := strconv.Atoi(match[1])
if err != nil || parsedHour > 23 {
return time.Time{}, fmt.Errorf("invalid reminder hour")
}
hour = parsedHour
if match[2] != "" {
parsedMinute, err := strconv.Atoi(match[2])
if err != nil {
return time.Time{}, fmt.Errorf("invalid reminder minute")
}
minute = parsedMinute
}
}
trigger := time.Date(day.Year(), day.Month(), day.Day(), hour, minute, 0, 0, now.Location())
if !trigger.After(now) {
trigger = trigger.AddDate(0, 0, 1)
}
return trigger, nil
}
func reminderSummary(message string) string {
clean := strings.TrimSpace(message)
lower := strings.ToLower(clean)
for _, prefix := range []string{"напомни мне", "напомни"} {
if strings.HasPrefix(lower, prefix) {
clean = strings.TrimSpace(clean[len(prefix):])
lower = strings.ToLower(clean)
break
}
}
for _, word := range []string{"послезавтра", "завтра", "сегодня"} {
clean = strings.TrimSpace(strings.ReplaceAll(strings.ToLower(clean), word, ""))
}
re := regexp.MustCompile(`(?i)(в|на)\s+[0-2]?\d(?::[0-5]\d)?`)
clean = strings.TrimSpace(re.ReplaceAllString(clean, ""))
clean = strings.Trim(clean, " .,!?:;")
return clean
}
+59
View File
@@ -0,0 +1,59 @@
package main
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"git.cin.su/m/Assistant/contracts/continuity"
"git.cin.su/m/Assistant/contracts/identity"
"git.cin.su/m/Assistant/contracts/interaction"
)
func TestBuildReminderRequestParsesTomorrowAtTen(t *testing.T) {
now := time.Date(2026, 8, 17, 12, 0, 0, 0, time.FixedZone("MSK", 3*60*60))
request, err := buildReminderRequest("Напомни завтра в 10 позвонить врачу.", identity.CreatorSession{PrincipalID: "principal-1"}, interaction.OriginalHumanIntent{ID: "intent-1"}, interaction.HumanUtterance{ID: "utterance-1"}, now)
if err != nil {
t.Fatal(err)
}
trigger := time.UnixMilli(request.TriggerAtUnixMillis).In(now.Location())
if trigger.Year() != 2026 || trigger.Month() != 8 || trigger.Day() != 18 || trigger.Hour() != 10 || trigger.Minute() != 0 {
t.Fatalf("unexpected trigger: %s", trigger)
}
if request.Summary != "позвонить врачу" || request.SourceIntentID != "intent-1" || request.SourceUtteranceID != "utterance-1" {
t.Fatalf("unexpected request: %#v", request)
}
}
func TestContinuityReminderClientDoesNotAcceptFalseSuccess(t *testing.T) {
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/creator/reminders" || r.Header.Get("Authorization") != "Bearer session" {
t.Fatal("unexpected reminder request")
}
var request continuity.ScheduleReminderRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil || request.Summary != "позвонить врачу" {
t.Fatalf("bad payload: %#v err=%v", request, err)
}
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(continuity.ScheduleReminderResult{Item: continuity.ProspectiveItem{ID: "reminder-1", State: continuity.ProspectiveArmed, Summary: request.Summary}})
}))
defer upstream.Close()
client, err := newContinuityReminderClient(upstream.URL, upstream.Client())
if err != nil {
t.Fatal(err)
}
result, err := client.Schedule(context.Background(), "Bearer session", continuity.ScheduleReminderRequest{Summary: "позвонить врачу"})
if err != nil || result.Item.ID != "reminder-1" {
t.Fatalf("result=%#v err=%v", result, err)
}
}
func TestReminderSummaryRemovesSchedulingWords(t *testing.T) {
if summary := reminderSummary("Напомни завтра в 10 позвонить врачу."); summary != "позвонить врачу" || strings.Contains(summary, "10") {
t.Fatalf("summary = %q", summary)
}
}
+90
View File
@@ -8,6 +8,7 @@ import (
"git.cin.su/m/Assistant/adapters/postgres"
"git.cin.su/m/Assistant/contracts/authority"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/continuity"
"git.cin.su/m/Assistant/contracts/epistemics"
"git.cin.su/m/Assistant/contracts/identity"
"git.cin.su/m/Assistant/contracts/interaction"
@@ -85,6 +86,10 @@ func main() {
log.Fatal(err)
}
defer interactionRepository.Close()
reminderClient, err := newContinuityReminderClient(envOr("CONTINUITY_RUNTIME_URL", "http://continuity-runtime:8081"), &http.Client{Timeout: 5 * time.Second})
if err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
authenticateCreator := func(w http.ResponseWriter, r *http.Request) (identity.CreatorSession, bool) {
session, err := identitydomain.ValidateCreatorSession(sessionKey, bearerToken(r), time.Now())
@@ -268,10 +273,63 @@ func main() {
ReceivedAtUnixMillis: utterance.ReceivedAtUnixMillis,
}
helpPath := workdomain.DetermineHelpPath(planningInput)
if isCapabilityQuestion(utterance.Content) {
kinds, err := capabilityRepository.AvailableCapabilityKinds(r.Context(), session.PrincipalID)
if err == nil && len(kinds) > 0 {
planningReply = "Сейчас у меня подключены реальные capabilities: " + humanCapabilityList(kinds) + ". Для рассуждений я использую отдельный Cognitive Gateway/Router, если локальный CANARY доступен. Возможности вроде отправки почты пока будут фиксироваться как capability gap, а не как выполненное действие."
} else {
planningReply = "Я умею записывать диалог и намерения, работать с текущим контекстом, строить планы и обращаться к Cognitive Gateway при доступности локального CANARY. Реальные action-capabilities подключаются через registry; если capability нет, я фиксирую gap."
}
needsCognition = false
}
if helpPath.Reply != "" {
planningReply = helpPath.Reply
needsCognition = false
}
var capabilityStatus capability.CapabilityResolutionStatus
var capabilityKind capability.CapabilityKind
var capabilityGapID capability.CapabilityGapID
var reminder *continuity.ProspectiveItem
if helpPath.Kind == work.HelpPathDirectCapability && len(helpPath.SuggestedCapabilityKinds) > 0 {
capabilityKind = helpPath.SuggestedCapabilityKinds[0]
started := time.Now()
resolution, resolveErr := resolveChatCapability(r.Context(), capabilityRepository, capabilityKind, session, intent, utterance, now)
capabilityStatus = resolution.Status
if resolveErr == nil && len(resolution.Eligible) > 0 {
reminderRequest, reminderErr := buildReminderRequest(utterance.Content, session, intent, utterance, now)
if reminderErr == nil {
result, scheduleErr := reminderClient.Schedule(r.Context(), r.Header.Get("Authorization"), reminderRequest)
finished := time.Now()
if scheduleErr == nil {
reminder = &result.Item
planningReply = fmt.Sprintf("Готово. Я создал напоминание: %s.", result.Item.Summary)
observeCapabilityUsage(r.Context(), capabilityRepository, resolution.Eligible[0], resolution.Requirement, capability.UsageSucceeded, string(result.Item.ID), started, finished)
} else {
planningReply = "Scheduling capability сейчас недоступна: напоминание не создано. Я не буду притворяться, что действие выполнено."
observeCapabilityUsage(r.Context(), capabilityRepository, resolution.Eligible[0], resolution.Requirement, capability.UsageFailed, "continuity-reminder-unavailable", started, finished)
}
} else {
planningReply = "Мне нужно уточнить время напоминания. Например: «Напомни завтра в 10 позвонить врачу»."
capabilityStatus = capability.ResolutionPartiallyAvailable
}
} else if resolution.Gap != nil {
capabilityGapID = resolution.Gap.ID
planningReply = "Сейчас scheduling capability недоступна, поэтому напоминание не создано. Я зафиксировал capability gap."
}
needsCognition = false
}
if helpPath.Kind == work.HelpPathCapabilityGap && len(helpPath.SuggestedCapabilityKinds) > 0 {
capabilityKind = helpPath.SuggestedCapabilityKinds[0]
resolution, resolveErr := resolveChatCapability(r.Context(), capabilityRepository, capabilityKind, session, intent, utterance, now)
capabilityStatus = resolution.Status
if resolveErr == nil {
planningReply = "Capability найдена, но в этом шаге она не исполняется без отдельного разрешённого action path."
} else if resolution.Gap != nil {
capabilityGapID = resolution.Gap.ID
planningReply = "Сейчас у меня ещё не подключена возможность отправлять почту. Я зафиксировал capability gap, без ложного выполнения."
}
needsCognition = false
}
if helpPath.NeedsDurableWork {
planning, err := workdomain.BuildPlanningResult(planningInput, now)
if err != nil {
@@ -289,6 +347,7 @@ func main() {
response := interaction.ChatMessageResponse{
ConversationID: conversation.ID,
UtteranceID: utterance.ID,
OriginalIntentID: intent.ID,
SpeechAct: semantic.Interpretation.SpeechAct,
InterpretationState: utterance.InterpretationState,
NeedsCognition: needsCognition,
@@ -298,6 +357,10 @@ func main() {
Clarification: semantic.Interpretation.Clarification,
WorkPlanned: workPlanned,
PlanningReply: planningReply,
CapabilityStatus: capabilityStatus,
CapabilityKind: capabilityKind,
CapabilityGapID: capabilityGapID,
Reminder: reminder,
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-store")
@@ -689,3 +752,30 @@ func readCredentialRefSecret(name, refName string) (string, error) {
Path: fileName,
})
}
func envOr(name, fallback string) string {
if value := os.Getenv(name); value != "" {
return value
}
return fallback
}
func isCapabilityQuestion(message string) bool {
lower := strings.ToLower(message)
return strings.Contains(lower, "что ты умеешь") || strings.Contains(lower, "что умеешь") || strings.Contains(lower, "какие возможности")
}
func humanCapabilityList(kinds []capability.CapabilityKind) string {
labels := make([]string, 0, len(kinds))
for _, kind := range kinds {
switch kind {
case capability.CapabilityKindScheduleReminder:
labels = append(labels, "создавать напоминания")
case capability.CapabilityKindCognitiveReasoning:
labels = append(labels, "отвечать через когнитивный gateway")
default:
labels = append(labels, strings.ToLower(strings.ReplaceAll(string(kind), "_", " ")))
}
}
return strings.Join(labels, ", ")
}
+34
View File
@@ -93,3 +93,37 @@ func TestChatMessageUsesCoreAndLocalOnlyCognitiveGateway(t *testing.T) {
t.Fatalf("unexpected trusted gateway request: %#v", cognitiveRequest)
}
}
func TestChatMessageReturnsHumanCapabilityMetadata(t *testing.T) {
core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodGet && r.URL.Path == "/v1/creator/sessions/current":
_ = json.NewEncoder(w).Encode(identity.CreatorSession{ActorID: "actor-1", PrincipalID: "principal-1"})
case r.Method == http.MethodPost && r.URL.Path == "/v1/creator/chat/messages":
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(interaction.ChatMessageResponse{ConversationID: "conversation-1", UtteranceID: "utterance-1", SpeechAct: interaction.SpeechActDirective, FallbackText: "fallback", PlanningReply: "Готово. Я создал напоминание: позвонить врачу.", CapabilityStatus: capability.ResolutionAvailable, CapabilityKind: capability.CapabilityKindScheduleReminder})
default:
t.Fatalf("unexpected core route %s %s", r.Method, r.URL.Path)
}
}))
defer core.Close()
handler, err := newHandler(core.URL, "http://continuity", "http://commit", core.Client(), "http://cognitive")
if err != nil {
t.Fatal(err)
}
request := httptest.NewRequest(http.MethodPost, "/api/chat/messages", strings.NewReader(`{"message":"Напомни завтра в 10 позвонить врачу."}`))
request.Header.Set("Authorization", "Bearer session-token")
request.Header.Set("Content-Type", "application/json")
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
}
var response browserChatResponse
if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
t.Fatal(err)
}
if response.ReplyText != "Готово. Я создал напоминание: позвонить врачу." || response.CapabilityStatus != string(capability.ResolutionAvailable) || response.CapabilityKind != string(capability.CapabilityKindScheduleReminder) {
t.Fatalf("unexpected browser response: %#v", response)
}
}
+4 -1
View File
@@ -151,6 +151,9 @@ type browserChatResponse struct {
ProjectProvenance string `json:"project_provenance"`
Clarification string `json:"clarification"`
WorkPlanned bool `json:"work_planned"`
CapabilityStatus string `json:"capability_status"`
CapabilityKind string `json:"capability_kind"`
ReminderScheduled bool `json:"reminder_scheduled"`
}
func (g *gateway) handleAdvisoryPolicy(w http.ResponseWriter, r *http.Request) {
@@ -340,7 +343,7 @@ func (g *gateway) handleChatMessage(w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-store")
_ = json.NewEncoder(w).Encode(browserChatResponse{ConversationID: string(saved.ConversationID), UtteranceID: string(saved.UtteranceID), SpeechAct: string(saved.SpeechAct), ReplyText: reply, Cognition: cognition, ActiveFocus: saved.ActiveProjectName, ProjectProvenance: string(saved.ProjectProvenance), Clarification: string(saved.Clarification), WorkPlanned: saved.WorkPlanned})
_ = json.NewEncoder(w).Encode(browserChatResponse{ConversationID: string(saved.ConversationID), UtteranceID: string(saved.UtteranceID), SpeechAct: string(saved.SpeechAct), ReplyText: reply, Cognition: cognition, ActiveFocus: saved.ActiveProjectName, ProjectProvenance: string(saved.ProjectProvenance), Clarification: string(saved.Clarification), WorkPlanned: saved.WorkPlanned, CapabilityStatus: string(saved.CapabilityStatus), CapabilityKind: string(saved.CapabilityKind), ReminderScheduled: saved.Reminder != nil})
}
func validAdvisoryResponse(response cognitive.GatewayAdvisoryResponse, request cognitive.GatewayAdvisoryRequest) bool {
+2
View File
@@ -81,6 +81,8 @@ chatForm.addEventListener("submit", async (event) => {
updateActiveFocus(result.active_focus, result.project_provenance);
const semanticMeta = [result.speech_act, `cognition=${result.cognition}`];
if (result.work_planned) semanticMeta.push("work=planned");
if (result.capability_status) semanticMeta.push(`capability=${result.capability_kind || "unknown"}:${result.capability_status}`);
if (result.reminder_scheduled) semanticMeta.push("reminder=scheduled");
if (result.clarification) semanticMeta.push(`clarification=${result.clarification}`);
appendMessage("assistant", result.reply_text || "Сообщение записано.", semanticMeta.join("; "));
} catch {
+162
View File
@@ -0,0 +1,162 @@
package capability
import (
"git.cin.su/m/Assistant/contracts/identity"
"git.cin.su/m/Assistant/platform/ids"
)
type CapabilityImplementationID ids.ID
type CapabilityResolutionID ids.ID
type CapabilityGapID ids.ID
type CapabilityUsageObservationID ids.ID
type RequirementRef string
type OriginalIntentRef string
type SourceUtteranceRef string
type ImplementationKind string
type ImplementationTargetRef string
type AuthorityPolicyRef string
type DataConstraint string
type CapabilityResolutionStatus string
type GapReason string
type AcquisitionStrategyKind string
type UsageOutcomeClass string
const (
ImplementationInternalFunction ImplementationKind = "INTERNAL_FUNCTION"
ImplementationCognitiveBinding ImplementationKind = "COGNITIVE_BINDING"
ImplementationExternalAdapter ImplementationKind = "EXTERNAL_ADAPTER"
ImplementationAgent ImplementationKind = "AGENT"
AuthorityPolicyCreatorDirectiveV1 AuthorityPolicyRef = "creator-directive-v1"
AuthorityPolicyCognitiveGatewayV1 AuthorityPolicyRef = "cognitive-gateway-v1"
DataConstraintLocalOnly DataConstraint = "LOCAL_ONLY"
DataConstraintExternalAllowed DataConstraint = "EXTERNAL_ALLOWED"
DataConstraintNoCredentialForward DataConstraint = "NO_CREDENTIAL_FORWARD"
ResolutionAvailable CapabilityResolutionStatus = "AVAILABLE"
ResolutionPartiallyAvailable CapabilityResolutionStatus = "PARTIALLY_AVAILABLE"
ResolutionUnavailable CapabilityResolutionStatus = "UNAVAILABLE"
ResolutionUnknown CapabilityResolutionStatus = "UNKNOWN"
GapNoImplementation GapReason = "NO_IMPLEMENTATION"
GapUnhealthy GapReason = "UNHEALTHY"
GapInactive GapReason = "INACTIVE"
GapUnqualified GapReason = "UNQUALIFIED"
GapUnauthorized GapReason = "UNAUTHORIZED"
GapIncompatible GapReason = "INCOMPATIBLE"
GapUnknown GapReason = "UNKNOWN"
AcquireUseAlternative AcquisitionStrategyKind = "USE_ALTERNATIVE"
AcquireDiscoverExisting AcquisitionStrategyKind = "DISCOVER_EXISTING"
AcquireConnectExisting AcquisitionStrategyKind = "CONNECT_EXISTING"
AcquireConfigureExisting AcquisitionStrategyKind = "CONFIGURE_EXISTING"
AcquireExternal AcquisitionStrategyKind = "ACQUIRE_EXTERNAL"
AcquireBuildNew AcquisitionStrategyKind = "BUILD_NEW"
AcquireRequestUserInput AcquisitionStrategyKind = "REQUEST_USER_INPUT"
AcquireUnresolvableCurrently AcquisitionStrategyKind = "UNRESOLVABLE_CURRENTLY"
UsageSucceeded UsageOutcomeClass = "SUCCEEDED"
UsageFailed UsageOutcomeClass = "FAILED"
UsageSkipped UsageOutcomeClass = "SKIPPED"
UsageUnknown UsageOutcomeClass = "UNKNOWN"
)
// CapabilityDescriptor names what Domovoy can obtain or do. CapabilityKind is
// intentionally extensible string semantics, not a closed-world enum.
type CapabilityDescriptor struct {
ID CapabilityID
Kind CapabilityKind
DisplayName string
Description string
Revision int64
RecordedAtUnixMillis int64
}
// CapabilityImplementation is the generic implementation/binding layer. It
// may point at a cognitive binding, internal function, agent, adapter or later
// provider type. CredentialRef is metadata only and never grants authority.
type CapabilityImplementation struct {
ID CapabilityImplementationID
CapabilityID CapabilityID
CapabilityKind CapabilityKind
OwnerPrincipalID identity.PrincipalID
Revision int64
Kind ImplementationKind
TargetRef ImplementationTargetRef
CredentialRef CredentialRef
Qualification QualificationState
Activation ActivationState
Health HealthState
AuthorityPolicyRef AuthorityPolicyRef
DataConstraints []DataConstraint
CostScoreMillis *int64
LatencyScoreMillis *int64
ReliabilityMillis *int64
ObservedAtUnixMillis int64
}
type CapabilityResolutionRequirement struct {
RequirementID RequirementRef
CapabilityKind CapabilityKind
CapabilityID CapabilityID
OwnerPrincipalID identity.PrincipalID
ActorID identity.ActorID
OriginalIntentID OriginalIntentRef
SourceUtteranceID SourceUtteranceRef
RequiredConstraints []DataConstraint
AllowCanary bool
AuthorityContextRef AuthorityPolicyRef
ObservedAtUnixMillis int64
}
type ResolvedCapabilityImplementation struct {
Implementation CapabilityImplementation
Selected bool
Rank int
}
type CapabilityResolution struct {
ID CapabilityResolutionID
Requirement CapabilityResolutionRequirement
Status CapabilityResolutionStatus
Eligible []ResolvedCapabilityImplementation
ExcludedReasons map[CapabilityImplementationID]GapReason
Gap *CapabilityGap
ObservedAtUnixMillis int64
}
type CapabilityGap struct {
ID CapabilityGapID
RequirementID RequirementRef
CapabilityKind CapabilityKind
CapabilityID CapabilityID
OwnerPrincipalID identity.PrincipalID
OriginalIntentID OriginalIntentRef
SourceUtteranceID SourceUtteranceRef
Reason GapReason
Detail string
Strategies []AcquisitionStrategyCandidate
RecordedAtUnixMillis int64
}
type AcquisitionStrategyCandidate struct {
Kind AcquisitionStrategyKind
Reason string
}
type CapabilityUsageObservation struct {
ID CapabilityUsageObservationID
ImplementationID CapabilityImplementationID
ImplementationRevision int64
CapabilityID CapabilityID
CapabilityKind CapabilityKind
OwnerPrincipalID identity.PrincipalID
OriginalIntentID OriginalIntentRef
SourceUtteranceID SourceUtteranceRef
Outcome UsageOutcomeClass
OutcomeRef string
LatencyMillis *int64
ObservedAtUnixMillis int64
}
+17
View File
@@ -34,11 +34,28 @@ type ProspectiveItem struct {
OwnerPrincipalID identity.PrincipalID
ScopeID authority.ScopeID
GoalID work.GoalID
SourceIntentID string
SourceUtteranceID string
Summary string
TriggerAtUnixMillis int64
State ProspectiveState
Revision ProspectiveRevision
}
type ScheduleReminderRequest struct {
OwnerPrincipalID identity.PrincipalID
ScopeID authority.ScopeID
SourceIntentID string
SourceUtteranceID string
Summary string
TriggerAtUnixMillis int64
RequestedAtUnixMillis int64
}
type ScheduleReminderResult struct {
Item ProspectiveItem
}
// SatisfyDeliveredSubject is the complete serializable operation subject. It
// is distinct from a Work TaskTarget, which remains the live justification.
type SatisfyDeliveredSubject struct {
+7
View File
@@ -1,6 +1,8 @@
package interaction
import (
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/continuity"
"git.cin.su/m/Assistant/contracts/identity"
"git.cin.su/m/Assistant/contracts/situation"
)
@@ -121,6 +123,7 @@ type ChatMessageRequest struct {
type ChatMessageResponse struct {
ConversationID ConversationID
UtteranceID HumanUtteranceID
OriginalIntentID OriginalHumanIntentID
SpeechAct SpeechAct
InterpretationState InterpretationState
NeedsCognition bool
@@ -130,4 +133,8 @@ type ChatMessageResponse struct {
Clarification ClarificationState
WorkPlanned bool
PlanningReply string
CapabilityStatus capability.CapabilityResolutionStatus
CapabilityKind capability.CapabilityKind
CapabilityGapID capability.CapabilityGapID
Reminder *continuity.ProspectiveItem
}
+130 -1
View File
@@ -407,6 +407,131 @@ CREATE TABLE IF NOT EXISTS capability.cognitive_performance_evidence (
CHECK (latency_micros IS NULL OR latency_micros >= 0), CHECK (memory_bytes IS NULL OR memory_bytes >= 0),
CHECK (reliability_millis IS NULL OR reliability_millis >= 0), CHECK (observed_at_unix_millis > 0)
);
CREATE TABLE IF NOT EXISTS capability.capability_descriptors (
capability_id TEXT PRIMARY KEY,
capability_kind TEXT NOT NULL,
display_name TEXT NOT NULL,
description TEXT NOT NULL,
revision BIGINT NOT NULL,
recorded_at_unix_millis BIGINT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (capability_id <> ''),
CHECK (capability_kind <> ''),
CHECK (display_name <> ''),
CHECK (revision > 0),
CHECK (recorded_at_unix_millis > 0)
);
CREATE TABLE IF NOT EXISTS capability.capability_implementations (
implementation_id TEXT NOT NULL,
capability_id TEXT NOT NULL,
capability_kind TEXT NOT NULL,
owner_principal_id TEXT NOT NULL,
revision BIGINT NOT NULL,
implementation_kind TEXT NOT NULL,
target_ref TEXT NOT NULL,
credential_ref TEXT NOT NULL DEFAULT '',
qualification TEXT NOT NULL,
activation TEXT NOT NULL,
health TEXT NOT NULL,
authority_policy_ref TEXT NOT NULL,
data_constraints TEXT[] NOT NULL,
cost_score_millis BIGINT,
latency_score_millis BIGINT,
reliability_millis BIGINT,
observed_at_unix_millis BIGINT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (implementation_id, revision),
CHECK (implementation_id <> ''),
CHECK (capability_id <> ''),
CHECK (capability_kind <> ''),
CHECK (owner_principal_id <> ''),
CHECK (revision > 0),
CHECK (implementation_kind IN ('INTERNAL_FUNCTION','COGNITIVE_BINDING','EXTERNAL_ADAPTER','AGENT')),
CHECK (target_ref <> ''),
CHECK (qualification IN ('DISCOVERED','PROBING','EVALUATING','VALIDATED','REJECTED')),
CHECK (activation IN ('INACTIVE','CANARY','ACTIVE','DEPRECATED','RETIRED')),
CHECK (health IN ('UNKNOWN','AVAILABLE','DEGRADED','UNAVAILABLE')),
CHECK (authority_policy_ref <> ''),
CHECK (cardinality(data_constraints) > 0),
CHECK (cost_score_millis IS NULL OR cost_score_millis >= 0),
CHECK (latency_score_millis IS NULL OR latency_score_millis >= 0),
CHECK (reliability_millis IS NULL OR reliability_millis >= 0),
CHECK (observed_at_unix_millis > 0)
);
CREATE TABLE IF NOT EXISTS capability.capability_implementation_heads (
implementation_id TEXT PRIMARY KEY,
revision BIGINT NOT NULL,
capability_kind TEXT NOT NULL,
owner_principal_id TEXT NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (revision > 0)
);
CREATE TABLE IF NOT EXISTS capability.capability_gaps (
gap_id TEXT PRIMARY KEY,
requirement_id TEXT NOT NULL,
capability_kind TEXT NOT NULL,
capability_id TEXT NOT NULL DEFAULT '',
owner_principal_id TEXT NOT NULL,
original_intent_id TEXT NOT NULL,
source_utterance_id TEXT NOT NULL,
reason TEXT NOT NULL,
detail TEXT NOT NULL,
strategies TEXT[] NOT NULL,
recorded_at_unix_millis BIGINT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (requirement_id <> ''),
CHECK (capability_kind <> ''),
CHECK (owner_principal_id <> ''),
CHECK (original_intent_id <> ''),
CHECK (source_utterance_id <> ''),
CHECK (reason IN ('NO_IMPLEMENTATION','UNHEALTHY','INACTIVE','UNQUALIFIED','UNAUTHORIZED','INCOMPATIBLE','UNKNOWN')),
CHECK (detail <> ''),
CHECK (cardinality(strategies) > 0),
CHECK (recorded_at_unix_millis > 0)
);
CREATE TABLE IF NOT EXISTS capability.capability_usage_observations (
observation_id TEXT PRIMARY KEY,
implementation_id TEXT NOT NULL,
implementation_revision BIGINT NOT NULL,
capability_id TEXT NOT NULL,
capability_kind TEXT NOT NULL,
owner_principal_id TEXT NOT NULL,
original_intent_id TEXT NOT NULL,
source_utterance_id TEXT NOT NULL,
outcome TEXT NOT NULL,
outcome_ref TEXT NOT NULL DEFAULT '',
latency_millis BIGINT,
observed_at_unix_millis BIGINT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (implementation_id <> ''),
CHECK (implementation_revision > 0),
CHECK (capability_id <> ''),
CHECK (capability_kind <> ''),
CHECK (owner_principal_id <> ''),
CHECK (original_intent_id <> ''),
CHECK (source_utterance_id <> ''),
CHECK (outcome IN ('SUCCEEDED','FAILED','SKIPPED','UNKNOWN')),
CHECK (latency_millis IS NULL OR latency_millis >= 0),
CHECK (observed_at_unix_millis > 0)
);
INSERT INTO capability.capability_descriptors(capability_id, capability_kind, display_name, description, revision, recorded_at_unix_millis)
VALUES
('schedule-reminder', 'SCHEDULE_REMINDER', 'Reminder scheduling', 'Create a Continuity-owned prospective reminder item from an authenticated human directive.', 1, 1),
('creator-advisory', 'COGNITIVE_REASONING', 'Creator advisory cognition', 'Reasoning capability resolved through the specialized Cognitive Registry and Gateway.', 1, 1)
ON CONFLICT (capability_id) DO NOTHING;
INSERT INTO capability.capability_implementations(
implementation_id, capability_id, capability_kind, owner_principal_id, revision, implementation_kind, target_ref,
credential_ref, qualification, activation, health, authority_policy_ref, data_constraints,
cost_score_millis, latency_score_millis, reliability_millis, observed_at_unix_millis)
VALUES (
'internal-reminder-scheduler-v1', 'schedule-reminder', 'SCHEDULE_REMINDER', 'domovoy-creator-principal',
1, 'INTERNAL_FUNCTION', 'continuity://prospective-reminder-v1', '', 'VALIDATED', 'ACTIVE', 'AVAILABLE',
'creator-directive-v1', ARRAY['LOCAL_ONLY','NO_CREDENTIAL_FORWARD'], 0, 10, 950, 1
)
ON CONFLICT DO NOTHING;
INSERT INTO capability.capability_implementation_heads(implementation_id, revision, capability_kind, owner_principal_id)
VALUES ('internal-reminder-scheduler-v1', 1, 'SCHEDULE_REMINDER', 'domovoy-creator-principal')
ON CONFLICT (implementation_id) DO NOTHING;
RESET ROLE;
SET ROLE continuity_role;
@@ -416,11 +541,15 @@ CREATE TABLE IF NOT EXISTS continuity.prospective_items (
owner_principal_id TEXT NOT NULL,
scope_id TEXT NOT NULL,
goal_id TEXT NOT NULL,
source_intent_id TEXT NOT NULL DEFAULT '',
source_utterance_id TEXT NOT NULL DEFAULT '',
summary TEXT NOT NULL DEFAULT '',
trigger_at_unix_millis BIGINT NOT NULL,
state TEXT NOT NULL,
revision BIGINT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (revision > 0)
CHECK (revision > 0),
CHECK (state <> 'ARMED' OR summary <> '')
);
CREATE TABLE IF NOT EXISTS continuity.action_receipts (
action_intent_id TEXT PRIMARY KEY,
@@ -0,0 +1,139 @@
BEGIN;
SET ROLE capability_role;
CREATE TABLE IF NOT EXISTS capability.capability_descriptors (
capability_id TEXT PRIMARY KEY,
capability_kind TEXT NOT NULL,
display_name TEXT NOT NULL,
description TEXT NOT NULL,
revision BIGINT NOT NULL,
recorded_at_unix_millis BIGINT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (capability_id <> ''),
CHECK (capability_kind <> ''),
CHECK (display_name <> ''),
CHECK (revision > 0),
CHECK (recorded_at_unix_millis > 0)
);
CREATE TABLE IF NOT EXISTS capability.capability_implementations (
implementation_id TEXT NOT NULL,
capability_id TEXT NOT NULL,
capability_kind TEXT NOT NULL,
owner_principal_id TEXT NOT NULL,
revision BIGINT NOT NULL,
implementation_kind TEXT NOT NULL,
target_ref TEXT NOT NULL,
credential_ref TEXT NOT NULL DEFAULT '',
qualification TEXT NOT NULL,
activation TEXT NOT NULL,
health TEXT NOT NULL,
authority_policy_ref TEXT NOT NULL,
data_constraints TEXT[] NOT NULL,
cost_score_millis BIGINT,
latency_score_millis BIGINT,
reliability_millis BIGINT,
observed_at_unix_millis BIGINT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (implementation_id, revision),
CHECK (implementation_id <> ''),
CHECK (capability_id <> ''),
CHECK (capability_kind <> ''),
CHECK (owner_principal_id <> ''),
CHECK (revision > 0),
CHECK (implementation_kind IN ('INTERNAL_FUNCTION','COGNITIVE_BINDING','EXTERNAL_ADAPTER','AGENT')),
CHECK (target_ref <> ''),
CHECK (qualification IN ('DISCOVERED','PROBING','EVALUATING','VALIDATED','REJECTED')),
CHECK (activation IN ('INACTIVE','CANARY','ACTIVE','DEPRECATED','RETIRED')),
CHECK (health IN ('UNKNOWN','AVAILABLE','DEGRADED','UNAVAILABLE')),
CHECK (authority_policy_ref <> ''),
CHECK (cardinality(data_constraints) > 0),
CHECK (cost_score_millis IS NULL OR cost_score_millis >= 0),
CHECK (latency_score_millis IS NULL OR latency_score_millis >= 0),
CHECK (reliability_millis IS NULL OR reliability_millis >= 0),
CHECK (observed_at_unix_millis > 0)
);
CREATE TABLE IF NOT EXISTS capability.capability_implementation_heads (
implementation_id TEXT PRIMARY KEY,
revision BIGINT NOT NULL,
capability_kind TEXT NOT NULL,
owner_principal_id TEXT NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (revision > 0)
);
CREATE TABLE IF NOT EXISTS capability.capability_gaps (
gap_id TEXT PRIMARY KEY,
requirement_id TEXT NOT NULL,
capability_kind TEXT NOT NULL,
capability_id TEXT NOT NULL DEFAULT '',
owner_principal_id TEXT NOT NULL,
original_intent_id TEXT NOT NULL,
source_utterance_id TEXT NOT NULL,
reason TEXT NOT NULL,
detail TEXT NOT NULL,
strategies TEXT[] NOT NULL,
recorded_at_unix_millis BIGINT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (requirement_id <> ''),
CHECK (capability_kind <> ''),
CHECK (owner_principal_id <> ''),
CHECK (original_intent_id <> ''),
CHECK (source_utterance_id <> ''),
CHECK (reason IN ('NO_IMPLEMENTATION','UNHEALTHY','INACTIVE','UNQUALIFIED','UNAUTHORIZED','INCOMPATIBLE','UNKNOWN')),
CHECK (detail <> ''),
CHECK (cardinality(strategies) > 0),
CHECK (recorded_at_unix_millis > 0)
);
CREATE TABLE IF NOT EXISTS capability.capability_usage_observations (
observation_id TEXT PRIMARY KEY,
implementation_id TEXT NOT NULL,
implementation_revision BIGINT NOT NULL,
capability_id TEXT NOT NULL,
capability_kind TEXT NOT NULL,
owner_principal_id TEXT NOT NULL,
original_intent_id TEXT NOT NULL,
source_utterance_id TEXT NOT NULL,
outcome TEXT NOT NULL,
outcome_ref TEXT NOT NULL DEFAULT '',
latency_millis BIGINT,
observed_at_unix_millis BIGINT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (implementation_id <> ''),
CHECK (implementation_revision > 0),
CHECK (capability_id <> ''),
CHECK (capability_kind <> ''),
CHECK (owner_principal_id <> ''),
CHECK (original_intent_id <> ''),
CHECK (source_utterance_id <> ''),
CHECK (outcome IN ('SUCCEEDED','FAILED','SKIPPED','UNKNOWN')),
CHECK (latency_millis IS NULL OR latency_millis >= 0),
CHECK (observed_at_unix_millis > 0)
);
INSERT INTO capability.capability_descriptors(capability_id, capability_kind, display_name, description, revision, recorded_at_unix_millis)
VALUES
('schedule-reminder', 'SCHEDULE_REMINDER', 'Reminder scheduling', 'Create a Continuity-owned prospective reminder item from an authenticated human directive.', 1, 1),
('creator-advisory', 'COGNITIVE_REASONING', 'Creator advisory cognition', 'Reasoning capability resolved through the specialized Cognitive Registry and Gateway.', 1, 1)
ON CONFLICT (capability_id) DO NOTHING;
INSERT INTO capability.capability_implementations(
implementation_id, capability_id, capability_kind, owner_principal_id, revision, implementation_kind, target_ref,
credential_ref, qualification, activation, health, authority_policy_ref, data_constraints,
cost_score_millis, latency_score_millis, reliability_millis, observed_at_unix_millis)
VALUES (
'internal-reminder-scheduler-v1', 'schedule-reminder', 'SCHEDULE_REMINDER', 'domovoy-creator-principal',
1, 'INTERNAL_FUNCTION', 'continuity://prospective-reminder-v1', '', 'VALIDATED', 'ACTIVE', 'AVAILABLE',
'creator-directive-v1', ARRAY['LOCAL_ONLY','NO_CREDENTIAL_FORWARD'], 0, 10, 950, 1
)
ON CONFLICT DO NOTHING;
INSERT INTO capability.capability_implementation_heads(implementation_id, revision, capability_kind, owner_principal_id)
VALUES ('internal-reminder-scheduler-v1', 1, 'SCHEDULE_REMINDER', 'domovoy-creator-principal')
ON CONFLICT (implementation_id) DO NOTHING;
RESET ROLE;
COMMIT;
@@ -0,0 +1,20 @@
BEGIN;
SET ROLE continuity_role;
ALTER TABLE continuity.prospective_items ADD COLUMN IF NOT EXISTS source_intent_id TEXT NOT NULL DEFAULT '';
ALTER TABLE continuity.prospective_items ADD COLUMN IF NOT EXISTS source_utterance_id TEXT NOT NULL DEFAULT '';
ALTER TABLE continuity.prospective_items ADD COLUMN IF NOT EXISTS summary TEXT NOT NULL DEFAULT '';
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='prospective_items_reminder_metadata_v1' AND conrelid='continuity.prospective_items'::regclass) THEN
ALTER TABLE continuity.prospective_items ADD CONSTRAINT prospective_items_reminder_metadata_v1 CHECK (
state <> 'ARMED' OR summary <> ''
) NOT VALID;
END IF;
END $$;
ALTER TABLE continuity.prospective_items VALIDATE CONSTRAINT prospective_items_reminder_metadata_v1;
RESET ROLE;
COMMIT;
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000063-intent-work-planning-foundation.json",
"task_id": "DEV-000063",
"task_file": "dev/tasks/DEV-000064-capability-resolution-selection-gap-foundation.json",
"task_id": "DEV-000064",
"task_revision": 1
}
@@ -0,0 +1,161 @@
{
"task_id": "DEV-000064",
"task_revision": 1,
"architecture_revision": "ADR-0013",
"base_repo_revision": "fe531ab56f43bb37a5786e38b094781d3eccb775",
"risk_tier": "E",
"intent": "Add provider-neutral capability implementation resolution, selection and typed gap handling foundation, with reminder scheduling as the first production end-to-end capability.",
"requirement_ids": [
"ADR-0013",
"INV-AUTH-001",
"INV-EPI-001",
"INV-OWN-001",
"Capability availability is not Authority",
"Cognitive Registry remains the specialized cognitive capability registry"
],
"allowed_change_scope": {
"modules": [
"capability",
"continuity",
"interaction",
"work"
],
"paths": [
"architecture/adrs/ADR-0013-capability-resolution-selection-gap-foundation.md",
"architecture/architecture-registry.yaml",
"architecture/architecture-registry.json",
"contracts/capability/**",
"contracts/continuity/**",
"contracts/interaction/**",
"contracts/work/**",
"modules/capability/**",
"modules/continuity/**",
"modules/work/**",
"adapters/postgres/**",
"database/bootstrap/**",
"database/migrations/**",
"cmd/core-runtime/**",
"cmd/continuity-runtime/**",
"cmd/creator-web/**",
"tests/capability_resolution/**",
"tests/owner_acl/**",
"tests/cognitive_advisory/**",
"docs/PROJECT_STATUS.md",
"dev/ACTIVE_TASK.json",
"dev/tasks/DEV-000064-capability-resolution-selection-gap-foundation.json"
]
},
"forbidden_change_scope": {
"paths": [
"architecture/ARCHITECTURE_CONSTITUTION.md",
"modules/authority/**",
"modules/commit/**",
"cmd/commit-runtime/**",
"security/**",
"ci/**",
".gitea/**"
]
},
"allowed_architecture_delta": {
"new_cross_module_dependencies": 3,
"new_canonical_owners": 0,
"new_privileges": 0,
"new_third_party_dependencies": 0,
"new_provider_adapters": 0
},
"contracts": {
"consume": [
"Work CapabilityRequirement",
"Interaction OriginalHumanIntent",
"Continuity ProspectiveItem",
"Cognitive Registry / Router",
"Secret Resolver CredentialRef",
"Authority boundary concepts"
],
"may_change": [
"contracts/capability@v1",
"contracts/continuity@v1",
"contracts/interaction@v1"
]
},
"semantic_owner": "capability",
"canonical_state": [
"Capability",
"CapabilityImplementation",
"CapabilityResolution",
"CapabilitySelection",
"CapabilityGap",
"AcquisitionStrategyCandidate",
"CapabilityUsageObservation"
],
"state_transitions": [
"implementation qualification DISCOVERED/PROBING/EVALUATING/VALIDATED/REJECTED",
"implementation activation INACTIVE/CANARY/ACTIVE/DEPRECATED/RETIRED",
"implementation health UNKNOWN/AVAILABLE/DEGRADED/UNAVAILABLE",
"resolution AVAILABLE/PARTIALLY_AVAILABLE/UNAVAILABLE/UNKNOWN",
"gap records reasons without executing or granting Authority"
],
"side_effects_and_privileges": [
"Capability registry writes are owner-local",
"Reminder scheduling creates Continuity-owned ProspectiveItem through Continuity runtime boundary",
"No external mutation",
"No Commit/Executor invocation for reminder registration",
"No ACTIVE cognitive binding promotion",
"No OpenAI activation",
"CredentialRef is metadata and does not grant Authority"
],
"persistence_and_migrations": [
"capability owner-local implementation registry",
"capability owner-local gap records",
"capability owner-local generic usage observations",
"continuity owner-local reminder creation endpoint using existing prospective_items",
"no cross-owner FK",
"no cross-owner SQL join dependency"
],
"failure_retry_restart": [
"registered implementation can be unavailable without breaking startup",
"resolver fails closed when implementation is missing, unhealthy, inactive, unqualified or unauthorized",
"capability gap is persisted with OriginalHumanIntent correlation",
"reminder creation is persisted as a Continuity ProspectiveItem",
"cognitive router remains authoritative for cognitive invocation"
],
"assumptions": [
"DEV-000063 is production baseline",
"Capability owner is canonical Capability registry owner per Core Semantic Model",
"Continuity owner remains canonical ProspectiveItem owner",
"existing CPU CANARY remains not ACTIVE",
"docker-test prohibited"
],
"acceptance_scenarios": [
"Reminder resolves to scheduling implementation",
"Reminder is actually created and can be verified",
"Simple question still works without durable Work",
"Unsupported SEND_EMAIL returns persisted CapabilityGap",
"Unavailable implementation is excluded",
"Same capability with two implementations can be represented",
"Selection does not depend on hardcoded provider name",
"CredentialRef does not grant Authority",
"Capability is not Authority",
"Capability gap preserves OriginalHumanIntent correlation",
"One Goal can require multiple capabilities",
"Cognitive binding remains functioning",
"RTX unavailable does not break CPU cognition",
"LLM-free mode passes"
],
"required_tests": [
"go test ./...",
"go vet ./...",
"go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json",
"go run ./cmd/devctl check-architecture",
"go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json",
"tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1",
"tests/owner_acl/verify.ps1 -DockerContext docker-prod -PostgresContainer assistant-postgres-1",
"production live /chat acceptance"
],
"terminal_gap_states": [
"BLOCKED_BY_ARCHITECTURE_GAP",
"BLOCKED_BY_SPEC_GAP",
"BLOCKED_BY_MISSING_CAPABILITY",
"BLOCKED_BY_CONFLICT"
]
}
+22 -5
View File
@@ -1,17 +1,34 @@
# Assistant project status
Audit date: 2026-08-17. Active implementation: DEV-000063 revision 1.
Audit date: 2026-08-17. Active implementation: DEV-000064 revision 1.
## DEV-000064 checkpoint
- `DEV_000064_STATUS = IMPLEMENTED_LOCAL`
- `LAST_COMPLETED = ADR-0013, Capability-owned generic implementation registry/resolution/selection/gap contracts, deterministic evidence-weighted resolver V1, capability gap acquisition strategy foundation, generic capability usage observations, Continuity reminder metadata, authenticated creator reminder registration endpoint, /chat reminder scheduling path, /chat SEND_EMAIL gap path, capability-aware "what can you do" response`
- `CAPABILITY_REGISTRY_STATUS = generic CapabilityImplementation + CapabilityGap + CapabilityUsageObservation foundation added under existing capability owner`
- `COGNITIVE_RELATION_STATUS = cognition remains resolved/invoked through existing Cognitive Registry/Gateway; no parallel cognitive router introduced`
- `REMINDER_CAPABILITY_STATUS = internal-reminder-scheduler-v1 seeded as VALIDATED/ACTIVE/AVAILABLE implementation for SCHEDULE_REMINDER`
- `CAPABILITY_GAP_STATUS = unsupported SEND_EMAIL persists CapabilityGap with OriginalHumanIntent/Utterance correlation`
- `AUTHORITY_STATUS = capability and CredentialRef do not grant Authority; reminder registration requires authenticated creator session and creates only Continuity-owned prospective state`
- `ACTIVE_COGNITIVE_BINDINGS = none; CPU CANARY unchanged`
- `OPENAI_REAL_CREDENTIAL = not required / not activated`
- `TESTS_PASS = go test ./...; go vet ./...; go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json; go run ./cmd/devctl check-architecture; go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json; tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1`
- `PENDING = production migration/deployment/live acceptance/PR promotion`
## DEV-000063 checkpoint
- `DEV_000063_STATUS = IN_PROGRESS`
- `LAST_COMPLETED = ADR-0012, HelpPath optionality foundation, Work-owned Goal/PlanCandidate/WorkPlan/PlanStep/CapabilityRequirement/DelegationCandidate contracts, deterministic planning V1, optional durable Work creation from /chat, capability-gap/self-extension readiness tests`
- `DEV_000063_STATUS = COMPLETED`
- `LAST_COMPLETED = ADR-0012, HelpPath optionality foundation, Work-owned Goal/PlanCandidate/WorkPlan/PlanStep/CapabilityRequirement/DelegationCandidate contracts, deterministic planning V1, optional durable Work creation from /chat, capability-gap/self-extension readiness tests, migration 017, production deployment and live /chat acceptance`
- `HELP_PATH_OPTIONALITY = enabled; simple answer/cognition/direct capability/capability gap can bypass durable Goal/Work/Plan`
- `GOAL_WORK_PLAN_OPTIONALITY = enabled; durable records are created only for complex work/planning cases`
- `PROJECT_NEUTRALITY = project context is optional opaque Work reference; Project is not mandatory context`
- `CODEX_NEUTRALITY = no Codex-specific canonical entity or executor selection`
- `TESTS_PASS = go test ./...; go vet ./...; go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json; go run ./cmd/devctl check-architecture; go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json; tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1`
- `NEXT_STEP = commit, push, PR/promotion, migration 017, production deploy, live /chat acceptance`
- `TESTS_PASS = go test ./...; go vet ./...; go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json; go run ./cmd/devctl check-architecture; go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json; tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1; tests/owner_acl/verify.ps1 -DockerContext docker-prod -PostgresContainer assistant-postgres-1; production live /chat acceptance`
- `LIVE_ACCEPTANCE = PASS: simple help uses cognition without Work; reminder uses direct scheduling-capability path without Work; complex УПО investigation creates one Work Goal/Plan; capability gap remains executor-neutral without Work`
- `PRODUCTION_REVISION = 9d50a3ec635782b59ab3335808d6d1e026be48a4`
- `PRODUCTION_HEALTH = healthy`
- `NEXT_STEP = DEV-000064 capability resolution/acquisition or concrete scheduler/mail/codex adapter foundation, if separately approved`
- `REAL_BLOCKERS = none known`
## DEV-000062 checkpoint
+192
View File
@@ -0,0 +1,192 @@
package domain
import (
"errors"
"slices"
"sort"
"strings"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/platform/ids"
)
var ErrInvalidCapabilityImplementation = errors.New("invalid capability implementation")
var ErrInvalidCapabilityRequirement = errors.New("invalid capability requirement")
var ErrNoCapabilityImplementation = errors.New("no eligible capability implementation")
func ValidateCapabilityImplementation(value capability.CapabilityImplementation) error {
if value.ID == "" || value.CapabilityID == "" || value.CapabilityKind == "" || value.OwnerPrincipalID == "" ||
value.Revision < 1 || value.Kind == "" || value.TargetRef == "" || value.AuthorityPolicyRef == "" ||
value.ObservedAtUnixMillis < 1 || !validQualification(value.Qualification) || !validActivation(value.Activation) ||
!validHealth(value.Health) {
return ErrInvalidCapabilityImplementation
}
switch value.Kind {
case capability.ImplementationInternalFunction, capability.ImplementationCognitiveBinding, capability.ImplementationExternalAdapter, capability.ImplementationAgent:
default:
return ErrInvalidCapabilityImplementation
}
for _, constraint := range value.DataConstraints {
switch constraint {
case capability.DataConstraintLocalOnly, capability.DataConstraintExternalAllowed, capability.DataConstraintNoCredentialForward:
default:
return ErrInvalidCapabilityImplementation
}
}
return nil
}
func ResolveCapability(requirement capability.CapabilityResolutionRequirement, candidates []capability.CapabilityImplementation) (capability.CapabilityResolution, error) {
if requirement.CapabilityKind == "" || requirement.OwnerPrincipalID == "" || requirement.OriginalIntentID == "" || requirement.SourceUtteranceID == "" || requirement.ObservedAtUnixMillis < 1 {
return capability.CapabilityResolution{}, ErrInvalidCapabilityRequirement
}
resolutionID, err := ids.New()
if err != nil {
return capability.CapabilityResolution{}, err
}
resolution := capability.CapabilityResolution{
ID: capability.CapabilityResolutionID(resolutionID),
Requirement: requirement,
Status: capability.ResolutionUnavailable,
ExcludedReasons: map[capability.CapabilityImplementationID]capability.GapReason{},
ObservedAtUnixMillis: requirement.ObservedAtUnixMillis,
}
eligible := make([]capability.CapabilityImplementation, 0, len(candidates))
anyForCapability := false
dominantReason := capability.GapNoImplementation
for _, candidate := range candidates {
if candidate.CapabilityKind != requirement.CapabilityKind {
continue
}
if requirement.CapabilityID != "" && candidate.CapabilityID != requirement.CapabilityID {
continue
}
anyForCapability = true
reason := exclusionReason(requirement, candidate)
if reason != "" {
resolution.ExcludedReasons[candidate.ID] = reason
if dominantReason == capability.GapNoImplementation {
dominantReason = reason
}
continue
}
eligible = append(eligible, candidate)
}
if len(eligible) == 0 {
if !anyForCapability {
dominantReason = capability.GapNoImplementation
}
gap, err := BuildCapabilityGap(requirement, dominantReason, gapDetail(dominantReason), requirement.ObservedAtUnixMillis)
if err != nil {
return capability.CapabilityResolution{}, err
}
resolution.Gap = &gap
return resolution, ErrNoCapabilityImplementation
}
sort.SliceStable(eligible, func(i, j int) bool { return betterImplementation(eligible[i], eligible[j]) })
resolution.Status = capability.ResolutionAvailable
resolution.Eligible = make([]capability.ResolvedCapabilityImplementation, 0, len(eligible))
for index, item := range eligible {
resolution.Eligible = append(resolution.Eligible, capability.ResolvedCapabilityImplementation{Implementation: item, Selected: index == 0, Rank: index + 1})
}
return resolution, nil
}
func BuildCapabilityGap(requirement capability.CapabilityResolutionRequirement, reason capability.GapReason, detail string, millis int64) (capability.CapabilityGap, error) {
id, err := ids.New()
if err != nil {
return capability.CapabilityGap{}, err
}
return capability.CapabilityGap{
ID: capability.CapabilityGapID(id),
RequirementID: requirement.RequirementID,
CapabilityKind: requirement.CapabilityKind,
CapabilityID: requirement.CapabilityID,
OwnerPrincipalID: requirement.OwnerPrincipalID,
OriginalIntentID: requirement.OriginalIntentID,
SourceUtteranceID: requirement.SourceUtteranceID,
Reason: reason,
Detail: detail,
Strategies: acquisitionStrategies(reason),
RecordedAtUnixMillis: millis,
}, nil
}
func exclusionReason(requirement capability.CapabilityResolutionRequirement, candidate capability.CapabilityImplementation) capability.GapReason {
if ValidateCapabilityImplementation(candidate) != nil {
return capability.GapIncompatible
}
if candidate.OwnerPrincipalID != requirement.OwnerPrincipalID {
return capability.GapUnauthorized
}
if candidate.Qualification != capability.QualificationValidated {
return capability.GapUnqualified
}
if candidate.Activation != capability.ActivationActive && !(requirement.AllowCanary && candidate.Activation == capability.ActivationCanary) {
return capability.GapInactive
}
if candidate.Health != capability.HealthAvailable {
return capability.GapUnhealthy
}
if requirement.AuthorityContextRef != "" && candidate.AuthorityPolicyRef != requirement.AuthorityContextRef {
return capability.GapUnauthorized
}
for _, required := range requirement.RequiredConstraints {
if !slices.Contains(candidate.DataConstraints, required) {
return capability.GapIncompatible
}
}
return ""
}
func betterImplementation(left, right capability.CapabilityImplementation) bool {
if left.ReliabilityMillis != nil && right.ReliabilityMillis != nil && *left.ReliabilityMillis != *right.ReliabilityMillis {
return *left.ReliabilityMillis > *right.ReliabilityMillis
}
if left.LatencyScoreMillis != nil && right.LatencyScoreMillis != nil && *left.LatencyScoreMillis != *right.LatencyScoreMillis {
return *left.LatencyScoreMillis < *right.LatencyScoreMillis
}
if left.CostScoreMillis != nil && right.CostScoreMillis != nil && *left.CostScoreMillis != *right.CostScoreMillis {
return *left.CostScoreMillis < *right.CostScoreMillis
}
return string(left.ID) < string(right.ID)
}
func gapDetail(reason capability.GapReason) string {
switch reason {
case capability.GapNoImplementation:
return "no registered implementation satisfies this capability requirement"
case capability.GapUnhealthy:
return "registered implementations are currently unavailable or degraded"
case capability.GapInactive:
return "registered implementations are not active for this use"
case capability.GapUnqualified:
return "registered implementations are not validated"
case capability.GapUnauthorized:
return "registered implementations are not allowed by the current authority or policy context"
case capability.GapIncompatible:
return "registered implementations do not match the required modality, data or environment constraints"
default:
return "capability availability is unknown"
}
}
func acquisitionStrategies(reason capability.GapReason) []capability.AcquisitionStrategyCandidate {
switch reason {
case capability.GapNoImplementation:
return []capability.AcquisitionStrategyCandidate{
{Kind: capability.AcquireDiscoverExisting, Reason: "look for an already available adapter or service"},
{Kind: capability.AcquireConnectExisting, Reason: "connect an existing provider after explicit authority"},
{Kind: capability.AcquireBuildNew, Reason: "build a new implementation through a future approved coding capability"},
}
case capability.GapUnhealthy:
return []capability.AcquisitionStrategyCandidate{
{Kind: capability.AcquireUseAlternative, Reason: "try another eligible implementation"},
{Kind: capability.AcquireConfigureExisting, Reason: "repair or reconfigure the registered implementation"},
}
case capability.GapUnauthorized:
return []capability.AcquisitionStrategyCandidate{{Kind: capability.AcquireRequestUserInput, Reason: "request explicit authority or policy decision"}}
default:
return []capability.AcquisitionStrategyCandidate{{Kind: capability.AcquireUnresolvableCurrently, Reason: strings.ToLower(gapDetail(reason))}}
}
}
@@ -0,0 +1,98 @@
package domain
import (
"errors"
"strings"
"testing"
"git.cin.su/m/Assistant/contracts/capability"
)
func TestReminderResolvesToSchedulingImplementation(t *testing.T) {
requirement := resolutionRequirement(capability.CapabilityKindScheduleReminder)
resolution, err := ResolveCapability(requirement, []capability.CapabilityImplementation{implementation("scheduler-b", capability.CapabilityKindScheduleReminder, 900, 50), implementation("scheduler-a", capability.CapabilityKindScheduleReminder, 950, 75)})
if err != nil {
t.Fatal(err)
}
if resolution.Status != capability.ResolutionAvailable || len(resolution.Eligible) != 2 || !resolution.Eligible[0].Selected || resolution.Eligible[0].Implementation.ID != "scheduler-a" {
t.Fatalf("unexpected resolution: %#v", resolution)
}
}
func TestUnavailableImplementationExcluded(t *testing.T) {
candidate := implementation("scheduler", capability.CapabilityKindScheduleReminder, 900, 50)
candidate.Health = capability.HealthUnavailable
resolution, err := ResolveCapability(resolutionRequirement(capability.CapabilityKindScheduleReminder), []capability.CapabilityImplementation{candidate})
if !errors.Is(err, ErrNoCapabilityImplementation) || resolution.Gap == nil || resolution.Gap.Reason != capability.GapUnhealthy {
t.Fatalf("expected unhealthy gap, got resolution=%#v err=%v", resolution, err)
}
}
func TestSelectionDoesNotDependOnProviderName(t *testing.T) {
left := implementation("zzz-provider-name", capability.CapabilityKindScheduleReminder, 980, 20)
right := implementation("aaa-provider-name", capability.CapabilityKindScheduleReminder, 900, 10)
resolution, err := ResolveCapability(resolutionRequirement(capability.CapabilityKindScheduleReminder), []capability.CapabilityImplementation{left, right})
if err != nil {
t.Fatal(err)
}
if resolution.Eligible[0].Implementation.ID != left.ID {
t.Fatalf("selection appears provider-name driven: %#v", resolution.Eligible)
}
}
func TestCredentialRefDoesNotGrantAuthority(t *testing.T) {
candidate := implementation("mail", capability.CapabilityKindSendEmail, 900, 50)
candidate.CredentialRef = "credential://mail/token"
candidate.AuthorityPolicyRef = "mail-send-approved-v1"
resolution, err := ResolveCapability(resolutionRequirement(capability.CapabilityKindSendEmail), []capability.CapabilityImplementation{candidate})
if !errors.Is(err, ErrNoCapabilityImplementation) || resolution.Gap == nil || resolution.Gap.Reason != capability.GapUnauthorized {
t.Fatalf("credential ref must not grant authority: resolution=%#v err=%v", resolution, err)
}
}
func TestCapabilityGapPreservesOriginalIntentCorrelation(t *testing.T) {
requirement := resolutionRequirement(capability.CapabilityKindSendEmail)
resolution, err := ResolveCapability(requirement, nil)
if !errors.Is(err, ErrNoCapabilityImplementation) || resolution.Gap == nil {
t.Fatalf("expected gap, got resolution=%#v err=%v", resolution, err)
}
if resolution.Gap.OriginalIntentID != requirement.OriginalIntentID || resolution.Gap.SourceUtteranceID != requirement.SourceUtteranceID || len(resolution.Gap.Strategies) == 0 {
t.Fatalf("gap lost correlation/strategies: %#v", resolution.Gap)
}
if strings.Contains(strings.ToLower(resolution.Gap.Detail), "codex") {
t.Fatalf("gap must not become codex-specific: %#v", resolution.Gap)
}
}
func resolutionRequirement(kind capability.CapabilityKind) capability.CapabilityResolutionRequirement {
return capability.CapabilityResolutionRequirement{
CapabilityKind: kind,
OwnerPrincipalID: "owner-1",
ActorID: "actor-1",
OriginalIntentID: "intent-1",
SourceUtteranceID: "utterance-1",
RequiredConstraints: []capability.DataConstraint{capability.DataConstraintLocalOnly},
AuthorityContextRef: capability.AuthorityPolicyCreatorDirectiveV1,
ObservedAtUnixMillis: 1000,
}
}
func implementation(id string, kind capability.CapabilityKind, reliability int64, latency int64) capability.CapabilityImplementation {
return capability.CapabilityImplementation{
ID: capability.CapabilityImplementationID(id),
CapabilityID: capability.CapabilityID(strings.ToLower(string(kind))),
CapabilityKind: kind,
OwnerPrincipalID: "owner-1",
Revision: 1,
Kind: capability.ImplementationInternalFunction,
TargetRef: capability.ImplementationTargetRef("internal://" + strings.ToLower(string(kind))),
Qualification: capability.QualificationValidated,
Activation: capability.ActivationActive,
Health: capability.HealthAvailable,
AuthorityPolicyRef: capability.AuthorityPolicyCreatorDirectiveV1,
DataConstraints: []capability.DataConstraint{capability.DataConstraintLocalOnly, capability.DataConstraintNoCredentialForward},
ReliabilityMillis: &reliability,
LatencyScoreMillis: &latency,
ObservedAtUnixMillis: 1000,
}
}
+22
View File
@@ -12,6 +12,7 @@ var (
ErrProspectiveRevision = errors.New("prospective item revision conflict")
ErrInvalidTransition = errors.New("invalid prospective item transition")
ErrInvalidSatisfaction = errors.New("invalid delivered satisfaction command")
ErrInvalidReminder = errors.New("invalid reminder request")
)
type Store struct {
@@ -28,6 +29,27 @@ func ValidateSatisfyDelivered(command contract.SatisfyDeliveredCommand) error {
return nil
}
func BuildReminder(request contract.ScheduleReminderRequest, id contract.ProspectiveItemID) (contract.ProspectiveItem, error) {
if id == "" || request.OwnerPrincipalID == "" || request.ScopeID == "" || strings.TrimSpace(request.SourceIntentID) == "" ||
strings.TrimSpace(request.SourceUtteranceID) == "" || strings.TrimSpace(request.Summary) == "" ||
request.TriggerAtUnixMillis <= request.RequestedAtUnixMillis || request.RequestedAtUnixMillis < 1 {
return contract.ProspectiveItem{}, ErrInvalidReminder
}
return contract.ProspectiveItem{
ID: id,
CommitmentID: contract.CommitmentID(""),
OwnerPrincipalID: request.OwnerPrincipalID,
ScopeID: request.ScopeID,
GoalID: "",
SourceIntentID: strings.TrimSpace(request.SourceIntentID),
SourceUtteranceID: strings.TrimSpace(request.SourceUtteranceID),
Summary: strings.TrimSpace(request.Summary),
TriggerAtUnixMillis: request.TriggerAtUnixMillis,
State: contract.ProspectiveArmed,
Revision: 1,
}, nil
}
func (s *Store) SatisfyDelivered(id contract.ProspectiveItemID, revision contract.ProspectiveRevision, evidenceRef string) (contract.ProspectiveItem, error) {
item, err := s.current(id, revision)
if err != nil {
+1 -1
View File
@@ -105,7 +105,7 @@ func ClassifySpeechAct(message string, corrects interaction.HumanUtteranceID) in
return interaction.SpeechActQuestion
case strings.Contains(lower, "проверь") || strings.Contains(lower, "исслед") || strings.Contains(lower, "разбер"):
return interaction.SpeechActRequestToInvestigate
case strings.Contains(lower, "сделай") || strings.Contains(lower, "запусти") || strings.Contains(lower, "создай") || strings.Contains(lower, "настрой") || strings.Contains(lower, "добавь") || strings.Contains(lower, "измени") || strings.Contains(lower, "поменяй") || strings.Contains(lower, "используй") || strings.Contains(lower, "удали") || strings.Contains(lower, "удалить"):
case strings.Contains(lower, "сделай") || strings.Contains(lower, "запусти") || strings.Contains(lower, "создай") || strings.Contains(lower, "настрой") || strings.Contains(lower, "добавь") || strings.Contains(lower, "измени") || strings.Contains(lower, "поменяй") || strings.Contains(lower, "используй") || strings.Contains(lower, "удали") || strings.Contains(lower, "удалить") || strings.Contains(lower, "напомни") || strings.Contains(lower, "подключи"):
return interaction.SpeechActDirective
case strings.HasPrefix(lower, "я решил") || strings.Contains(lower, "решение"):
return interaction.SpeechActDecision
+13
View File
@@ -36,6 +36,19 @@ func TestNewHumanUtteranceAppendsCorrectionRelation(t *testing.T) {
}
}
func TestNewHumanUtteranceClassifiesReminderAndCapabilityConnectionAsDirective(t *testing.T) {
conversation := interaction.Conversation{ID: "conversation-1", ActorID: "actor-1", PrincipalID: "principal-1", Channel: interaction.ChannelWebChat, State: interaction.ConversationOpen}
for _, message := range []string{"Напомни завтра позвонить врачу.", "Подключи интеграцию с неизвестным сервисом."} {
utterance, _, _, err := NewHumanUtterance(conversation, interaction.ChatMessageRequest{Message: message}, time.Unix(10, 0))
if err != nil {
t.Fatal(err)
}
if utterance.SpeechAct != interaction.SpeechActDirective {
t.Fatalf("expected directive for %q, got %s", message, utterance.SpeechAct)
}
}
}
func TestNewConversationRequiresActorAndPrincipal(t *testing.T) {
_, err := NewConversation(identity.ActorID(""), identity.PrincipalID("principal-1"), time.Unix(10, 0))
if err == nil {
+2
View File
@@ -78,6 +78,8 @@ func DetermineHelpPath(input work.PlanningInput) work.HelpPath {
return work.HelpPath{Kind: work.HelpPathCognition, Reason: "simple help can be answered without durable Goal/Work/Plan"}
case containsAny(lower, "напомни", "напомин"):
return work.HelpPath{Kind: work.HelpPathDirectCapability, SuggestedCapabilityKinds: []capability.CapabilityKind{capability.CapabilityKindScheduleReminder}, Reason: "reminder is a direct scheduling-capability path, not a coding/project workflow", Reply: "Понял: нужен reminder/scheduler capability. В DEV-000063 я фиксирую это как безопасный путь помощи; само напоминание ещё не зарегистрировано, потому что scheduling adapter не подключён."}
case containsAny(lower, "отправь", "письм", "почт"):
return work.HelpPath{Kind: work.HelpPathCapabilityGap, SuggestedCapabilityKinds: []capability.CapabilityKind{capability.CapabilityKindSendEmail}, CapabilityGap: true, Reason: "email sending needs a communication capability implementation and explicit authority", Reply: "Сейчас у меня ещё не подключена возможность отправлять почту. Я зафиксирую этот capability gap, чтобы позже к нему можно было вернуться."}
case containsAny(lower, "не умеешь", "подключи интеграцию", "интеграц", "неизвестн", "нет capability", "создай adapter", "создай адаптер"):
return work.HelpPath{Kind: work.HelpPathCapabilityGap, SuggestedCapabilityKinds: []capability.CapabilityKind{}, CapabilityGap: true, Reason: "requested help needs a currently missing capability and may later trigger acquisition/self-extension", Reply: "Похоже, для этой просьбы нужна capability, которой сейчас нет. Это не задача конкретного executor автоматически: в будущем Домовой сможет найти, подключить или создать подходящую capability и вернуться к исходной потребности."}
case requiresDurableWork(lower):