Compare commits

..
Author SHA1 Message Date
m aa01e8e99b DEV-000065 final project status
CI / test (pull_request) Successful in 15s
CI / test (push) Successful in 21s
2026-08-17 16:43:33 +03:00
m 1113ced623 DEV-000065 answer attention from canonical today
CI / test (push) Successful in 15s
Promote DEV-000065 canonical attention chat answer.
2026-08-17 13:41:59 +00:00
m f3ff430812 DEV-000065 answer attention from canonical today
CI / test (pull_request) Successful in 18s
CI / test (push) Successful in 14s
2026-08-17 16:40:59 +03:00
m 2ec53e40cf DEV-000065 fix notification binding origin
CI / test (push) Successful in 19s
Promote DEV-000065 live wiring fix.
2026-08-17 13:38:11 +00:00
3 changed files with 99 additions and 5 deletions
+42
View File
@@ -9,6 +9,7 @@ import (
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/cognitive"
"git.cin.su/m/Assistant/contracts/continuity"
"git.cin.su/m/Assistant/contracts/identity"
"git.cin.su/m/Assistant/contracts/interaction"
)
@@ -127,3 +128,44 @@ func TestChatMessageReturnsHumanCapabilityMetadata(t *testing.T) {
t.Fatalf("unexpected browser response: %#v", response)
}
}
func TestChatAttentionQuestionUsesContinuityToday(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.SpeechActQuestion, FallbackText: "Сейчас в фокусе: УПО."})
default:
t.Fatalf("unexpected core route %s %s", r.Method, r.URL.Path)
}
}))
defer core.Close()
continuityRuntime := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet || r.URL.Path != "/v1/creator/today" {
t.Fatalf("unexpected continuity route %s %s", r.Method, r.URL.Path)
}
_ = json.NewEncoder(w).Encode(continuity.TodayResult{Items: []continuity.TodayItem{{Title: "проверить уведомление", HumanState: "доставлено", DeliveryState: "уведомление отправлено"}}})
}))
defer continuityRuntime.Close()
handler, err := newHandler(core.URL, continuityRuntime.URL, "http://commit", core.Client(), "http://cognitive")
if err != nil {
t.Fatal(err)
}
request := httptest.NewRequest(http.MethodPost, "/api/chat/messages", strings.NewReader(`{"message":"Что сейчас требует моего внимания?"}`))
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 !strings.Contains(response.ReplyText, "проверить уведомление") || strings.Contains(response.ReplyText, "УПО") {
t.Fatalf("attention reply did not use canonical today: %#v", response)
}
}
+49
View File
@@ -13,6 +13,7 @@ import (
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/cognitive"
"git.cin.su/m/Assistant/contracts/continuity"
"git.cin.su/m/Assistant/contracts/identity"
"git.cin.su/m/Assistant/contracts/interaction"
"git.cin.su/m/Assistant/contracts/situation"
@@ -323,6 +324,15 @@ func (g *gateway) handleChatMessage(w http.ResponseWriter, r *http.Request) {
if saved.PlanningReply != "" {
reply = saved.PlanningReply
}
if asksAttention(browserRequest.Message) {
var today continuity.TodayResult
status, err = g.getTrustedJSON(r, g.continuity, "/v1/creator/today", "", authorization, &today)
if err == nil && status == http.StatusOK {
reply = renderTodayReply(today)
cognition = "not_requested"
saved.NeedsCognition = false
}
}
if saved.NeedsCognition && !saved.WorkPlanned && saved.Clarification != interaction.ClarificationRequired {
cognition = "unavailable"
requestID, idErr := ids.New()
@@ -346,6 +356,45 @@ func (g *gateway) handleChatMessage(w http.ResponseWriter, r *http.Request) {
_ = 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 asksAttention(message string) bool {
lower := strings.ToLower(message)
return strings.Contains(lower, "требует моего внимания") || strings.Contains(lower, "требует внимания") || strings.Contains(lower, "что сейчас важно")
}
func renderTodayReply(today continuity.TodayResult) string {
if len(today.Items) == 0 {
return "Сейчас ничего срочного не требует внимания."
}
limit := len(today.Items)
if limit > 5 {
limit = 5
}
var builder strings.Builder
builder.WriteString("Сейчас требует внимания:\n")
for index := 0; index < limit; index++ {
item := today.Items[index]
title := strings.TrimSpace(item.Title)
if title == "" {
title = "событие Домового"
}
state := strings.TrimSpace(item.HumanState)
if state == "" {
state = "ожидает внимания"
}
builder.WriteString("- ")
builder.WriteString(title)
builder.WriteString(" — ")
builder.WriteString(state)
if item.DeliveryState != "" {
builder.WriteString(" (")
builder.WriteString(item.DeliveryState)
builder.WriteString(")")
}
builder.WriteString("\n")
}
return strings.TrimSpace(builder.String())
}
func validAdvisoryResponse(response cognitive.GatewayAdvisoryResponse, request cognitive.GatewayAdvisoryRequest) bool {
if response.Version != cognitive.CreatorAdvisoryV1 || response.RequestID != request.RequestID || len(response.ReferencedExcerptIDs) != 0 {
return false
+8 -5
View File
@@ -4,19 +4,22 @@ Audit date: 2026-08-17. Active implementation: DEV-000065 revision 1.
## DEV-000065 checkpoint
- `DEV_000065_STATUS = IN_PROGRESS`
- `DEV_000065_STATUS = COMPLETED`
- `LAST_COMPLETED = ADR-0014 accepted; Continuity-owned AttentionItem contract and persistence; due reminder scanner; Commit internal prospective notification endpoint; human-facing Chat / Today / History surface; relative reminder parsing for "через N минут"; deterministic due-delivery regression tests; notification binding origin corrected to assistant.cin.su revision 2`
- `ADR_STATUS = ADR-0014 accepted`
- `OWNERSHIP_STATUS = no new owner; AttentionItem belongs to Continuity; notification delivery remains Commit/Capability/receiver path`
- `REMINDER_LIFECYCLE_STATUS = ARMED -> TRIGGERED -> REVALIDATING -> DELIVERED implemented for due scanner`
- `REMINDER_DELIVERY_STATUS = implemented through Commit deliver-prospective-notification-v1 path; production live verification pending`
- `REMINDER_DELIVERY_STATUS = PASS: production live delayed reminder delivered through Commit deliver-prospective-notification-v1 and immutable receiver receipt`
- `ATTENTION_STATUS = typed AttentionItem PENDING -> DELIVERING -> DELIVERED foundation implemented`
- `HUMAN_SURFACE_STATUS = /chat remains primary; Today/Attention and History added; Operator Console preserved at /`
- `AUTHORITY_STATUS = external notification still requires Commit authorization, current Creator authority context, and active Capability notification binding`
- `LLM_FREE_STATUS = deterministic reminder firing does not require cognitive model; LLM-free env policy regression PASS`
- `TESTS_PASS_SO_FAR = 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`
- `PRODUCTION_STATUS = pending deploy/live acceptance`
- `NEXT_STEP = apply migration, deploy affected services, run owner ACL and live delayed reminder acceptance, then promote if PASS`
- `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 delayed reminder acceptance; production /chat attention and capability-gap acceptance`
- `LIVE_ACCEPTANCE = PASS: "Напомни мне через 3 минуты проверить уведомление." persisted ARMED reminder, transitioned to DELIVERED, generated AttentionItem, delivered notification through Commit, persisted SUCCEEDED attempt and receiver receipt, duplicate counts action_attempts=1 attention_items=1; "Отправь Ивану письмо." returned readable capability gap; "Что сейчас требует моего внимания?" answered from canonical Today with cognition=not_requested`
- `PRODUCTION_STATUS = healthy`
- `PRODUCTION_REVISION = origin/main 1113ced623896f1498d606653d71f36c361e9bb0; creator-web deployed at 1113ced623896f1498d606653d71f36c361e9bb0; continuity-runtime deployed at 2ec53e40cf19c609193c1aa74955573cac7abfe1; core-runtime/commit-runtime deployed at a0dce634f3c6efec0481f730c9dcc12797749ec4`
- `KNOWN_LIMITATION = one early DEV-000065 live probe occurred before notification binding origin revision 2 and produced UNKNOWN_OUTCOME; later regression prevents UNKNOWN_OUTCOME from being marked as delivered`
- `NEXT_STEP = Human response operations for attention/reminders: Done/Snooze/Cancel semantic commands, if separately approved`
## DEV-000064 checkpoint