fix(repo):统一发布健康与只读质量门禁
bat-rust / Build and test Rust (push) Canceled after 0s
bat-rust / Build and test Go API (push) Canceled after 0s

This commit is contained in:
2026-09-13 22:09:44 +08:00
parent 32fc64fa83
commit c17904ee1c
25 changed files with 869 additions and 184 deletions
+152 -28
View File
@@ -113,10 +113,17 @@ type LocalizedBackend interface {
LocalizedRollback(ctx context.Context, params backendrpc.LocalizedRollbackParams) (json.RawMessage, error)
}
// ReleaseStatusBackend exposes the Rust-owned release health fact used during
// discovery. It is kept separate so lightweight test/diagnostic backends do
// not have to implement the administrative release surface.
type ReleaseStatusBackend interface {
ReleaseStatus(ctx context.Context) (*backendrpc.ReleaseStatusReport, error)
}
// ReleaseBackend exposes Rust-owned dual-release queries, distribution
// selection and the explicit cleanup operation.
type ReleaseBackend interface {
ReleaseStatus(ctx context.Context) (*backendrpc.ReleaseStatusReport, error)
ReleaseStatusBackend
ReleaseList(ctx context.Context, params backendrpc.ReleaseListParams) (*backendrpc.ReleaseListReport, error)
ReleaseDistribution(ctx context.Context, params backendrpc.ReleaseDistributionParams) (*backendrpc.ReleaseDistributionPage, error)
ReleaseCleanup(ctx context.Context, params backendrpc.ReleaseCleanupParams) (*backendrpc.ReleaseCleanupReport, error)
@@ -303,14 +310,18 @@ type DiscoverResult struct {
DoctorHealthy *bool
Status *backendrpc.DaemonStatusReport
Doctor *backendrpc.DoctorReport
ReleaseStatus *backendrpc.ReleaseStatusReport
Distribution DistributionHealth
Snapshot *SnapshotSummary
ResourceRoot string
Index *ReleaseIndex
Warnings []string
}
// DiscoverAndIndex contacts the daemon (status first, then doctor) and builds
// a release index from paginated resource.manifest plus on-disk checks.
// DiscoverAndIndex contacts the daemon (status first, then doctor, then
// release.status) and builds a release index from paginated resource.manifest
// plus on-disk checks. Rust's release.status is the only release-level
// integrity authorization used for the production RPC path.
//
// If resourceRootOverride is non-empty, it wins over RPC-reported roots after
// RPC health probes (still preferred for production to call status/doctor).
@@ -326,6 +337,7 @@ func DiscoverAndIndex(ctx context.Context, backend Backend, resourceRootOverride
return nil, err
}
out.ResourceRoot = idx.ResourceRoot
out.Distribution = idx.Distribution
out.Index = idx
return out, nil
}
@@ -340,6 +352,7 @@ func DiscoverAndIndex(ctx context.Context, backend Backend, resourceRootOverride
return out, fmt.Errorf("daemon.status failed (%v) and resource-root load failed: %w", err, loadErr)
}
out.ResourceRoot = idx.ResourceRoot
out.Distribution = idx.Distribution
out.Index = idx
return out, nil
}
@@ -359,6 +372,45 @@ func DiscoverAndIndex(ctx context.Context, backend Backend, resourceRootOverride
out.DoctorHealthy = &h
}
// An explicit root is a fixture/emergency read-only override. Keep it
// outside the production RPC release-health contract, while still probing
// daemon status and doctor first.
if resourceRootOverride != "" {
idx, loadErr := LoadIndexFromResourceRoot(resourceRootOverride)
if loadErr != nil {
return out, fmt.Errorf("resource-root override load failed: %w", loadErr)
}
idx.Source = "resource_root"
idx.RPCAvailable = true
idx.DoctorHealthy = out.DoctorHealthy
out.ResourceRoot = idx.ResourceRoot
out.Snapshot = idx.Snapshot
out.Distribution = idx.Distribution
out.Index = idx
return out, nil
}
// 3) release.status is the Rust-owned whole-release distribution gate.
releaseStatusBackend, ok := backend.(ReleaseStatusBackend)
if !ok {
out.Warnings = append(out.Warnings, "release.status: backend does not expose Rust release health")
return emptyRPCResult(out, nil, "Rust release health is unavailable"),
fmt.Errorf("rust release health is unavailable")
}
releaseStatus, err := releaseStatusBackend.ReleaseStatus(ctx)
if err != nil {
out.Warnings = append(out.Warnings, fmt.Sprintf("release.status: %v", err))
return emptyRPCResult(out, nil, "Rust release health query failed"),
fmt.Errorf("release.status failed: %w", err)
}
if releaseStatus == nil {
out.Warnings = append(out.Warnings, "release.status: empty response")
return emptyRPCResult(out, nil, "Rust release health query returned no response"),
fmt.Errorf("release.status returned an empty response")
}
out.ReleaseStatus = releaseStatus
out.Distribution = rustDistributionHealth(releaseStatus)
// Catalog / resource discovery
var snapshot *SnapshotSummary
var resourceRoot string
@@ -375,6 +427,20 @@ func DiscoverAndIndex(ctx context.Context, backend Backend, resourceRootOverride
resourceRoot = root
}
}
if snapshot != nil &&
snapshot.VersionID != "" &&
out.ReleaseStatus.OfficialCurrentReleaseID != "" &&
snapshot.VersionID != out.ReleaseStatus.OfficialCurrentReleaseID {
return emptyRPCResult(
out,
snapshot,
fmt.Sprintf(
"release.status current ID %q does not match catalog current ID %q",
out.ReleaseStatus.OfficialCurrentReleaseID,
snapshot.VersionID,
),
), nil
}
if catalogAvailabilityKnown && !catalogAvailable {
return emptyRPCResult(out, snapshot, "catalog.status available=false; no published release"), nil
}
@@ -393,12 +459,13 @@ func DiscoverAndIndex(ctx context.Context, backend Backend, resourceRootOverride
resourceRoot = resourceRootOverride
}
if resourceRoot == "" {
out.Snapshot = snapshot
out.Snapshot = snapshotWithDistributionHealth(snapshot, out.Distribution)
out.Index = &ReleaseIndex{
Source: "rpc",
RPCAvailable: true,
DoctorHealthy: out.DoctorHealthy,
Snapshot: snapshot,
Distribution: out.Distribution,
Snapshot: snapshotWithDistributionHealth(snapshot, out.Distribution),
byRel: map[string]int{},
}
out.Warnings = append(out.Warnings, "no resource root from RPC; set --resource-root or publish a version")
@@ -421,28 +488,14 @@ func DiscoverAndIndex(ctx context.Context, backend Backend, resourceRootOverride
entries, manifestVersion, rootFromManifest, err := fetchAllManifestEntries(ctx, backend)
if err != nil {
out.Warnings = append(out.Warnings, fmt.Sprintf("resource.manifest: %v", err))
// Fallback: load local manifest file under root.
idx, loadErr := LoadIndexFromResourceRoot(resourceRoot)
if loadErr != nil {
if resourceRootOverride == "" {
return emptyRPCResult(
out,
snapshot,
fmt.Sprintf("published release cannot be indexed: %v", loadErr),
), nil
}
return out, fmt.Errorf("manifest RPC and local load failed: rpc=%v local=%w", err, loadErr)
}
idx.Source = "rpc+local_manifest"
idx.RPCAvailable = true
idx.DoctorHealthy = out.DoctorHealthy
if snapshot != nil {
idx.Snapshot = snapshot
}
out.ResourceRoot = idx.ResourceRoot
out.Snapshot = idx.Snapshot
out.Index = idx
return out, nil
// Without the RPC manifest there is no evidence that the local
// snapshot matches the Rust health fact. Do not pair a fresh health
// result with a potentially stale on-disk manifest.
return emptyRPCResult(
out,
snapshot,
fmt.Sprintf("published release cannot be indexed: %v", err),
), nil
}
if rootFromManifest != "" {
resourceRoot = rootFromManifest
@@ -459,6 +512,7 @@ func DiscoverAndIndex(ctx context.Context, backend Backend, resourceRootOverride
snapshot,
manifestVersion,
entries,
out.Distribution,
)
if err != nil {
if resourceRootOverride == "" {
@@ -471,18 +525,25 @@ func DiscoverAndIndex(ctx context.Context, backend Backend, resourceRootOverride
return out, err
}
out.ResourceRoot = idx.ResourceRoot
out.Snapshot = snapshot
out.Snapshot = idx.Snapshot
out.Index = idx
return out, nil
}
func emptyRPCResult(out *DiscoverResult, snapshot *SnapshotSummary, warning string) *DiscoverResult {
distribution := out.Distribution
if distribution.Source == "" {
distribution = unavailableRustDistributionHealth()
out.Distribution = distribution
}
snapshot = snapshotWithDistributionHealth(snapshot, distribution)
out.ResourceRoot = ""
out.Snapshot = snapshot
out.Index = &ReleaseIndex{
Source: "rpc",
RPCAvailable: true,
DoctorHealthy: out.DoctorHealthy,
Distribution: distribution,
Snapshot: snapshot,
byRel: map[string]int{},
}
@@ -492,6 +553,69 @@ func emptyRPCResult(out *DiscoverResult, snapshot *SnapshotSummary, warning stri
return out
}
func rustDistributionHealth(report *backendrpc.ReleaseStatusReport) DistributionHealth {
health := DistributionHealth{
Source: "rust_release_status",
Status: "blocked",
StatusCode: "distribution.blocked",
IntegrityStatus: "unknown",
}
if report == nil {
return health
}
health.Ready = report.OfficialDistributionReady
health.Status = report.Status
health.StatusCode = report.StatusCode
if health.Status == "" {
if health.Ready {
health.Status = "ready"
} else {
health.Status = "blocked"
}
}
if health.StatusCode == "" {
if health.Ready {
health.StatusCode = "distribution.ready"
} else {
health.StatusCode = "distribution.blocked"
}
}
for _, release := range report.Releases {
if release.Channel == "official" && release.Current {
health.IntegrityStatus = release.DistributionIntegrityStatus
health.Diagnostics = append([]string(nil), release.Diagnostics...)
break
}
}
if health.IntegrityStatus == "valid" && !health.Ready {
health.IntegrityStatus = "invalid"
}
return health
}
func unavailableRustDistributionHealth() DistributionHealth {
return DistributionHealth{
Source: "rust_release_status",
Status: "unavailable",
StatusCode: "distribution.health_unavailable",
IntegrityStatus: "unknown",
}
}
func snapshotWithDistributionHealth(snapshot *SnapshotSummary, health DistributionHealth) *SnapshotSummary {
if snapshot == nil {
return nil
}
updated := *snapshot
if health.Status != "" {
updated.DistributionStatus = health.Status
}
if health.StatusCode != "" {
updated.DistributionStatusCode = health.StatusCode
}
return &updated
}
func parseCatalogStatus(raw json.RawMessage) (*SnapshotSummary, string, bool) {
if len(raw) == 0 || string(raw) == "null" {
return nil, "", false