feat(events): add FAV subscriber shadow without live writes

ObserveShadow computes intended outcome only and refuses FAV_DEGRADED_READ; HandleLease and Register(fav) stay fail-closed.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-17 04:25:17 +00:00
co-authored by Cursor
parent 49d901e964
commit 2c1d040ccf
5 changed files with 100 additions and 3 deletions
+6 -2
View File
@@ -32,9 +32,13 @@ func main() {
"full recover-plan / recover-apply / gc-preflight / destroy",
"server ingest hooks (OnFinalArtifact*)",
"durable fav event Register after separate approval",
"live staging → artifact → publication (each ≥7d observation)",
},
"source_branch": "feature/final-artifact-vault",
"note": "do not enable FAV_DEGRADED_READ or fav consumer without approval",
"shadow_observe": true,
"would_stage": false,
"would_publish": false,
"source_branch": "feature/final-artifact-vault",
"note": "do not enable FAV_DEGRADED_READ or fav consumer without approval; subscriber shadow is read-only",
})
case "help", "-h", "--help":
usage()
+47
View File
@@ -8,6 +8,8 @@ package fav
import (
"context"
"fmt"
"os"
"strings"
"chat-agent-system/internal/artifactvault"
"chat-agent-system/internal/events"
@@ -24,6 +26,8 @@ type EventConsumer struct {
// Enabled must stay false by default. Even when true, events.Registry
// still rejects Register(ConsumerFAV) until the hard gate is lifted.
Enabled bool
// AllowDegradedRead must stay false. Shadow refuses if this is true.
AllowDegradedRead bool
}
// HandleLease refuses work while the subscriber is disabled (default).
@@ -44,3 +48,46 @@ func (c *EventConsumer) HandleLease(ctx context.Context, lease events.Lease, tx
func DefaultEnabled() bool {
return false
}
// IntendedOutcome is a read-only shadow calculation. It must never be treated
// as a write receipt; WouldStage/WouldPublish stay false in this slice.
type IntendedOutcome struct {
EventName string
AggregateType string
AggregateID string
LogicalResource string
WouldStage bool
WouldPublish bool
DegradedReadUsed bool
}
// ObserveShadow computes the intended FAV outcome without business side effects.
// FAV_DEGRADED_READ must stay false; live staging/artifact/publication is not opened.
func (c *EventConsumer) ObserveShadow(lease events.Lease) (IntendedOutcome, error) {
out := IntendedOutcome{
EventName: lease.EventName,
AggregateType: lease.AggregateType,
AggregateID: lease.AggregateID,
LogicalResource: strings.TrimSpace(lease.AggregateType) + ":" + strings.TrimSpace(lease.AggregateID),
}
if c == nil {
return out, fmt.Errorf("%w: fav", events.ErrDisabledConsumer)
}
if c.AllowDegradedRead || envTruthy("FAV_DEGRADED_READ") {
return out, fmt.Errorf("fav shadow refuses FAV_DEGRADED_READ")
}
out.WouldStage = false
out.WouldPublish = false
out.DegradedReadUsed = false
_ = c.Publisher // never Drain / Stage / Publish here
return out, nil
}
func envTruthy(key string) bool {
switch strings.ToLower(strings.TrimSpace(os.Getenv(key))) {
case "1", "true", "yes", "on":
return true
default:
return false
}
}
+33
View File
@@ -33,3 +33,36 @@ func TestFavSubscriberStillDisabledWhenFlagOnWithoutRegistryApproval(t *testing.
t.Fatal("Registry.Register(fav) must remain fail-closed")
}
}
func TestFavSubscriberShadowComputesIntendedOutcomeWithoutWrites(t *testing.T) {
t.Setenv("FAV_DEGRADED_READ", "false")
t.Setenv("FAV_PUBLISHER_ENABLED", "false")
c := &fav.EventConsumer{Enabled: false, Env: "dev"}
out, err := c.ObserveShadow(events.Lease{
EventName: "document.finalized", AggregateType: "document", AggregateID: "doc_1",
})
if err != nil {
t.Fatal(err)
}
if out.WouldStage || out.WouldPublish || out.DegradedReadUsed {
t.Fatalf("shadow must not claim writes: %+v", out)
}
if out.LogicalResource != "document:doc_1" {
t.Fatalf("logical=%s", out.LogicalResource)
}
err = c.HandleLease(context.Background(), events.Lease{}, nil)
if !errors.Is(err, events.ErrDisabledConsumer) {
t.Fatalf("HandleLease still disabled: %v", err)
}
c.AllowDegradedRead = true
if _, err := c.ObserveShadow(events.Lease{}); err == nil {
t.Fatal("shadow must refuse degraded read")
}
c.AllowDegradedRead = false
t.Setenv("FAV_DEGRADED_READ", "true")
if _, err := c.ObserveShadow(events.Lease{AggregateType: "document", AggregateID: "doc_2"}); err == nil {
t.Fatal("shadow must refuse process env FAV_DEGRADED_READ")
}
}
+5 -1
View File
@@ -95,7 +95,11 @@ func (s *Server) handleVaultStatus(w http.ResponseWriter, r *http.Request) {
"degraded_read": degraded,
"store_configured": storeReady,
"subscriber_default": false,
"note": "fav consumer remains OFF until separate approval; full FAV branch not merged",
"shadow_observe": true,
"would_stage": false,
"would_publish": false,
"live_not_opened": true,
"note": "fav consumer remains OFF until separate approval; shadow computes intended outcome only; full FAV branch not merged",
})
}
+9
View File
@@ -57,6 +57,15 @@ func TestVaultStatusReportsOff(t *testing.T) {
if body["degraded_read"] != false {
t.Fatalf("degraded_read=%v", body["degraded_read"])
}
if body["shadow_observe"] != true {
t.Fatalf("shadow_observe=%v", body["shadow_observe"])
}
if body["would_stage"] != false || body["would_publish"] != false {
t.Fatalf("shadow must not claim writes: %+v", body)
}
if body["live_not_opened"] != true {
t.Fatalf("live_not_opened=%v", body["live_not_opened"])
}
}
func TestValidateIndependentVaultRejectsDataRoot(t *testing.T) {