feat: close send message production gate behavior

This commit is contained in:
zhaoyilun
2026-07-10 21:01:04 +08:00
parent 41b4b5b7d2
commit 6781c75f84
4 changed files with 74 additions and 7 deletions

View File

@@ -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.

View File

@@ -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:

View File

@@ -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

View File

@@ -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()