DEV-000048 r2: honor advisory message size contract #21

Merged
m merged 2 commits from codex/creator-advisory-console-v1 into main 2026-08-16 17:23:50 +00:00
4 changed files with 17 additions and 9 deletions
+2 -1
View File
@@ -19,6 +19,7 @@ import (
const (
maxRequestBytes = 8 << 10
maxAdvisoryRequestBytes = 20 << 10
maxResponseBytes = 1 << 20
)
@@ -113,7 +114,7 @@ type browserAdvisoryRequest struct {
func (g *gateway) handleAdvisory(w http.ResponseWriter, r *http.Request) {
var browserRequest browserAdvisoryRequest
if err := decodeStrictJSON(r.Body, maxRequestBytes, &browserRequest); err != nil || browserRequest.Message == "" || len(browserRequest.Message) > 16*1024 || !validID(browserRequest.SessionID) || !validID(browserRequest.WorkspaceID) {
if err := decodeStrictJSON(r.Body, maxAdvisoryRequestBytes, &browserRequest); err != nil || browserRequest.Message == "" || len(browserRequest.Message) > 16*1024 || !validID(browserRequest.SessionID) || !validID(browserRequest.WorkspaceID) {
http.Error(w, "invalid advisory request", http.StatusBadRequest)
return
}
+10 -3
View File
@@ -8,6 +8,8 @@ import (
"net/http/httptest"
"strings"
"testing"
"git.cin.su/m/Assistant/contracts/cognitive"
)
func TestGatewayForwardsOnlyCreatorAllowlist(t *testing.T) {
@@ -251,6 +253,7 @@ func TestAdvisoryRequiresSessionBeforeUpstreamCalls(t *testing.T) {
}
func TestAdvisoryAssemblesTrustedIdentityAndScope(t *testing.T) {
longMessage := strings.Repeat("a", 16*1024)
core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "Bearer bounded-session" {
t.Fatal("missing bounded session")
@@ -272,8 +275,12 @@ func TestAdvisoryAssemblesTrustedIdentityAndScope(t *testing.T) {
cognitive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
text := string(body)
if !strings.Contains(text, `"ActorID":"creator-actor"`) || !strings.Contains(text, `"PrincipalID":"creator-principal"`) || !strings.Contains(text, `"ScopeID":"trusted-scope"`) || !strings.Contains(text, `"Message":"help"`) || strings.Contains(text, "attacker") {
t.Fatalf("untrusted cognitive request: %s", text)
var trusted cognitive.AdvisoryRequest
if err := json.Unmarshal(body, &trusted); err != nil {
t.Fatal(err)
}
if trusted.ActorID != "creator-actor" || trusted.PrincipalID != "creator-principal" || trusted.ScopeID != "trusted-scope" || trusted.Message != longMessage || strings.Contains(text, "attacker") {
t.Fatal("cognitive request was not assembled from trusted context")
}
var request map[string]any
_ = json.Unmarshal(body, &request)
@@ -284,7 +291,7 @@ func TestAdvisoryAssemblesTrustedIdentityAndScope(t *testing.T) {
if err != nil {
t.Fatal(err)
}
request := httptest.NewRequest(http.MethodPost, "/api/advisory", strings.NewReader(`{"message":"help","session_id":"focus-session","workspace_id":"home"}`))
request := httptest.NewRequest(http.MethodPost, "/api/advisory", strings.NewReader(fmt.Sprintf(`{"message":%q,"session_id":"focus-session","workspace_id":"home"}`, longMessage)))
request.Header.Set("Authorization", "Bearer bounded-session")
request.Header.Set("Content-Type", "application/json")
response := httptest.NewRecorder()
+1 -1
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000048-creator-advisory-console.json",
"task_id": "DEV-000048",
"task_revision": 1
"task_revision": 2
}
@@ -1,6 +1,6 @@
{
"task_id": "DEV-000048",
"task_revision": 1,
"task_revision": 2,
"architecture_revision": "ADR-0006",
"base_repo_revision": "861438a4cae844edfe8a17615fb700d17c950d8e",
"risk_tier": "E",
@@ -17,7 +17,7 @@
"persistence_and_migrations": [],
"failure_retry_restart": ["advisory text is not persisted", "session/focus failure prevents cognitive call", "provider unavailable is displayed explicitly", "no automatic retry"],
"assumptions": ["browser supplies only message and existing focus locators", "deterministic fake remains the only binding", "docker-test remains prohibited"],
"acceptance_scenarios": ["unauthenticated advisory denied before cognitive call", "browser cannot select Actor/Principal/Scope/binding/provider/context", "trusted request uses current session and validated focus", "UI labels advisory as non-canonical and unavailable", "message is not stored in local/session storage", "desktop/mobile layout and console are clean"],
"acceptance_scenarios": ["unauthenticated advisory denied before cognitive call", "browser cannot select Actor/Principal/Scope/binding/provider/context", "trusted request uses current session and validated focus", "the gateway accepts the full 16 KiB advisory message contract plus its JSON envelope", "UI labels advisory as non-canonical and unavailable", "message is not stored in local/session storage", "desktop/mobile layout and console are clean"],
"required_tests": ["unit", "gateway adversarial", "static UI", "go test", "go vet", "architecture preflight", "exact diff scope", "browser desktop/mobile", "CI"],
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
}