DEV-000285 Route source-dependent questions to research #263

Merged
m merged 1 commits from codex/dev-000248-self-development-runtime-status into main 2026-08-24 03:32:35 +00:00
4 changed files with 61 additions and 2 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000284-live-generic-synthesis-evidence.json",
"task_id": "DEV-000284",
"task_file": "dev/tasks/DEV-000285-semantic-research-routing-fix.json",
"task_id": "DEV-000285",
"task_revision": 1
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000285",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "5c98bc3",
"risk_tier": "E",
"intent": "Route semantically source-dependent current-fact questions to the existing provider-neutral WEB_RESEARCH capability instead of returning an unverified fallback.",
"requirement_ids": ["truth-first source routing", "no entity-specific keyword branches", "local deterministic fallback remains honest"],
"allowed_change_scope": {"modules": ["interaction"], "paths": ["dev/ACTIVE_TASK.json", "dev/tasks/DEV-000285-semantic-research-routing-fix.json", "modules/interaction/domain/chat.go", "modules/interaction/domain/chat_test.go"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "cmd/**", "deploy/**", "LLM-local/**"]},
"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": ["interaction semantic candidate contract", "capability WEB_RESEARCH hint"], "may_change": []},
"semantic_owner": "interaction",
"canonical_state": ["no new canonical state; only semantic interpretation hints change"],
"state_transitions": ["source-dependent question -> WEB_RESEARCH hint -> existing research runtime"],
"side_effects_and_privileges": ["existing read-only research connector may be selected by downstream runtime"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["if research is unavailable, existing truthful unverified response remains"],
"assumptions": ["lexical cues represent semantic classes such as currentness and observed external state, not named entities"],
"acceptance_scenarios": ["weather-like current fact routes to WEB_RESEARCH without city-specific code", "ordinary model mention does not route to research", "non-research factual question remains honest when no source-dependent cue exists"],
"required_tests": ["go test ./modules/interaction/domain", "go vet ./modules/interaction/domain", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_MISSING_CAPABILITY"]
}
+17
View File
@@ -405,6 +405,9 @@ func capabilityHintsFor(primary, secondary interaction.SpeechAct, tokens []strin
if primary == interaction.SpeechActQuestion || primary == interaction.SpeechActRequestToInvestigate || secondary == interaction.SpeechActQuestion || secondary == interaction.SpeechActRequestToInvestigate {
add("COGNITIVE_REASONING")
}
if sourceDependentQuestion(primary, secondary, tokens) {
add("WEB_RESEARCH")
}
if containsToken(tokens, "напомни") || containsToken(tokens, "напомин") {
add("SCHEDULE_REMINDER")
}
@@ -446,6 +449,20 @@ func capabilityHintsFor(primary, secondary interaction.SpeechAct, tokens []strin
return values
}
// sourceDependentQuestion identifies semantic classes whose answer depends on
// a current or externally observed source. It intentionally uses no named
// place, person or one-off question branch; when the cue is absent the
// truth-first fallback remains in force.
func sourceDependentQuestion(primary, secondary interaction.SpeechAct, tokens []string) bool {
inquiry := primary == interaction.SpeechActQuestion || primary == interaction.SpeechActRequestToInvestigate || secondary == interaction.SpeechActQuestion || secondary == interaction.SpeechActRequestToInvestigate
if !inquiry {
return false
}
currentCue := containsStemInTokens(tokens, "сейчас", "сегодня", "текущ", "актуаль", "последн", "прямо")
observedStateCue := containsStemInTokens(tokens, "погод", "температур", "осад", "ветер", "курс", "цен", "стоим", "новост", "распис", "событ", "статус", "доступен", "работает", "запущен", "срок")
return currentCue || observedStateCue
}
func informationRequestKind(message string, candidate interaction.SemanticCandidate) interaction.InformationRequestKind {
tokens := semanticTokens(message)
if len(tokens) == 0 {
+19
View File
@@ -133,6 +133,25 @@ func TestInformationRequestsAreClassifiedOnceByInteraction(t *testing.T) {
}
}
func TestSourceDependentQuestionRequestsWebResearchWithoutEntityBranch(t *testing.T) {
candidate := BuildDeterministicCandidate("какая погода в Киеве?", "", time.Unix(10, 0).UnixMilli())
seen := false
for _, hint := range candidate.CapabilityHints {
if hint == "WEB_RESEARCH" {
seen = true
}
}
if !seen {
t.Fatalf("source-dependent question was not routed to WEB_RESEARCH: %+v", candidate)
}
ordinary := BuildDeterministicCandidate("что такое гравитация?", "", time.Unix(10, 0).UnixMilli())
for _, hint := range ordinary.CapabilityHints {
if hint == "WEB_RESEARCH" {
t.Fatalf("ordinary conceptual question was incorrectly routed to WEB_RESEARCH: %+v", ordinary)
}
}
}
func TestDeterministicCandidateKeepsFirstPersonCompletedWorkAsAssertion(t *testing.T) {
candidate := BuildDeterministicCandidate("Я всё уже настроил.", "", time.Unix(10, 0).UnixMilli())
if candidate.PrimarySpeechAct != interaction.SpeechActAssertion {