diff --git a/cmd/creator-web/server.go b/cmd/creator-web/server.go index d419abf..b3bebfb 100644 --- a/cmd/creator-web/server.go +++ b/cmd/creator-web/server.go @@ -378,7 +378,7 @@ func (g *gateway) handleChatMessage(w http.ResponseWriter, r *http.Request) { cognition = "unavailable" requestID, idErr := ids.New() if idErr == nil { - trusted := cognitive.GatewayAdvisoryRequest{Version: cognitive.CreatorAdvisoryV1, RequestID: cognitive.RequestID(requestID), ActorID: session.ActorID, PrincipalID: session.PrincipalID, ScopeID: "domovoy-global-scope", Message: browserRequest.Message, Context: []cognitive.ContextExcerpt{}, Locale: "ru-RU", MaxOutputBytes: 4096, DataPolicy: capability.DataPolicyLocalOnly, EgressAcknowledged: false, Meaning: cognitive.TaskMeaning{SpeechAct: string(saved.SpeechAct), CapabilityKind: saved.CapabilityKind, CapabilityHints: saved.CapabilityHints, RequestedOutcome: saved.RequestedOutcome, InformationRequestKind: saved.InformationRequestKind, ConfidenceMillis: saved.InterpretationConfidenceMillis}} + trusted := cognitive.GatewayAdvisoryRequest{Version: cognitive.CreatorAdvisoryV1, RequestID: cognitive.RequestID(requestID), ActorID: session.ActorID, PrincipalID: session.PrincipalID, ScopeID: "domovoy-global-scope", Message: browserRequest.Message, Context: localChatContext(saved), Locale: "ru-RU", MaxOutputBytes: 4096, DataPolicy: capability.DataPolicyLocalOnly, EgressAcknowledged: false, Meaning: cognitive.TaskMeaning{SpeechAct: string(saved.SpeechAct), CapabilityKind: saved.CapabilityKind, CapabilityHints: saved.CapabilityHints, RequestedOutcome: saved.RequestedOutcome, InformationRequestKind: saved.InformationRequestKind, ConfidenceMillis: saved.InterpretationConfidenceMillis}} payload, err = json.Marshal(trusted) if err == nil { var advisory cognitive.GatewayAdvisoryResponse @@ -415,6 +415,30 @@ func (g *gateway) handleChatMessage(w http.ResponseWriter, r *http.Request) { }) } +func localChatContext(saved interaction.ChatMessageResponse) []cognitive.ContextExcerpt { + if strings.TrimSpace(saved.ActiveProjectName) == "" { + return []cognitive.ContextExcerpt{} + } + bundle, err := cognitive.CompileContext(cognitive.ContextCompileInput{ + DataPolicy: capability.DataPolicyLocalOnly, + MaxBytes: 2048, + Candidates: []cognitive.ContextCandidate{{ + Excerpt: cognitive.ContextExcerpt{ + ID: cognitive.ContextExcerptID("active-project-focus"), + SourceKind: "situation-active-project", + SourceID: strings.TrimSpace(saved.ActiveProjectName), + Sensitivity: cognitive.SensitivityPrivateAllowed, + Text: "Текущий проект: " + strings.TrimSpace(saved.ActiveProjectName), + }, + Relevance: 100, + }}, + }) + if err != nil { + return []cognitive.ContextExcerpt{} + } + return bundle.Excerpts +} + func asksAttention(message string) bool { lower := strings.ToLower(message) return strings.Contains(lower, "требует моего внимания") || strings.Contains(lower, "требует внимания") || strings.Contains(lower, "что сейчас важно") diff --git a/cmd/creator-web/server_test.go b/cmd/creator-web/server_test.go index a5641d1..888bcbc 100644 --- a/cmd/creator-web/server_test.go +++ b/cmd/creator-web/server_test.go @@ -10,8 +10,19 @@ import ( "testing" "git.cin.su/m/Assistant/contracts/cognitive" + "git.cin.su/m/Assistant/contracts/interaction" ) +func TestLocalChatContextUsesOnlyActiveProject(t *testing.T) { + context := localChatContext(interaction.ChatMessageResponse{ActiveProjectName: "Домовой"}) + if len(context) != 1 || context[0].SourceKind != "situation-active-project" || context[0].Text != "Текущий проект: Домовой" { + t.Fatalf("unexpected local context: %#v", context) + } + if empty := localChatContext(interaction.ChatMessageResponse{}); len(empty) != 0 { + t.Fatalf("expected empty context without active project: %#v", empty) + } +} + const legacyExternalMode = "legacy-external-test-mode" func newHandlerWithAdvisoryMode(coreURL, continuityURL, commitURL string, client *http.Client, mode string, cognitiveURLs ...string) (http.Handler, error) { diff --git a/dev/ACTIVE_TASK.json b/dev/ACTIVE_TASK.json index f4e9f0d..9a7b87a 100644 --- a/dev/ACTIVE_TASK.json +++ b/dev/ACTIVE_TASK.json @@ -1,5 +1,5 @@ { - "task_file": "dev/tasks/DEV-000107-context-compiler-v1.json", - "task_id": "DEV-000107", + "task_file": "dev/tasks/DEV-000108-context-compiler-gateway-integration.json", + "task_id": "DEV-000108", "task_revision": 1 } diff --git a/dev/tasks/DEV-000108-context-compiler-gateway-integration.json b/dev/tasks/DEV-000108-context-compiler-gateway-integration.json new file mode 100644 index 0000000..38b36d2 --- /dev/null +++ b/dev/tasks/DEV-000108-context-compiler-gateway-integration.json @@ -0,0 +1,23 @@ +{ + "task_id": "DEV-000108", + "task_revision": 1, + "architecture_revision": "ADR-0018", + "base_repo_revision": "879de3b", + "risk_tier": "E", + "intent": "Use the typed context compiler for local chat cognition with the canonical active-project focus, keeping external advisory context empty unless explicitly supplied by a future policy-aware path.", + "requirement_ids": ["WP-101", "Follow-up context", "Active project isolation"], + "allowed_change_scope": {"modules": ["creator-gateway", "cognitive-contracts"], "paths": ["cmd/creator-web/**", "contracts/cognitive/**", "tests/context_compiler/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000108-context-compiler-gateway-integration.json"]}, + "forbidden_change_scope": {"paths": ["architecture/**", "database/**", "migrations/**", "deploy/**", "security/**", "modules/**", "runtime/**", "adapters/**", ".gitea/**"]}, + "allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0}, + "contracts": {"consume": ["ContextBundle compiler from DEV-000107", "Core ChatMessageResponse active project fields", "Existing local cognitive gateway request"], "may_change": ["Creator gateway context assembly"]}, + "semantic_owner": "creator-gateway-context-projection", + "canonical_state": [], + "state_transitions": ["canonical active project -> local context candidate -> compiled gateway context", "missing active project -> empty context"], + "side_effects_and_privileges": ["Context assembly is read-only; no persistence, provider selection, credential access or external mutation"], + "persistence_and_migrations": ["No database schema or migration changes"], + "failure_retry_restart": ["Compiler failure or absent focus yields empty context and preserves the existing chat fallback"], + "assumptions": ["External advisory remains message-only until a separate policy-aware context path exists", "Active project name is already canonicalized by Core", "Current project context is not reused for unrelated requests"], + "acceptance_scenarios": ["Local cognitive request receives the active project as provenance-bearing context", "No active project yields empty context", "External advisory receives no implicit private context", "Project context does not leak into a different saved response", "Existing creator gateway and full tests remain green"], + "required_tests": ["go test ./...", "go vet ./...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "go run ./cmd/devctl/independent", "git diff --check", "context compiler gateway integration tests"], + "terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"] +}