carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package hostops
2
3import (
4 "context"
5 "errors"
6 "os"
7
8 "github.com/carlosframework/platform/internal/release"
9)
10
11// ProjectionGrant is positive bucket evidence that lets one console replay
12// a per-console SQL projection of a settled app operation. It is minted
13// only here; it cannot create an operation, reopen admission or write app
14// content. The SQL view accepts exactly the statement family Kind names,
15// with the app_id predicate fixed to AppID.
16type ProjectionGrant struct {
17 kind, account, app, appID, operation string
18 epoch uint64
19 deletedAt int64
20}
21
22const (
23 ProjectionPurge = "purge"
24 ProjectionRestore = "restore"
25 ProjectionTrash = "trash"
26)
27
28func (g ProjectionGrant) Kind() string { return g.kind }
29func (g ProjectionGrant) Account() string { return g.account }
30func (g ProjectionGrant) App() string { return g.app }
31func (g ProjectionGrant) AppID() string { return g.appID }
32func (g ProjectionGrant) Operation() string { return g.operation }
33func (g ProjectionGrant) Epoch() uint64 { return g.epoch }
34func (g ProjectionGrant) DeletedAt() int64 { return g.deletedAt }
35func (g ProjectionGrant) Valid() bool { return g.kind != "" && g.appID != "" }
36
37func (w *ControlWriter) canonical(ctx context.Context, account, app, appID string) (AdmissionObserved, error) {
38 if err := w.ready(); err != nil {
39 return AdmissionObserved{}, err
40 }
41 a, err := ReadCanonicalAdmission(ctx, w.raw, account, app)
42 if err != nil {
43 return AdmissionObserved{}, err
44 }
45 if a.Value.AppID != appID {
46 return AdmissionObserved{}, ErrIdentity
47 }
48 return a, nil
49}
50
51// PurgeProjection: the incarnation is retired and its journal is gone.
52func (w *ControlWriter) PurgeProjection(ctx context.Context, account, app, appID string) (ProjectionGrant, error) {
53 a, err := ReadCanonicalAdmission(ctx, w.raw, account, app)
54 if err != nil {
55 return ProjectionGrant{}, err
56 }
57 if !CompletedAdmission(a.Value) || a.Value.LastPurge.AppID != appID {
58 return ProjectionGrant{}, ErrPending
59 }
60 if err = w.purgeAbsent(ctx, account, app); err != nil {
61 return ProjectionGrant{}, err
62 }
63 return ProjectionGrant{kind: ProjectionPurge, account: account, app: app, appID: appID, operation: a.Value.LastPurge.ID, epoch: a.Value.Epoch}, nil
64}
65
66// RestoreProjection: the exact receipt for this incarnation is complete, no
67// tombstone and no purge journal exist, and ctx holds a live permit for the
68// app. The open-admission projections (restore, trash) run inside their own
69// short exclusive app-mutation ("projection"), so no purge can start between
70// this evidence read and the SQL write. Epochs only grow after the receipt.
71func (w *ControlWriter) RestoreProjection(ctx context.Context, account, app, appID string) (ProjectionGrant, error) {
72 a, err := w.canonical(ctx, account, app, appID)
73 if err != nil {
74 return ProjectionGrant{}, err
75 }
76 if err = RequireAppMutation(ctx, w.raw, account, app, appID); err != nil {
77 return ProjectionGrant{}, err
78 }
79 if err = w.purgeAbsent(ctx, account, app); err != nil {
80 return ProjectionGrant{}, err
81 }
82 r := a.Value.LastRestore
83 if CompletedAdmission(a.Value) || r == nil || r.State != "complete" || r.AppID != appID || r.Epoch > a.Value.Epoch || r.DeletedAt <= 0 {
84 return ProjectionGrant{}, ErrPending
85 }
86 if _, _, err = w.raw.Get(ctx, release.AppTombstoneKey(account, app)); !errors.Is(err, os.ErrNotExist) {
87 if err != nil {
88 return ProjectionGrant{}, err
89 }
90 return ProjectionGrant{}, ErrPending
91 }
92 return ProjectionGrant{kind: ProjectionRestore, account: account, app: app, appID: appID, operation: r.ID, epoch: a.Value.Epoch, deletedAt: r.DeletedAt}, nil
93}
94
95// TrashProjection: a valid tombstone is present for the live incarnation.
96func (w *ControlWriter) TrashProjection(ctx context.Context, account, app, appID string) (ProjectionGrant, error) {
97 a, err := w.canonical(ctx, account, app, appID)
98 if err != nil {
99 return ProjectionGrant{}, err
100 }
101 if CompletedAdmission(a.Value) {
102 return ProjectionGrant{}, ErrPending
103 }
104 if err = RequireAppMutation(ctx, w.raw, account, app, appID); err != nil {
105 return ProjectionGrant{}, err
106 }
107 tomb, err := release.ReadAppTombstone(ctx, w.raw, account, app)
108 if err != nil {
109 return ProjectionGrant{}, err
110 }
111 if tomb == nil || tomb.DeletedAt.IsZero() {
112 return ProjectionGrant{}, ErrPending
113 }
114 return ProjectionGrant{kind: ProjectionTrash, account: account, app: app, appID: appID, epoch: a.Value.Epoch, deletedAt: tomb.DeletedAt.Unix()}, nil
115}
116