//go:build linux package host import ( "context" "reflect" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostguard" ) // Startup resumes only an existing immutable intent for its active successor. // A newer successor needs the ordinary request's stopped-continuation checks. // Incomplete paired retagging remains the independent claim writer's work. func (p *servingParkRoot) recoverServingPark(ctx context.Context, saved servingParkRecord) error { active := saved.activeAuthorization() return p.withRootInstallation(ctx, active.Successor, ctx.Err, func(o hostclaim.Observed, view hostguard.FenceView, actor hostbox.Actor, dir servingArtifactDirectory, tags uint64) error { r, err := p.ledger.read(saved.Authorization.ID) if err != nil || !reflect.DeepEqual(saved, r) || r.Complete || active.Directory != dir || (tags != active.TagsFrom && tags != active.Successor.Stamp) { return hostbox.ErrChanged } if err = p.validateServingRecoveryAuthority(ctx, o, view, actor, r); err != nil { return err } r, err = p.ledger.parkWithEvidenceLocked(ctx, r.Authorization, r.Receipts, r.LegacyEvidence) if err != nil { return err } if tags != active.Successor.Stamp { return errServingParkRetagPending } return p.ledger.completeLocked(ctx, r.Authorization.ID, active.Successor) }) } func (p *servingParkRoot) recoverServingParks(ctx context.Context, after string, limit int) (servingParkRecoveryPage, error) { var out servingParkRecoveryPage if p == nil || p.ledger == nil { return out, hostbox.ErrUnsupported } if after == "" { err := p.ledger.withNamespace(ctx, func() error { fs, ok := p.ledger.fs.(*servingParkFS) if !ok { return hostbox.ErrUnsupported } if err := fs.recoverRecordedNamespace(ctx); err == nil { return nil } observation, err := fs.inspectRebootTopology(ctx) if err != nil { return err } binding := servingParkBootBinding{Version: 1, ID: "namespace", BeforeBoot: observation.Previous.BootID, BootID: observation.BootID, BeforeParent: observation.Previous.Parent, Parent: observation.CurrentParent} if err := ctx.Err(); err != nil { return err } if err := p.ledger.write("current-namespace.json", binding); err != nil { return err } return fs.recoverRecordedNamespace(ctx) }) if err != nil { out.Results = append(out.Results, servingParkRecoveryResult{Host: "unknown", ID: "namespace", Err: err}) } } ids, more, err := p.ledger.recoveryIDs(ctx, after, limit) if err != nil { return out, err } out.More = more for _, id := range ids { if err = ctx.Err(); err != nil { out.More = true return out, err } r, readErr := p.ledger.read(id) result := servingParkRecoveryResult{Host: "unknown", ID: id, Err: readErr} if readErr == nil { result.Host = r.Authorization.Successor.Host fs, ok := p.ledger.fs.(*servingParkFS) parentChanged := false if ok { parent, id, e := fs.parent() if e != nil { result.Err = e } else { parent.Close() parentChanged = id != r.Source.Parent } } if ok && result.Err == nil && r.Source.BootID != "" && (fs.checkBoot(r.Source.BootID) != nil || parentChanged) { result.Err = p.commitServingReboot(ctx, id) if result.Err == nil { r, result.Err = p.ledger.read(id) } } switch { case result.Err != nil: case !r.Complete: result.Err = p.recoverServingPark(ctx, r) default: result.Err = p.ledger.recoverCompleted(ctx, id) } } out.Results = append(out.Results, result) out.Next = id } return out, nil } func (p *servingParkRoot) validateServingRecoveryAuthority(ctx context.Context, o hostclaim.Observed, view hostguard.FenceView, actor hostbox.Actor, r servingParkRecord) error { active := r.activeAuthorization() if err := validateServingRootHistory(ctx, o, view, actor, r); err != nil { return err } // Any main-role intent is ambiguous at startup, even if a start reply // was lost. Never resume a rename beneath a potentially live successor. if err := p.ledger.legacyUnlaunched(ctx, active.Successor, actor.Enrollment); err != nil { return err } if r.Authorization.Legacy { consumed, e := p.ledger.legacyConsumption(active.Successor) if e != nil || consumed == nil || !sameServingLegacyConsumption(*consumed, r) { return hostbox.ErrChanged } if err := p.validateLegacyRootEvidence(ctx, o, view, actor, r.Authorization, r.LegacyEvidence, r.Receipts); err != nil { return err } } else { prior, e := servingPredecessor(o.Claim, active.Successor, r.Authorization.Predecessor.Stamp, r.Authorization.Predecessor.Generation) if e != nil || prior != r.Authorization.Predecessor { return hostbox.ErrChanged } receipts, e := servingRootStops(ctx, view, prior, actor) if e != nil || !sameServingRootStops(receipts, r.Receipts) { return ErrClaimStopReceipt } disposition, e := servingGenerationParkDisposition(ctx, p.ledger.journal, prior, receipts, r.Source.Files[0].Present) if e != nil || disposition != r.Disposition { return hostbox.ErrChanged } } return nil }