feat: close send message production gate behavior
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user