Make live chat the public starting surface #76
@@ -32,6 +32,29 @@ func TestChatPageServedWithoutInternalIDs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicSurfacesSeparateChatAndConsole(t *testing.T) {
|
||||
handler, err := newHandler("http://core", "http://continuity", "http://commit", http.DefaultClient, "http://cognitive")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, test := range []struct {
|
||||
path string
|
||||
must string
|
||||
mustNot string
|
||||
}{
|
||||
{path: "/", must: "Напишите Домовому обычное сообщение", mustNot: "context-preset"},
|
||||
{path: "/admin", must: "context-preset", mustNot: "Напишите Домовому обычное сообщение"},
|
||||
{path: "/chat", must: "Напишите Домовому обычное сообщение", mustNot: "context-preset"},
|
||||
} {
|
||||
recorder := httptest.NewRecorder()
|
||||
handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, test.path, nil))
|
||||
body := recorder.Body.String()
|
||||
if recorder.Code != http.StatusOK || !strings.Contains(body, test.must) || strings.Contains(body, test.mustNot) {
|
||||
t.Fatalf("%s response=%d contains expected=%t forbidden=%t", test.path, recorder.Code, strings.Contains(body, test.must), strings.Contains(body, test.mustNot))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatMessageUsesCoreAndLocalOnlyCognitiveGateway(t *testing.T) {
|
||||
var cognitiveRequest cognitive.GatewayAdvisoryRequest
|
||||
core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -112,7 +112,12 @@ func (g *gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/" {
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
serveEmbeddedHTML(w, "chat.html")
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/admin" {
|
||||
serveEmbeddedHTML(w, "index.html")
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/chat" {
|
||||
r = r.Clone(r.Context())
|
||||
@@ -122,6 +127,17 @@ func (g *gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
g.static.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func serveEmbeddedHTML(w http.ResponseWriter, name string) {
|
||||
body, err := staticFiles.ReadFile("static/" + name)
|
||||
if err != nil {
|
||||
http.Error(w, "page unavailable", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, _ = w.Write(body)
|
||||
}
|
||||
|
||||
type browserAdvisoryRequest struct {
|
||||
Message string `json:"message"`
|
||||
SessionID string `json:"session_id"`
|
||||
|
||||
@@ -229,7 +229,7 @@ func TestConsoleAndSecurityHeaders(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
request := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
request := httptest.NewRequest(http.MethodGet, "/admin", nil)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
if response.Code != http.StatusOK || !strings.Contains(response.Body.String(), "Домовой") ||
|
||||
|
||||
@@ -25,12 +25,12 @@
|
||||
|
||||
<section id="chat-view" class="chat-view" hidden>
|
||||
<header class="chat-header">
|
||||
<a href="/chat" aria-label="Домовой">Домовой</a>
|
||||
<a href="/" aria-label="Домовой">Домовой</a>
|
||||
<nav class="human-nav" aria-label="Разделы Домового">
|
||||
<button class="nav-tab active" data-tab="chat-panel" type="button">Чат</button>
|
||||
<button class="nav-tab" data-tab="today-panel" type="button">Сегодня</button>
|
||||
<button class="nav-tab" data-tab="history-panel" type="button">История</button>
|
||||
<a href="/" class="operator-link">Оператор</a>
|
||||
<a href="/admin" class="operator-link">Консоль</a>
|
||||
</nav>
|
||||
<span id="active-focus">фокус: не выбран</span>
|
||||
<span id="session-state">сессия активна</span>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"task_file": "dev/tasks/DEV-000074-creator-cabinet-operations.json",
|
||||
"task_id": "DEV-000074",
|
||||
"task_revision": 6
|
||||
"task_file": "dev/tasks/DEV-000075-creator-chat-primary-surface.json",
|
||||
"task_id": "DEV-000075",
|
||||
"task_revision": 1
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"task_id": "DEV-000075",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "DEV-000073-baseline",
|
||||
"base_repo_revision": "39a27102c9665e41f29e85bc8f82a316bbbe3962",
|
||||
"risk_tier": "E",
|
||||
"intent": "Make live conversation with Domovoy the public starting surface and keep the Creator operator console on a separate admin route.",
|
||||
"requirement_ids": [
|
||||
"Public root opens the human chat",
|
||||
"Operator console remains available at a separate admin route",
|
||||
"Existing /chat route remains compatible",
|
||||
"Chat does not expose provider, model, binding, session or workspace internals"
|
||||
],
|
||||
"allowed_change_scope": {
|
||||
"modules": ["cmd/creator-web"],
|
||||
"paths": [
|
||||
"cmd/creator-web/server.go",
|
||||
"cmd/creator-web/server_test.go",
|
||||
"cmd/creator-web/chat_test.go",
|
||||
"cmd/creator-web/static/chat.html",
|
||||
"dev/ACTIVE_TASK.json",
|
||||
"dev/tasks/DEV-000075-creator-chat-primary-surface.json"
|
||||
]
|
||||
},
|
||||
"forbidden_change_scope": {
|
||||
"paths": ["architecture/**", "security/**", "database/**", "modules/**", "contracts/**", "adapters/**", "deploy/**"]
|
||||
},
|
||||
"allowed_architecture_delta": {
|
||||
"new_cross_module_dependencies": 0,
|
||||
"new_canonical_owners": 0,
|
||||
"new_privileges": 0,
|
||||
"new_third_party_dependencies": 0,
|
||||
"new_provider_adapters": 0,
|
||||
"new_database_owner_roles": 0
|
||||
},
|
||||
"contracts": {"consume": ["existing /chat and authenticated Creator endpoints"], "may_change": []},
|
||||
"semantic_owner": "none-interface-adapter",
|
||||
"canonical_state": [],
|
||||
"state_transitions": ["GET / -> chat surface", "GET /admin -> operator console surface"],
|
||||
"side_effects_and_privileges": ["No new privilege", "No canonical mutation", "No model/provider routing change"],
|
||||
"persistence_and_migrations": ["No persistence or migration"],
|
||||
"failure_retry_restart": ["Existing chat failure behavior remains unchanged"],
|
||||
"assumptions": ["/chat is the existing human chat surface", "The operator console may be reached at /admin"],
|
||||
"acceptance_scenarios": ["Root serves chat", "Admin serves console", "Chat compatibility remains", "Internal IDs remain absent from chat HTML"],
|
||||
"required_tests": ["go test ./cmd/creator-web", "go test ./...", "go vet ./...", "devctl preflight", "devctl check-diff", "devctl check-architecture", "git diff --check"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
Reference in New Issue
Block a user