mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 11:34:59 +08:00
fix(repo):统一发布健康与只读质量门禁
This commit is contained in:
+152
-28
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user