| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | |
| 6 | "github.com/carlosframework/platform/internal/hostbox" |
| 7 | "github.com/carlosframework/platform/internal/hostguard" |
| 8 | ) |
| 9 | |
| 10 | type servingParkDisposition uint8 |
| 11 | |
| 12 | const ( |
| 13 | servingParkRefuse servingParkDisposition = iota |
| 14 | servingParkArtifactFree |
| 15 | servingParkDiscard |
| 16 | servingParkRetain |
| 17 | ) |
| 18 | |
| 19 | // servingGenerationParkDisposition is a root-side decision made after source |
| 20 | // capture and stop authentication. The database must exist to own the slot. |
| 21 | // A prelaunch record alone proves no tenant started. Once a main-role launch |
| 22 | // was intended, missing start evidence is ambiguous: StartedUnit is recorded |
| 23 | // after the process may have run. Preserve that source and refuse retirement. |
| 24 | // Task 0R's filesystem driver must persist this decision with its source |
| 25 | // identities before either replacing the slot or discarding a restore. |
| 26 | func servingGenerationParkDisposition(ctx context.Context, journal *hostbox.LaunchJournal, prior servingArtifactIdentity, receipts []hostguard.FenceReceipt, databasePresent bool) (servingParkDisposition, error) { |
| 27 | if ctx == nil || journal == nil || prior.Host == "" || prior.Box == "" || prior.Region == "" || prior.Incarnation == "" || prior.Generation == "" || prior.Stamp == 0 || prior.Session == 0 || len(receipts) == 0 { |
| 28 | return servingParkRefuse, hostbox.ErrChanged |
| 29 | } |
| 30 | launched := false |
| 31 | for _, receipt := range receipts { |
| 32 | t := receipt.Target |
| 33 | if t.Host != prior.Host || t.Box != prior.Box || t.Region != prior.Region || t.Incarnation != prior.Incarnation || t.Stamp != prior.Stamp || t.Session != prior.Session || t.Op != "" || t.Phase != "" || t.ObligationID != "" || verifyRetirementReceipt(ctx, receipt) != nil { |
| 34 | return servingParkRefuse, ErrClaimStopReceipt |
| 35 | } |
| 36 | } |
| 37 | if databasePresent { |
| 38 | t := receipts[0].Target |
| 39 | records, err := journal.RootSessionLaunches(ctx, prior.Host, prior.Incarnation, prior.Stamp, prior.Session, prior.Box, t.Enrollment) |
| 40 | if err != nil { |
| 41 | return servingParkRefuse, err |
| 42 | } |
| 43 | if len(records) != 1 { |
| 44 | return servingParkRefuse, hostbox.ErrChanged |
| 45 | } |
| 46 | record := records[0] |
| 47 | matched := false |
| 48 | for _, receipt := range receipts { |
| 49 | matched = matched || hostbox.SameLaunchOwner(record.Target, receipt.Target) |
| 50 | } |
| 51 | if !matched { |
| 52 | return servingParkRefuse, hostbox.ErrChanged |
| 53 | } |
| 54 | seen := make(map[string]bool) |
| 55 | for _, reservation := range record.Reservations { |
| 56 | if reservation.Role != "main" { |
| 57 | continue |
| 58 | } |
| 59 | if reservation.ID == "" || seen[reservation.ID] || !hostbox.SameLaunchOwner(reservation.Target, record.Target) { |
| 60 | return servingParkRefuse, hostbox.ErrChanged |
| 61 | } |
| 62 | seen[reservation.ID] = true |
| 63 | if reservation.NeverIntended && record.WritersClosed && reservation.State == hostbox.UnitSettled && reservation.PID == 0 && reservation.ProcessStart == "" && reservation.Group.InvocationID == "" && reservation.Group.Device == 0 && reservation.Group.Inode == 0 { |
| 64 | continue |
| 65 | } |
| 66 | if !hostbox.IsUnitReservation(reservation) || reservation.State != hostbox.UnitSettled || reservation.PID <= 0 || reservation.ProcessStart == "" || reservation.Group.InvocationID == "" || reservation.Group.Device == 0 || reservation.Group.Inode == 0 || reservation.Executable.Device == 0 || reservation.Executable.Inode == 0 { |
| 67 | return servingParkRefuse, hostbox.ErrChanged |
| 68 | } |
| 69 | launched = true |
| 70 | } |
| 71 | } |
| 72 | if !databasePresent { |
| 73 | return servingParkArtifactFree, nil |
| 74 | } |
| 75 | if launched { |
| 76 | return servingParkRetain, nil |
| 77 | } |
| 78 | return servingParkDiscard, nil |
| 79 | } |
| 80 | |