From 6781c75f845c87e58ce06bb4ba750676dca5811d Mon Sep 17 00:00:00 2001 From: zhaoyilun Date: Fri, 10 Jul 2026 21:01:04 +0800 Subject: [PATCH] feat: close send message production gate behavior --- docs/current-status-card.md | 14 ++++-- docs/go-mcp-runbook.md | 2 + ...07-10-r6f-r14-continuous-execution-plan.md | 15 ++++-- internal/tools/isphere_send_message_test.go | 50 +++++++++++++++++++ 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/docs/current-status-card.md b/docs/current-status-card.md index 2fd8478..d7b4cff 100644 --- a/docs/current-status-card.md +++ b/docs/current-status-card.md @@ -58,7 +58,7 @@ N13/N14/N15 are pre-business validation results. They can help identify UI eleme ## Current loop -Current loop: `R6k returned sandbox evidence intake v2 complete; next R6l send-message production path gate closure`. +Current loop: `R6l send-message production path gate closure complete; next R7 send-file preview tool`. Active continuous execution plan: `docs/superpowers/plans/2026-07-10-r6f-r14-continuous-execution-plan.md`. @@ -239,8 +239,8 @@ Continuous execution plan R6f-R14 is approved and execution has started: - Plan file: `docs/superpowers/plans/2026-07-10-r6f-r14-continuous-execution-plan.md`. - Scope: 15 ordered rounds covering send-message connector/gate hardening, send-file preview/idempotency/package, receive-file download preview, receive-message reconciliation, end-to-end business smoke, and release-candidate report. - Execution rule after approval: one round at a time, status-card update, verification, commit, push, then continue automatically until an evidence-only blocker requires user action. -- Last completed round: R6k returned sandbox evidence intake v2. -- Next active round: R6l send-message production path gate closure. +- Last completed round: R6l send-message production path gate closure. +- Next active round: R7 send-file preview tool. R6g send audit and idempotency replay diagnostics is complete: @@ -287,3 +287,11 @@ R6k returned sandbox evidence intake v2 is complete: - Decision: `r6d_gate_pass=false`, `strict_v2_pass=false`, production `isphere_send_message` remains blocked. - Standard verification still reports 9 tools and `production_send_enabled=false`. - Next node: R6l send-message production path gate closure. + +R6l send-message production path gate closure is complete: + +- Because R6k found `strict_v2_pass=false`, production `isphere_send_message` remains blocked. +- Added regression coverage for `ISPHERE_SEND_PRODUCTION_ENABLED=1` with missing strict evidence; response returns `gate_reason_code="send_evidence_missing"`. +- Added an explicit skipped gated-production test with reason `strict v2 send evidence not present in this repository state`. +- Standard verification still reports 9 tools and `production_send_enabled=false`. +- Next node: R7 send-file preview tool. diff --git a/docs/go-mcp-runbook.md b/docs/go-mcp-runbook.md index 446b9ab..5c08d8f 100644 --- a/docs/go-mcp-runbook.md +++ b/docs/go-mcp-runbook.md @@ -294,6 +294,8 @@ R6h centralizes the production-send gate. Default `execution_mode="production"` `ISPHERE_SEND_PRODUCTION_ENABLED=1` is recognized by the gate policy, but it is not sufficient by itself: the evidence gate and connector gate must also pass before production can be allowed. In the default MCP server path, `production_send_enabled` remains `false`. +R6l closes the current send-message production path after R6k: because `strict_v2_pass=false`, production send remains blocked even if `ISPHERE_SEND_PRODUCTION_ENABLED=1` is set. In that state, the MCP response uses `gate_reason_code="send_evidence_missing"` and still reports all real side-effect flags as false. + ## 10. Current tool surface and next-stage route The current runbook verifies nine tools: diff --git a/docs/superpowers/plans/2026-07-10-r6f-r14-continuous-execution-plan.md b/docs/superpowers/plans/2026-07-10-r6f-r14-continuous-execution-plan.md index 8948dad..dce22b2 100644 --- a/docs/superpowers/plans/2026-07-10-r6f-r14-continuous-execution-plan.md +++ b/docs/superpowers/plans/2026-07-10-r6f-r14-continuous-execution-plan.md @@ -479,19 +479,19 @@ git push gitea main - If `r6d_gate_pass=false`: add regression tests proving production remains blocked with `gate_reason_code="send_evidence_missing"`. - If `r6d_gate_pass=true`: add tests that production can only run through an injected connector with `ISPHERE_SEND_PRODUCTION_ENABLED=1`, evidence gate pass, and idempotency key present. -- [ ] **Step 1: Write default-blocked regression test** +- [x] **Step 1: Write default-blocked regression test** Add `TestISphereSendMessageProductionGateBlockedWithoutStrictEvidence`, asserting `ok=false`, `send_status="blocked"`, `gate_reason_code` is not empty, `production_send_enabled=false`, and `real_send_attempted=false`. -- [ ] **Step 2: Write gated fake-production test if strict v2 passed** +- [x] **Step 2: Write gated fake-production test if strict v2 passed** Add `TestISphereSendMessageProductionRequiresEvidenceAndConnector`. If strict v2 did not pass, keep this test skipped with explicit skip reason: `strict v2 send evidence not present in this repository state`. -- [ ] **Step 3: Update verification smoke** +- [x] **Step 3: Update verification smoke** `verify-go-mcp.ps1` must continue printing `production_send_enabled=false` on the default path unless strict evidence and env gate are configured. -- [ ] **Step 4: Verify and commit** +- [x] **Step 4: Verify and commit** ```powershell git diff --check @@ -506,6 +506,13 @@ git push gitea main **Acceptance gate:** No ambiguous state remains: send-message is either gated by validated evidence or explicitly blocked with a machine-readable reason. +**R6l Result:** + +- R6k found `strict_v2_pass=false`, so R6l used the blocked branch. +- Added `TestISphereSendMessageProductionGateBlockedWithoutStrictEvidence`, proving production remains blocked with `gate_reason_code="send_evidence_missing"` when `ISPHERE_SEND_PRODUCTION_ENABLED=1` but strict evidence is missing. +- Added `TestISphereSendMessageProductionRequiresEvidenceAndConnector` as an explicit skipped test with reason `strict v2 send evidence not present in this repository state`. +- Standard MCP smoke continues to report `production_send_enabled=false`. + --- ### R7: `isphere_send_file` preview tool diff --git a/internal/tools/isphere_send_message_test.go b/internal/tools/isphere_send_message_test.go index 2695976..be3cc71 100644 --- a/internal/tools/isphere_send_message_test.go +++ b/internal/tools/isphere_send_message_test.go @@ -188,6 +188,56 @@ func TestISphereSendMessageProductionBlockedIncludesGateReason(t *testing.T) { } } +func TestISphereSendMessageProductionGateBlockedWithoutStrictEvidence(t *testing.T) { + t.Setenv(EnvSendMessageProductionEnabled, "1") + audit := &fakeSendMessageAuditSink{} + session, cleanup := connectToolsTestSession(t, func(server *mcp.Server) { + RegisterISphereSendMessageToolWithState(server, audit, newFakeSendMessageIdempotencyStore()) + }) + defer cleanup() + + content := "blocked without strict evidence" + callResult, err := session.CallTool(context.Background(), &mcp.CallToolParams{ + Name: ToolNameSendMessage, + Arguments: map[string]any{ + "target_type": "direct", + "target_id": "alice@imopenfire1-lanzhou", + "content_text": content, + "content_sha256": sha256HexForSendMessageTest(content), + "idempotency_key": "idem-no-strict-evidence-1", + "execution_mode": "production", + }, + }) + if err != nil { + t.Fatalf("call production without strict evidence: %v", err) + } + + var decoded struct { + OK bool `json:"ok"` + SendStatus string `json:"send_status"` + GateReasonCode string `json:"gate_reason_code"` + ProductionSendEnabled bool `json:"production_send_enabled"` + SideEffects map[string]any `json:"side_effects"` + } + payload, _ := json.Marshal(callResult.StructuredContent) + if err := json.Unmarshal(payload, &decoded); err != nil { + t.Fatalf("decode payload %s: %v", payload, err) + } + if decoded.OK || decoded.SendStatus != "blocked" || decoded.GateReasonCode != "send_evidence_missing" { + t.Fatalf("production should remain blocked on missing strict evidence: %s", payload) + } + if decoded.ProductionSendEnabled { + t.Fatalf("production_send_enabled should remain false: %s", payload) + } + if decoded.SideEffects["sent_message"] != false || decoded.SideEffects["clicked_ui"] != false || decoded.SideEffects["captured_network"] != false { + t.Fatalf("blocked production should have no real side effects: %#v", decoded.SideEffects) + } +} + +func TestISphereSendMessageProductionRequiresEvidenceAndConnector(t *testing.T) { + t.Skip("strict v2 send evidence not present in this repository state") +} + func TestISphereSendMessageFakeConnectorAcceptedAudit(t *testing.T) { audit := &fakeSendMessageAuditSink{} idempotency := newFakeSendMessageIdempotencyStore()