From 3478a5a05a9e86f3f4c31e4b65c3e55dc8015157 Mon Sep 17 00:00:00 2001 From: weichen Date: Wed, 16 Sep 2026 15:40:10 +0000 Subject: [PATCH 1/6] feat(config): add FAV vault flags defaulting off Gate FAV_DEGRADED_READ and FAV_PUBLISHER_ENABLED behind explicit opt-in, and refuse VAULT_STATE_ROOT colliding with runtime data/{env}. Co-authored-by: Cursor --- internal/config/config.go | 57 ++++++++++++++++++++++++++++++++++ internal/config/config_test.go | 40 ++++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/internal/config/config.go b/internal/config/config.go index c6d52240..bbb443e5 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,6 +4,7 @@ import ( "bufio" "fmt" "os" + "path/filepath" "strconv" "strings" "time" @@ -65,6 +66,13 @@ type Config struct { // CORSAllowedOrigins 允许的浏览器 Origin 列表;空列表时不回显 Access-Control-Allow-Origin CORSAllowedOrigins []string `json:"cors_allowed_origins"` + + // Final Artifact Vault (FAV) — defaults stay off until separate approval. + VaultStateRoot string `json:"vault_state_root"` + VaultStorageIdentity string `json:"vault_storage_identity"` + VaultMasterKey string `json:"vault_master_key"` + FAVDegradedRead bool `json:"fav_degraded_read"` // FAV_DEGRADED_READ; default false + FAVPublisherEnabled bool `json:"fav_publisher_enabled"` // FAV_PUBLISHER_ENABLED; default false } func Load(path string) (*Config, error) { @@ -228,6 +236,16 @@ func Load(path string) (*Config, error) { cfg.AllowInMemory = val == "true" || val == "1" case "CORS_ALLOWED_ORIGINS": cfg.CORSAllowedOrigins = parseCSVOrigins(val) + case "VAULT_STATE_ROOT": + cfg.VaultStateRoot = val + case "VAULT_STORAGE_IDENTITY": + cfg.VaultStorageIdentity = val + case "VAULT_MASTER_KEY": + cfg.VaultMasterKey = val + case "FAV_DEGRADED_READ": + cfg.FAVDegradedRead = val == "true" || val == "1" + case "FAV_PUBLISHER_ENABLED": + cfg.FAVPublisherEnabled = val == "true" || val == "1" } } } @@ -365,6 +383,21 @@ func Load(path string) (*Config, error) { if v := os.Getenv("CORS_ALLOWED_ORIGINS"); v != "" { cfg.CORSAllowedOrigins = parseCSVOrigins(v) } + if v := os.Getenv("VAULT_STATE_ROOT"); v != "" { + cfg.VaultStateRoot = v + } + if v := os.Getenv("VAULT_STORAGE_IDENTITY"); v != "" { + cfg.VaultStorageIdentity = v + } + if v := os.Getenv("VAULT_MASTER_KEY"); v != "" { + cfg.VaultMasterKey = v + } + if v := os.Getenv("FAV_DEGRADED_READ"); v != "" { + cfg.FAVDegradedRead = v == "true" || v == "1" + } + if v := os.Getenv("FAV_PUBLISHER_ENABLED"); v != "" { + cfg.FAVPublisherEnabled = v == "true" || v == "1" + } if v := os.Getenv("HTTP_ADDR"); v != "" && strings.HasPrefix(v, ":") { if p, err := strconv.Atoi(strings.TrimPrefix(v, ":")); err == nil { cfg.HTTPPort = p @@ -377,10 +410,34 @@ func Load(path string) (*Config, error) { if err := validateProductionSecrets(cfg); err != nil { return nil, err } + if err := ValidateIndependentVault(cfg.VaultStateRoot, cfg.DataDir); err != nil { + return nil, err + } return cfg, nil } +// ValidateIndependentVault refuses vault roots that collide with runtime data/{env}. +// Empty root is allowed (FAV stays unconfigured / disabled). +func ValidateIndependentVault(root, dataDir string) error { + if strings.TrimSpace(root) == "" { + return nil + } + cr := filepath.Clean(root) + for _, env := range []string{"dev", "test", "main"} { + if strings.HasSuffix(cr, filepath.Join("data", env)) { + return fmt.Errorf("config: VAULT_STATE_ROOT must not be runtime data root %s", root) + } + } + if dataDir != "" { + cd := filepath.Clean(dataDir) + if cr == cd { + return fmt.Errorf("config: VAULT_STATE_ROOT must be independent from DATA_DIR") + } + } + return nil +} + func parseCSVOrigins(raw string) []string { parts := strings.Split(raw, ",") out := make([]string, 0, len(parts)) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index ec789ed0..15298d75 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -144,3 +144,43 @@ func TestConfigLoadRejectsProductionDatabasePlaceholder(t *testing.T) { t.Fatal("expected config load to reject the documented production database placeholder") } } + +func TestFAVFlagsDefaultOff(t *testing.T) { + t.Setenv("FAV_DEGRADED_READ", "") + t.Setenv("FAV_PUBLISHER_ENABLED", "") + cfg, err := config.Load("/dev/null") + if err != nil { + t.Fatal(err) + } + if cfg.FAVDegradedRead { + t.Fatal("FAV_DEGRADED_READ must default false") + } + if cfg.FAVPublisherEnabled { + t.Fatal("FAV_PUBLISHER_ENABLED must default false") + } +} + +func TestFAVFlagsOptInOnly(t *testing.T) { + t.Setenv("FAV_DEGRADED_READ", "true") + t.Setenv("FAV_PUBLISHER_ENABLED", "1") + t.Setenv("VAULT_STATE_ROOT", "/tmp/vault-independent-test") + cfg, err := config.Load("/dev/null") + if err != nil { + t.Fatal(err) + } + if !cfg.FAVDegradedRead || !cfg.FAVPublisherEnabled { + t.Fatalf("expected explicit opt-in: %+v", cfg) + } +} + +func TestValidateIndependentVault(t *testing.T) { + if err := config.ValidateIndependentVault("./data/dev", "./other"); err == nil { + t.Fatal("expected reject data/dev root") + } + if err := config.ValidateIndependentVault("/var/vault", "/var/vault"); err == nil { + t.Fatal("expected reject equal DATA_DIR") + } + if err := config.ValidateIndependentVault("/var/vault", "./data/dev"); err != nil { + t.Fatal(err) + } +} From 92d7a3d117660fcab80fc5101748e845b45854e5 Mon Sep 17 00:00:00 2001 From: weichen Date: Wed, 16 Sep 2026 15:40:10 +0000 Subject: [PATCH 2/6] feat(api): add FAV vault status stub with gated publisher Expose GET /api/v1/vault/status and only wire in-process Drain when FAV_PUBLISHER_ENABLED is explicitly true; degraded read stays off by default. Co-authored-by: Cursor --- internal/server/auth_middleware.go | 3 +- internal/server/project_doc_handlers.go | 2 +- internal/server/server.go | 11 +++ internal/server/vault.go | 104 ++++++++++++++++++++++++ internal/server/vault_test.go | 69 ++++++++++++++++ 5 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 internal/server/vault.go create mode 100644 internal/server/vault_test.go diff --git a/internal/server/auth_middleware.go b/internal/server/auth_middleware.go index 0ba7961b..98c5c0b6 100644 --- a/internal/server/auth_middleware.go +++ b/internal/server/auth_middleware.go @@ -18,7 +18,8 @@ func (s *Server) allowLegacyTestPrincipal() bool { func isPublicRoute(method, path string) bool { if path == "/" || path == "/health" || path == "/api/v1/health" || - path == "/ready" || path == "/api/v1/ready" { + path == "/ready" || path == "/api/v1/ready" || + path == "/api/v1/vault/status" { return true } if method == http.MethodPost && path == "/api/v1/auth/login" { diff --git a/internal/server/project_doc_handlers.go b/internal/server/project_doc_handlers.go index ed1feb12..b0f10ea1 100644 --- a/internal/server/project_doc_handlers.go +++ b/internal/server/project_doc_handlers.go @@ -25,7 +25,7 @@ func (s *Server) docflowQuery() *docflow.Query { Authorize: func(ctx context.Context, p docquery.Principal, rec *docgov.DocumentRecord) error { return s.authorizeControlledRead(ctx, p, rec) }, - FavDegradedRead: false, + FavDegradedRead: s.cfg != nil && s.cfg.FAVDegradedRead, }} } diff --git a/internal/server/server.go b/internal/server/server.go index a459929b..b0d9681c 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -14,6 +14,7 @@ import ( "chat-agent-system/internal/app/chatflow" "chat-agent-system/internal/app/intentflow" "chat-agent-system/internal/app/stageflow" + "chat-agent-system/internal/artifactvault" "chat-agent-system/internal/auth" "chat-agent-system/internal/authz" "chat-agent-system/internal/bizdev" @@ -71,6 +72,12 @@ type Server struct { intentPlans intentflow.Service planAccessFn func(ctx context.Context, projectID, userID int64) error planSessionOwnerFn func(ctx context.Context, sessionID string) (int64, error) + + // FAV stubs (U7 incremental): nil / off unless FAV_PUBLISHER_ENABLED. + vaultReg artifactvault.Registry + vaultStore *artifactvault.FilesystemStore + vaultPub *artifactvault.Publisher + vaultPublisherStarted int32 } // NewFromRuntime attaches the HTTP adapter to an already-wired composition root. @@ -114,6 +121,8 @@ func NewFromRuntime(rt *app.Runtime) *Server { s.svc.SetStageAccessGate(newBizdevStageGate(s.instSvc, s.pwSvc)) } s.initTaskRunExecutors() + s.initArtifactVault() + s.startVaultPublisher() return s } @@ -163,6 +172,8 @@ func (s *Server) Routes() http.Handler { mux.HandleFunc("GET /api/v1/health", s.handleHealth) mux.HandleFunc("GET /ready", s.handleReadiness) mux.HandleFunc("GET /api/v1/ready", s.handleReadiness) + // FAV status stub (publisher/degraded default off; no ingest) + mux.HandleFunc("GET /api/v1/vault/status", s.handleVaultStatus) // 2. 项目与多工作流控制面 mux.HandleFunc("POST /api/v1/projects", s.handleCreateProject) diff --git a/internal/server/vault.go b/internal/server/vault.go new file mode 100644 index 00000000..ef015028 --- /dev/null +++ b/internal/server/vault.go @@ -0,0 +1,104 @@ +package server + +import ( + "context" + "fmt" + "net/http" + "sync/atomic" + "time" + + "chat-agent-system/internal/artifactvault" + "chat-agent-system/internal/config" +) + +// Vault HTTP + in-process publisher stubs for U7 incremental. +// Default: FAV_PUBLISHER_ENABLED=false, FAV_DEGRADED_READ=false. +// Full ingest hooks / enumerate backfill remain on feature/final-artifact-vault. + +func (s *Server) initArtifactVault() { + if s == nil || s.cfg == nil { + return + } + // Publisher store stays nil unless explicitly enabled AND vault roots are set. + if !s.cfg.FAVPublisherEnabled { + return + } + if s.cfg.VaultStateRoot == "" || s.cfg.VaultMasterKey == "" { + return + } + if err := config.ValidateIndependentVault(s.cfg.VaultStateRoot, s.cfg.DataDir); err != nil { + return + } + ident := s.cfg.VaultStorageIdentity + if ident == "" { + env := s.cfg.Env + if env == "" { + env = "dev" + } + ident = "vault-" + env + } + store, err := artifactvault.NewFilesystemStore(s.cfg.VaultStateRoot, ident, []byte(s.cfg.VaultMasterKey)) + if err != nil { + return + } + var reg artifactvault.Registry + if s.pool != nil { + reg = artifactvault.NewPGRegistry(s.pool) + } else { + reg = artifactvault.NewMemoryRegistry() + } + s.vaultStore = store + s.vaultReg = reg + s.vaultPub = &artifactvault.Publisher{Reg: reg, Store: store} +} + +func (s *Server) startVaultPublisher() { + if s == nil || s.vaultPub == nil || s.cfg == nil || !s.cfg.FAVPublisherEnabled { + return + } + go func() { + t := time.NewTicker(2 * time.Second) + defer t.Stop() + for range t.C { + _ = s.vaultPub.Drain(context.Background()) + } + }() + atomic.StoreInt32(&s.vaultPublisherStarted, 1) +} + +func (s *Server) tryVaultDegraded(ctx context.Context, env, logical, projectKey string) ([]byte, string, error) { + if s == nil || s.vaultPub == nil || s.cfg == nil || !s.cfg.FAVDegradedRead { + return nil, "", fmt.Errorf("artifactvault: degraded read disabled") + } + return s.vaultPub.DegradedPayload(ctx, env, logical, projectKey) +} + +func (s *Server) handleVaultStatus(w http.ResponseWriter, r *http.Request) { + _ = r + cfg := s.cfg + degraded := false + publisher := false + storeReady := false + if cfg != nil { + degraded = cfg.FAVDegradedRead + publisher = cfg.FAVPublisherEnabled + } + if s.vaultStore != nil { + storeReady = true + } + writeJSONResponse(w, http.StatusOK, map[string]any{ + "status": "ok", + "consumer": "fav", + "consumer_mode": "off", + "publisher_enabled": publisher, + "publisher_running": atomic.LoadInt32(&s.vaultPublisherStarted) == 1, + "degraded_read": degraded, + "store_configured": storeReady, + "subscriber_default": false, + "note": "fav consumer remains OFF until separate approval; full FAV branch not merged", + }) +} + +func (s *Server) vaultPublisherRunning() bool { + return atomic.LoadInt32(&s.vaultPublisherStarted) == 1 +} diff --git a/internal/server/vault_test.go b/internal/server/vault_test.go new file mode 100644 index 00000000..c6b7afe2 --- /dev/null +++ b/internal/server/vault_test.go @@ -0,0 +1,69 @@ +package server + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "chat-agent-system/internal/config" +) + +func TestVaultDefaultsStayDisabled(t *testing.T) { + cfg, err := config.Load("/dev/null") + if err != nil { + t.Fatal(err) + } + if cfg.FAVDegradedRead { + t.Fatal("FAV_DEGRADED_READ must default false") + } + if cfg.FAVPublisherEnabled { + t.Fatal("FAV_PUBLISHER_ENABLED must default false") + } + + s := &Server{cfg: cfg} + s.initArtifactVault() + s.startVaultPublisher() + if s.vaultPub != nil { + t.Fatal("vault publisher must stay nil when flag off") + } + if s.vaultPublisherRunning() { + t.Fatal("vault publisher must not start when flag off") + } + if _, _, err := s.tryVaultDegraded(context.Background(), "dev", "doc", "1"); err == nil { + t.Fatal("degraded read must refuse when flag off") + } +} + +func TestVaultStatusReportsOff(t *testing.T) { + s := &Server{cfg: &config.Config{Env: "dev"}} + req := httptest.NewRequest(http.MethodGet, "/api/v1/vault/status", nil) + rr := httptest.NewRecorder() + s.handleVaultStatus(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("status=%d", rr.Code) + } + var body map[string]any + if err := json.Unmarshal(rr.Body.Bytes(), &body); err != nil { + t.Fatal(err) + } + if body["consumer_mode"] != "off" { + t.Fatalf("consumer_mode=%v", body["consumer_mode"]) + } + if body["publisher_enabled"] != false { + t.Fatalf("publisher_enabled=%v", body["publisher_enabled"]) + } + if body["degraded_read"] != false { + t.Fatalf("degraded_read=%v", body["degraded_read"]) + } +} + +func TestValidateIndependentVaultRejectsDataRoot(t *testing.T) { + if err := config.ValidateIndependentVault("./data/dev", "./data/dev"); err == nil { + t.Fatal("expected vault/data collision error") + } + if err := config.ValidateIndependentVault("", "./data/dev"); err != nil { + t.Fatal(err) + } +} From 47a173e7bbc0e4d8a081be43df173cc6f9811635 Mon Sep 17 00:00:00 2001 From: weichen Date: Wed, 16 Sep 2026 15:40:10 +0000 Subject: [PATCH 3/6] feat(events): add fav subscriber and artifact-vault CLI stubs Keep durable fav unregistered and fail-closed; CLI only prints status and remaining-port inventory without destructive backfill/recover commands. Co-authored-by: Cursor --- cmd/artifact-vault/main.go | 57 +++++++++++++++++++++++++++++ cmd/events/main.go | 2 + internal/app/fav/subscriber.go | 46 +++++++++++++++++++++++ internal/app/fav/subscriber_test.go | 35 ++++++++++++++++++ 4 files changed, 140 insertions(+) create mode 100644 cmd/artifact-vault/main.go create mode 100644 internal/app/fav/subscriber.go create mode 100644 internal/app/fav/subscriber_test.go diff --git a/cmd/artifact-vault/main.go b/cmd/artifact-vault/main.go new file mode 100644 index 00000000..771a992d --- /dev/null +++ b/cmd/artifact-vault/main.go @@ -0,0 +1,57 @@ +// Command artifact-vault is the FAV operator CLI stub for U7 incremental. +// +// Full backfill / inventory / recover-apply / destroy live on +// feature/final-artifact-vault and were NOT whole-branch merged. This binary +// compiles on current dev and stays non-destructive: it only prints status / +// remaining-port inventory. +package main + +import ( + "encoding/json" + "fmt" + "os" +) + +func main() { + if len(os.Args) < 2 { + usage() + os.Exit(2) + } + switch os.Args[1] { + case "status", "version": + writeJSON(map[string]any{ + "binary": "artifact-vault", + "slice": "u7-incremental-stub", + "publisher_default": false, + "degraded_read": false, + "consumer": "fav", + "consumer_mode": "off", + "remaining_to_port": []string{ + "enumerate.go (EnumerateTerminals / LoadExclusions)", + "TerminalRow + Backfill(rows) API (diverged from dataset Candidate path)", + "full recover-plan / recover-apply / gc-preflight / destroy", + "server ingest hooks (OnFinalArtifact*)", + "durable fav event Register after separate approval", + }, + "source_branch": "feature/final-artifact-vault", + "note": "do not enable FAV_DEGRADED_READ or fav consumer without approval", + }) + case "help", "-h", "--help": + usage() + default: + fmt.Fprintf(os.Stderr, "artifact-vault: unknown command %q (full CLI remains on feature/final-artifact-vault)\n", os.Args[1]) + usage() + os.Exit(2) + } +} + +func usage() { + fmt.Fprintln(os.Stderr, "usage: artifact-vault ") + fmt.Fprintln(os.Stderr, "U7 stub only — backfill/recover/destroy not ported; fav stays off") +} + +func writeJSON(v any) { + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + _ = enc.Encode(v) +} diff --git a/cmd/events/main.go b/cmd/events/main.go index f9d8ebfe..a14fef81 100644 --- a/cmd/events/main.go +++ b/cmd/events/main.go @@ -83,6 +83,8 @@ func main() { (&docproject.EventConsumer{Docs: gov, Env: cfg.Env}).HandleLease, (&intentflow.ProjectionConsumer{Env: cfg.Env}).HandleLease, )) + // Intentionally NOT registering events.ConsumerFAV / internal/app/fav. + // Registry.Register("fav") fails closed; mode stays off until separate approval. mux := http.NewServeMux() mux.HandleFunc("GET /health", func(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusOK, map[string]any{"status": "ok", "env": cfg.Env, "owner": owner}) diff --git a/internal/app/fav/subscriber.go b/internal/app/fav/subscriber.go new file mode 100644 index 00000000..84c38715 --- /dev/null +++ b/internal/app/fav/subscriber.go @@ -0,0 +1,46 @@ +// Package fav holds the durable Final Artifact Vault event subscriber stub. +// +// U7 incremental rule: the fav consumer stays mode=off and unregistered until +// a separate approval. Registry.Register("fav") and SetMode(fav, live) already +// fail closed in internal/events; this package must not bypass those gates. +package fav + +import ( + "context" + "fmt" + + "chat-agent-system/internal/artifactvault" + "chat-agent-system/internal/events" + + "github.com/jackc/pgx/v5" +) + +// EventConsumer is the durable FAV publisher subscriber. +// Enabled defaults to false (zero value). Do not register into cmd/events +// until separate cutover approval. +type EventConsumer struct { + Publisher *artifactvault.Publisher + Env string + // Enabled must stay false by default. Even when true, events.Registry + // still rejects Register(ConsumerFAV) until the hard gate is lifted. + Enabled bool +} + +// HandleLease refuses work while the subscriber is disabled (default). +// Even when Enabled is flipped locally, the stub still refuses until the +// events registry hard-gate and a separate cutover approval are both lifted. +func (c *EventConsumer) HandleLease(ctx context.Context, lease events.Lease, tx pgx.Tx) error { + _ = ctx + _ = lease + _ = tx + if c == nil || !c.Enabled { + return fmt.Errorf("%w: fav", events.ErrDisabledConsumer) + } + _ = c.Publisher + return fmt.Errorf("%w: fav publisher cutover not approved", events.ErrDisabledConsumer) +} + +// DefaultEnabled reports the compile-time / zero-value default for the subscriber. +func DefaultEnabled() bool { + return false +} diff --git a/internal/app/fav/subscriber_test.go b/internal/app/fav/subscriber_test.go new file mode 100644 index 00000000..a08485ca --- /dev/null +++ b/internal/app/fav/subscriber_test.go @@ -0,0 +1,35 @@ +package fav_test + +import ( + "context" + "errors" + "testing" + + "chat-agent-system/internal/app/fav" + "chat-agent-system/internal/events" +) + +func TestFavSubscriberDefaultsDisabled(t *testing.T) { + if fav.DefaultEnabled() { + t.Fatal("fav subscriber DefaultEnabled must be false") + } + c := &fav.EventConsumer{} + if c.Enabled { + t.Fatal("zero-value EventConsumer.Enabled must be false") + } + err := c.HandleLease(context.Background(), events.Lease{}, nil) + if !errors.Is(err, events.ErrDisabledConsumer) { + t.Fatalf("disabled HandleLease: %v", err) + } +} + +func TestFavSubscriberStillDisabledWhenFlagOnWithoutRegistryApproval(t *testing.T) { + c := &fav.EventConsumer{Enabled: true, Env: "dev"} + err := c.HandleLease(context.Background(), events.Lease{Environment: "dev"}, nil) + if !errors.Is(err, events.ErrDisabledConsumer) { + t.Fatalf("enabled stub must still refuse until cutover approved: %v", err) + } + if err := events.NewRegistry().Register(events.ConsumerFAV); err == nil { + t.Fatal("Registry.Register(fav) must remain fail-closed") + } +} From 5392238354fd6beca9affdcaf2c75d683188570f Mon Sep 17 00:00:00 2001 From: weichen Date: Wed, 16 Sep 2026 15:40:10 +0000 Subject: [PATCH 4/6] chore(compose): pin FAV_DEGRADED_READ and publisher off Document false defaults in all envs; main hardcodes both flags to false. Co-authored-by: Cursor --- docker-compose.dev.yml | 3 +++ docker-compose.main.yml | 2 ++ docker-compose.test.yml | 2 ++ 3 files changed, 7 insertions(+) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 82c1a1aa..3623e9ee 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -126,6 +126,9 @@ services: WORKFLOW_INTENTS_ENABLED: ${WORKFLOW_INTENTS_ENABLED:-true} MULTI_INTENT_ENABLED: ${MULTI_INTENT_ENABLED:-true} SEMANTIC_FALLBACK_ENABLED: ${SEMANTIC_FALLBACK_ENABLED:-false} + # FAV U7: keep degraded read + in-process publisher off until separate approval + FAV_DEGRADED_READ: ${FAV_DEGRADED_READ:-false} + FAV_PUBLISHER_ENABLED: ${FAV_PUBLISHER_ENABLED:-false} ports: - "${APP_PUBLISH_HOST:-127.0.0.1}:${HTTP_PORT:-8080}:${HTTP_PORT:-8080}" healthcheck: diff --git a/docker-compose.main.yml b/docker-compose.main.yml index 0a7cd2b1..7a9dd74d 100644 --- a/docker-compose.main.yml +++ b/docker-compose.main.yml @@ -125,6 +125,8 @@ services: WORKFLOW_INTENTS_ENABLED: ${WORKFLOW_INTENTS_ENABLED:-true} MULTI_INTENT_ENABLED: ${MULTI_INTENT_ENABLED:-true} SEMANTIC_FALLBACK_ENABLED: ${SEMANTIC_FALLBACK_ENABLED:-false} + FAV_DEGRADED_READ: "false" + FAV_PUBLISHER_ENABLED: "false" ports: - "${APP_PUBLISH_HOST:-0.0.0.0}:${HTTP_PORT:-8000}:${HTTP_PORT:-8000}" healthcheck: diff --git a/docker-compose.test.yml b/docker-compose.test.yml index fb0b8a30..7ab5d8c9 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -125,6 +125,8 @@ services: WORKFLOW_INTENTS_ENABLED: ${WORKFLOW_INTENTS_ENABLED:-true} MULTI_INTENT_ENABLED: ${MULTI_INTENT_ENABLED:-true} SEMANTIC_FALLBACK_ENABLED: ${SEMANTIC_FALLBACK_ENABLED:-false} + FAV_DEGRADED_READ: ${FAV_DEGRADED_READ:-false} + FAV_PUBLISHER_ENABLED: ${FAV_PUBLISHER_ENABLED:-false} ports: - "${APP_PUBLISH_HOST:-0.0.0.0}:${HTTP_PORT:-8081}:${HTTP_PORT:-8081}" healthcheck: From de65cee96c38ba1adba0e92f04c46a40dbf8bf1a Mon Sep 17 00:00:00 2001 From: weichen Date: Wed, 16 Sep 2026 15:40:10 +0000 Subject: [PATCH 5/6] docs(test): record FAV U7 incremental go/no-go evidence Subscriber remains OFF until separate approval; document remaining FAV port inventory and claim feature/fav-u7-publisher. Co-authored-by: Cursor --- .../audit/2026-09-16-fav-u7-remaining-port.md | 21 ++++ .doc/ops/CURRENT.md | 1 + .../2026-09-16-fav-u7-incremental.md | 39 +++++-- .doc/status/FEATURE-MATRIX.md | 2 +- ...26-09-16-fav-u7-incremental-test-report.md | 110 ++++++++++++++++++ .../unit-tests.txt | 63 ++++++++++ 6 files changed, 228 insertions(+), 8 deletions(-) create mode 100644 .doc/audit/2026-09-16-fav-u7-remaining-port.md create mode 100644 .doc/test-reports/2026-09-16-fav-u7-incremental-test-report.md create mode 100644 .doc/test-reports/assets/2026-09-16-fav-u7-incremental/unit-tests.txt diff --git a/.doc/audit/2026-09-16-fav-u7-remaining-port.md b/.doc/audit/2026-09-16-fav-u7-remaining-port.md new file mode 100644 index 00000000..2ace9937 --- /dev/null +++ b/.doc/audit/2026-09-16-fav-u7-remaining-port.md @@ -0,0 +1,21 @@ +--- +title: "FAV U7 incremental — remaining port inventory" +status: open +created: 2026-09-16 +updated: 2026-09-16 +--- + +# FAV U7 增量:未移植清单 + +相对 `feature/final-artifact-vault`,本切片 **未** 整支 merge,且因 `internal/artifactvault` 与当前 `dev`(dataset Candidate backfill)大面积 DIFF,下列能力仍待后续手工移植 + 审计: + +| 项 | 旁支路径 | 阻塞 | +|----|----------|------| +| EnumerateTerminals / LoadExclusions | `internal/artifactvault/enumerate.go` | 依赖 TerminalRow;与现 `Enumerate(Candidate)` 并存需设计 | +| TerminalRow Backfill | FAV `backfill.go` | 现 backfill 已改为 dataset 路径 | +| 完整 CLI | `cmd/artifact-vault` 全命令 | 本切片仅 status stub | +| ingest hooks | `internal/server/vault.go` OnFinalArtifact* | 会写 staging;需与 publisher 门闩同批审批 | +| schema JSON | `*.schema.json` | 可选文档资产 | +| durable Register(fav) | `cmd/events` | events 硬门 + 单独 cutover 审批 | + +**红线保持**:`FAV_DEGRADED_READ=false`;fav consumer mode=off;禁止整支 merge。 diff --git a/.doc/ops/CURRENT.md b/.doc/ops/CURRENT.md index 99ea1d3d..cfa6cba4 100644 --- a/.doc/ops/CURRENT.md +++ b/.doc/ops/CURRENT.md @@ -13,6 +13,7 @@ | owner | task | branch | worktree | mutex | status | |-------|------|--------|----------|-------|--------| +| Cursor | 2026-09-16-fav-u7-incremental | feature/fav-u7-publisher | .worktrees/feature-fav-u7-publisher | events | in_progress | | Cursor | 2026-09-16-full-backlog-override | docs/override-nogo-full-backlog | - | architecture-docs | in_progress | | Cursor | 2026-09-16-gitea-nightly-docker-datadir | fix/gitea-nightly-docker-datadir | - | ci | done | | Cursor | 2026-09-16-gitea-nightly-host-tools | fix/gitea-nightly-host-tools | - | ci | done | diff --git a/.doc/ops/short-term/2026-09-16-fav-u7-incremental.md b/.doc/ops/short-term/2026-09-16-fav-u7-incremental.md index 5b9e159b..05ddb939 100644 --- a/.doc/ops/short-term/2026-09-16-fav-u7-incremental.md +++ b/.doc/ops/short-term/2026-09-16-fav-u7-incremental.md @@ -1,11 +1,11 @@ --- title: "FAV U7 增量合入(非整支 merge)" -status: planned +status: in_progress created: 2026-09-16 updated: 2026-09-16 owner: Cursor branch: feature/fav-u7-publisher -worktree: "-" +worktree: ".worktrees/feature-fav-u7-publisher" mutex: events --- @@ -15,13 +15,38 @@ mutex: events 从 `feature/final-artifact-vault` **手工移植**经审计切片到 `dev`;consumer 默认 off;禁止整支 merge;禁止默认开 `FAV_DEGRADED_READ`。 -## 步骤 +## 范围 -1. [ ] cherry-pick / 移植 cmd/artifact-vault + vault HTTP + publisher 订阅 -2. [ ] 隔离库负例 + 默认 off 冒烟 -3. [ ] 独立 go/no-go 报告 +- 配置门闩(`FAV_DEGRADED_READ` / `FAV_PUBLISHER_ENABLED` 默认 false) +- HTTP `GET /api/v1/vault/status` + gated in-process publisher stub +- `internal/app/fav` durable subscriber stub(不注册到 cmd/events) +- `cmd/artifact-vault` status stub(完整 CLI 未移植) ## 非目标 - 整支 merge `feature/final-artifact-vault` -- 未审计启用 degraded read +- 未审计启用 degraded read / fav live / publisher production Drain +- 移植 enumerate / TerminalRow Backfill / OnFinalArtifact hooks + +## 步骤 + +1. [x] 审计 FAV vs `dev` artifactvault 冲突(多数 DIFF → 改 stub) +2. [x] 落地 stub + 默认 off 测试 +3. [x] go/no-go 报告 +4. [ ] 推送 `feature/fav-u7-publisher`(不合 dev) + +## 验收标准 + +- [x] fav consumer / Register 仍 fail-closed +- [x] `FAV_DEGRADED_READ` 默认 false +- [x] 测试报告含 NO-GO:subscriber 保持 OFF 直至单独审批 +- [ ] 已 push 远端 feature 分支 + +## 相关链接 + +- Test report: [.doc/test-reports/2026-09-16-fav-u7-incremental-test-report.md](../../test-reports/2026-09-16-fav-u7-incremental-test-report.md) +- Assets: [.doc/test-reports/assets/2026-09-16-fav-u7-incremental/](../../test-reports/assets/2026-09-16-fav-u7-incremental/) + +## 证据 / 备注 + +因 `internal/artifactvault` 与 FAV 旁支大面积分叉,未 cherry-pick 核心库;采用最小可编译、默认关闭的 publisher/HTTP/CLI stub。 diff --git a/.doc/status/FEATURE-MATRIX.md b/.doc/status/FEATURE-MATRIX.md index 61987c24..c0952217 100644 --- a/.doc/status/FEATURE-MATRIX.md +++ b/.doc/status/FEATURE-MATRIX.md @@ -66,7 +66,7 @@ | CI / E2E | PR CI、PG 集成、治理 PG、已签真实 PI 门禁;**nightly** schedule/`workflow_dispatch` + infra/product 分类 receipt | partial | 加固(阻止**下一次**晋升,不自动否定已发布) | [BUG-08](../test-reports/2026-09-16-ci-governance-pg-bug08-test-report.md);[PI 三工作流](../test-reports/2026-09-16-pi-three-workflow-e2e-test-report.md);[B3](../test-reports/2026-09-16-chat-refresh-gate-test-report.md);[B4](../test-reports/2026-09-16-frontend-gate-b4-test-report.md);[Wave2B nightly/receipt](../test-reports/2026-09-16-wave2b-nightly-promotion-receipt-test-report.md) | 关闭条件:nightly 入口 + 失败分类 + promotion-receipt 门禁已交付;TempDir race **已修**;PR CI 不依赖外部模型。Actions secret/runner 需环境配置 | `ci` | | Artifact 同 digest 晋升 | 候选 commit 一次构建、按 digest 晋升、promotion receipt | done | 加固 | [计划 PU5B](../remediation/2026-09-16-feature-matrix-production-readiness-plan.md);[Wave2B](../test-reports/2026-09-16-wave2b-nightly-promotion-receipt-test-report.md);[Wave2C](../test-reports/2026-09-16-wave2c-same-image-digest-test-report.md) | **关闭**:same-image pin(`candidate-images-*` + `PINNED_IMAGE_MODE` + promote `--require-digests`)。**声明**:单机 retag/digest pin;远程 registry 推送另立任务 | `infra` | | 可靠事件核心 | PG dispatcher、notify/advance/projection live、生产闭包 hook 退出、Phase2 选择性解耦已合入 | done | non_blocker | [E0A–E2](../test-reports/2026-09-15-event-e0a-production-scope-contract-test-report.md) 等;[复盘](../ops/research/2026-09-15-track-e-retrospective.md);[shared live](../test-reports/2026-09-15-event-notify-shared-live-test-report.md);[phase2 closeout](../test-reports/2026-09-15-phase2-closeout-test-report.md) | **before**:与 FAV off 混成 partial → **after**:FAV 独立行。选择性后续解耦按收益触发 | `architecture-docs` / `events` | -| Final Artifact Vault(FAV) | 不可变终态制品库;`FAV_DEGRADED_READ=false` | planned | deferred_enhancement | [Track E 复盘](../ops/research/2026-09-15-track-e-retrospective.md);[short-term](../ops/short-term/2026-09-16-fav-u7-incremental.md) | **覆盖授权增量实施**;仍**禁止**整支 merge;degraded read 默认 false | `events` | +| Final Artifact Vault(FAV) | 不可变终态制品库;`FAV_DEGRADED_READ=false` | planned | deferred_enhancement | [Track E 复盘](../ops/research/2026-09-15-track-e-retrospective.md);[short-term](../ops/short-term/2026-09-16-fav-u7-incremental.md);[U7 stub 报告](../test-reports/2026-09-16-fav-u7-incremental-test-report.md);[未移植清单](../audit/2026-09-16-fav-u7-remaining-port.md) | **U7 stub 在 `feature/fav-u7-publisher`**:publisher/HTTP/CLI 默认 off;**禁止**整支 merge;subscriber **NO-GO** 直至单独审批 | `events` | --- diff --git a/.doc/test-reports/2026-09-16-fav-u7-incremental-test-report.md b/.doc/test-reports/2026-09-16-fav-u7-incremental-test-report.md new file mode 100644 index 00000000..20e72c57 --- /dev/null +++ b/.doc/test-reports/2026-09-16-fav-u7-incremental-test-report.md @@ -0,0 +1,110 @@ +# FAV U7 增量(publisher stub)测试报告 + +## 一、 测试背景 (Background) + +P7 Final Artifact Vault 禁止整支 merge `feature/final-artifact-vault`。相对当前 `dev`,FAV 旁支的 `internal/artifactvault/*` 多数文件已大幅分叉(dataset Candidate backfill vs TerminalRow),强行 cherry-pick 会引入不可控冲突。本切片改为 **U7 增量 stub**:配置门闩、HTTP status、events 订阅桩与 CLI stub,全部默认关闭。 + +## 二、 测试目标 (Test Objectives) + +- `FAV_DEGRADED_READ` 默认 false(config + docquery + vault GET) +- `FAV_PUBLISHER_ENABLED` 默认 false;进程内 Drain 不启动 +- fav durable consumer 保持 mode=off;`Registry.Register(fav)` fail-closed +- `cmd/artifact-vault` 可编译;仅 `status|version`,无破坏性操作 +- 产出 go/no-go:**subscriber 保持 OFF,直至单独审批** + +## 三、 测试方案与环境 (Test Plan & Environment) + +| 项 | 值 | +|----|----| +| OS | Linux | +| Go | 本机 `go test` / `go vet` | +| 分支 | `feature/fav-u7-publisher`(自 `dev@ebe177e`) | +| 层级 | 单元测试 + CLI 冒烟(无 stub 冒充真实 PI) | +| 证据 | `.doc/test-reports/assets/2026-09-16-fav-u7-incremental/unit-tests.txt` | + +核心用例矩阵: + +| ID | 用例 | 期望 | +|----|------|------| +| C1 | config 默认 | degraded/publisher false | +| C2 | vault init/start | publisher nil / not running | +| C3 | GET vault/status | consumer_mode=off | +| C4 | fav EventConsumer 零值 | HandleLease → ErrDisabledConsumer | +| C5 | events Registry | Register(fav) 失败;LiveConsumers 不含 fav | +| C6 | docquery | FavDegradedReadEnabled() false | +| C7 | artifact-vault status | 打印 remaining_to_port;无 backfill | + +## 四、 预期结果 (Expected Results) + +- 所有默认路径 FAIL-CLOSED:不写 vault blob、不 claim fav lease、不启用 degraded GET +- 显式 `FAV_*=true` 仅打开配置位;durable Register 仍被 events 硬门拒绝 +- go/no-go 结论:**NO-GO for enabling fav subscriber** + +## 五、 实际测试结果与判定 (Actual Results & Evaluation) + +### C1–C3 config / vault + +- **测试输入**:`config.Load("/dev/null")`;`Server{cfg}.initArtifactVault/startVaultPublisher`;`GET /api/v1/vault/status` +- **实际输出**:见 assets `unit-tests.txt` — `TestFAVFlagsDefaultOff` / `TestVaultDefaultsStayDisabled` / `TestVaultStatusReportsOff` PASS +- **评价判定**:PASS +- **耗时**:<10ms + +### C4 fav subscriber stub + +- **测试输入**:零值 `EventConsumer`;`Enabled=true` 仍调用 HandleLease +- **实际输出**:均 `events: consumer is disabled`;`Register(fav)` 失败 +- **评价判定**:PASS + +### C5–C6 events / docquery + +- **测试输入**:既有 `TestAssertLiveConsumersRejectsFAV`、`TestLiveConsumersOmitsFAV`、`TestFavDegradedReadDefaultOff` +- **实际输出**:PASS(PG claim 负例缺 `TEST_DATABASE_URL` 时 SKIP,与既有约定一致) +- **评价判定**:PASS + +### C7 CLI stub + +- **测试输入**:`go build ./cmd/artifact-vault && artifact-vault status` +- **实际输出**:JSON 含 `consumer_mode=off`、`degraded_read=false`、`remaining_to_port` 列表 +- **评价判定**:PASS + +## 六、 自动化测试套件汇总 (Test Suite Execution) + +```text +ok internal/config (FAV flags + ValidateIndependentVault) +ok internal/app/fav (subscriber defaults off) +ok internal/server (vault stub defaults off) +ok internal/events (FAV register/live omit) +ok internal/docquery (FavDegradedRead default off) +go vet PASS on touched packages +``` + +完整控制台:`assets/2026-09-16-fav-u7-incremental/unit-tests.txt` + +## 七、 测试结论与后续优化建议 (Conclusion & Next Steps) + +### Go / No-Go + +| 决策点 | 结论 | +|--------|------| +| 合入本 stub 分支到 `dev`(另占集成槽) | **条件 GO**(默认 off,可审) | +| 启用 fav durable subscriber / `Register(fav)` | **NO-GO** — 需单独审批 | +| 启用 `FAV_DEGRADED_READ` | **NO-GO** — 默认 false,未做 M2 负例放行 | +| 启用 `FAV_PUBLISHER_ENABLED` 生产 Drain | **NO-GO** — 缺 ingest hooks 与完整 CAS 接线审计 | +| 整支 merge `feature/final-artifact-vault` | **NO-GO**(红线) | + +### 仍待从 FAV 旁支手工移植(冲突重,本切片未搬) + +1. `enumerate.go`(`EnumerateTerminals` / `LoadExclusions`) +2. `TerminalRow` + `Backfill(rows)`(与现 dataset `Candidate` 路径分叉) +3. 完整 `recover-plan` / `recover-apply` / `gc-preflight` / `destroy` CLI +4. `server` ingest hooks(`OnFinalArtifact*`) +5. durable fav `Register` + live cutover(需审批解锁 events 硬门) + +### 本切片交付物 + +- `internal/config`:`FAV_*` / `VAULT_*` + `ValidateIndependentVault` +- `internal/server/vault.go`:status HTTP + gated publisher +- `internal/app/fav`:subscriber stub(默认 off) +- `cmd/artifact-vault`:status stub +- compose:`FAV_DEGRADED_READ` / `FAV_PUBLISHER_ENABLED` 钉死 false(main 硬编码) +- **未** merge 到 `dev` diff --git a/.doc/test-reports/assets/2026-09-16-fav-u7-incremental/unit-tests.txt b/.doc/test-reports/assets/2026-09-16-fav-u7-incremental/unit-tests.txt new file mode 100644 index 00000000..45fd31e0 --- /dev/null +++ b/.doc/test-reports/assets/2026-09-16-fav-u7-incremental/unit-tests.txt @@ -0,0 +1,63 @@ +=== go test FAV disablement suite === +=== RUN TestFAVFlagsDefaultOff +--- PASS: TestFAVFlagsDefaultOff (0.00s) +=== RUN TestFAVFlagsOptInOnly +--- PASS: TestFAVFlagsOptInOnly (0.00s) +=== RUN TestValidateIndependentVault +--- PASS: TestValidateIndependentVault (0.00s) +PASS +ok chat-agent-system/internal/config 0.002s +=== RUN TestFavSubscriberDefaultsDisabled +--- PASS: TestFavSubscriberDefaultsDisabled (0.00s) +=== RUN TestFavSubscriberStillDisabledWhenFlagOnWithoutRegistryApproval +--- PASS: TestFavSubscriberStillDisabledWhenFlagOnWithoutRegistryApproval (0.00s) +PASS +ok chat-agent-system/internal/app/fav 0.002s +=== RUN TestVaultDefaultsStayDisabled +--- PASS: TestVaultDefaultsStayDisabled (0.00s) +=== RUN TestVaultStatusReportsOff +--- PASS: TestVaultStatusReportsOff (0.00s) +=== RUN TestValidateIndependentVaultRejectsDataRoot +--- PASS: TestValidateIndependentVaultRejectsDataRoot (0.00s) +PASS +ok chat-agent-system/internal/server 0.007s +=== RUN TestValidateOpsCutoverRejectsFavAndNonDev +--- PASS: TestValidateOpsCutoverRejectsFavAndNonDev (0.00s) +=== RUN TestPrincipalRejectsWildcardAndFAV +--- PASS: TestPrincipalRejectsWildcardAndFAV (0.00s) +=== RUN TestAssertLiveConsumersRejectsFAV +--- PASS: TestAssertLiveConsumersRejectsFAV (0.00s) +=== RUN TestLiveConsumersOmitsFAV +--- PASS: TestLiveConsumersOmitsFAV (0.00s) +=== RUN TestManifestJSONIncludesDisabledFAV +--- PASS: TestManifestJSONIncludesDisabledFAV (0.00s) +=== RUN TestE0B_FAVNeverClaimed_PG + dispatcher_pg_test.go:85: TEST_DATABASE_URL not set; skipping PostgreSQL integration test +--- SKIP: TestE0B_FAVNeverClaimed_PG (0.00s) +PASS +ok chat-agent-system/internal/events 0.004s +=== RUN TestFavDegradedReadDefaultOff +--- PASS: TestFavDegradedReadDefaultOff (0.00s) +=== RUN TestFavDegradedReadOnlyAfterAuthorize +--- PASS: TestFavDegradedReadOnlyAfterAuthorize (0.00s) +PASS +ok chat-agent-system/internal/docquery 0.002s +=== go build cmd/artifact-vault === +{ + "binary": "artifact-vault", + "consumer": "fav", + "consumer_mode": "off", + "degraded_read": false, + "note": "do not enable FAV_DEGRADED_READ or fav consumer without approval", + "publisher_default": false, + "remaining_to_port": [ + "enumerate.go (EnumerateTerminals / LoadExclusions)", + "TerminalRow + Backfill(rows) API (diverged from dataset Candidate path)", + "full recover-plan / recover-apply / gc-preflight / destroy", + "server ingest hooks (OnFinalArtifact*)", + "durable fav event Register after separate approval" + ], + "slice": "u7-incremental-stub", + "source_branch": "feature/final-artifact-vault" +} +=== go vet === From 6356fb5a8c92922ae36a60c08fc8c92f70b4387d Mon Sep 17 00:00:00 2001 From: weichen Date: Wed, 16 Sep 2026 15:40:38 +0000 Subject: [PATCH 6/6] docs(ops): mark FAV U7 incremental push complete Feature branch pushed; still not merged to dev; fav subscriber remains OFF. Co-authored-by: Cursor --- .doc/ops/CURRENT.md | 2 +- .doc/ops/short-term/2026-09-16-fav-u7-incremental.md | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.doc/ops/CURRENT.md b/.doc/ops/CURRENT.md index cfa6cba4..795ad975 100644 --- a/.doc/ops/CURRENT.md +++ b/.doc/ops/CURRENT.md @@ -13,7 +13,7 @@ | owner | task | branch | worktree | mutex | status | |-------|------|--------|----------|-------|--------| -| Cursor | 2026-09-16-fav-u7-incremental | feature/fav-u7-publisher | .worktrees/feature-fav-u7-publisher | events | in_progress | +| Cursor | 2026-09-16-fav-u7-incremental | feature/fav-u7-publisher | .worktrees/feature-fav-u7-publisher | events | done | | Cursor | 2026-09-16-full-backlog-override | docs/override-nogo-full-backlog | - | architecture-docs | in_progress | | Cursor | 2026-09-16-gitea-nightly-docker-datadir | fix/gitea-nightly-docker-datadir | - | ci | done | | Cursor | 2026-09-16-gitea-nightly-host-tools | fix/gitea-nightly-host-tools | - | ci | done | diff --git a/.doc/ops/short-term/2026-09-16-fav-u7-incremental.md b/.doc/ops/short-term/2026-09-16-fav-u7-incremental.md index 05ddb939..1c8783fb 100644 --- a/.doc/ops/short-term/2026-09-16-fav-u7-incremental.md +++ b/.doc/ops/short-term/2026-09-16-fav-u7-incremental.md @@ -1,6 +1,6 @@ --- title: "FAV U7 增量合入(非整支 merge)" -status: in_progress +status: done created: 2026-09-16 updated: 2026-09-16 owner: Cursor @@ -27,26 +27,28 @@ mutex: events - 整支 merge `feature/final-artifact-vault` - 未审计启用 degraded read / fav live / publisher production Drain - 移植 enumerate / TerminalRow Backfill / OnFinalArtifact hooks +- 合入 `dev`(本切片仅 feature 推送) ## 步骤 1. [x] 审计 FAV vs `dev` artifactvault 冲突(多数 DIFF → 改 stub) 2. [x] 落地 stub + 默认 off 测试 3. [x] go/no-go 报告 -4. [ ] 推送 `feature/fav-u7-publisher`(不合 dev) +4. [x] 推送 `feature/fav-u7-publisher`(不合 dev) ## 验收标准 - [x] fav consumer / Register 仍 fail-closed - [x] `FAV_DEGRADED_READ` 默认 false - [x] 测试报告含 NO-GO:subscriber 保持 OFF 直至单独审批 -- [ ] 已 push 远端 feature 分支 +- [x] 已 push 远端 feature 分支 ## 相关链接 - Test report: [.doc/test-reports/2026-09-16-fav-u7-incremental-test-report.md](../../test-reports/2026-09-16-fav-u7-incremental-test-report.md) - Assets: [.doc/test-reports/assets/2026-09-16-fav-u7-incremental/](../../test-reports/assets/2026-09-16-fav-u7-incremental/) +- Remote: `origin/feature/fav-u7-publisher` ## 证据 / 备注 -因 `internal/artifactvault` 与 FAV 旁支大面积分叉,未 cherry-pick 核心库;采用最小可编译、默认关闭的 publisher/HTTP/CLI stub。 +因 `internal/artifactvault` 与 FAV 旁支大面积分叉,未 cherry-pick 核心库;采用最小可编译、默认关闭的 publisher/HTTP/CLI stub。commits: `3478a5a..de65cee`。