package hostops import ( "context" "crypto/ed25519" "encoding/json" "errors" "testing" "time" "github.com/carlosframework/platform/internal/release" ) func testWriter(t *testing.T) (*ControlWriter, *release.DirStore, func() time.Time) { t.Helper() raw := release.NewDirStore(t.TempDir()) pub, _, err := ed25519.GenerateKey(nil) if err != nil { t.Fatal(err) } now := time.Date(2026, 10, 7, 12, 0, 0, 0, time.UTC) clock := func() time.Time { return now } w, err := NewControlWriter(raw, pub, clock) if err != nil { t.Fatal(err) } return w, raw, clock } func seedAdmission(t *testing.T, s release.Store, account, app string, a AppAdmission) { t.Helper() body, _ := json.Marshal(a) key, _ := AdmissionKey(account, app) _, tag, _ := s.Get(context.Background(), key) if _, err := s.Put(context.Background(), key, body, tag); err != nil { t.Fatal(err) } } func openAdmission(appID string) AppAdmission { return AppAdmission{V: 1, AppID: appID, Revision: 1, Epoch: 1, State: "open", Registrations: map[string]Registration{}, Effects: map[string]Effect{}} } var testOwner = RegistrationOwner{Console: "console", Enrollment: "enrollment", Controller: "controller"} // Review P1-1: the data boundary refuses every canonical control object even // when its checker would allow everything, and even with a live permit. func TestDataBoundaryAlwaysRefusesControlObjects(t *testing.T) { ctx := t.Context() w, raw, clock := testWriter(t) seedAdmission(t, raw, "bab", "demo", openAdmission("app")) data, err := NewMutationBoundary(true, raw, func(context.Context, MutationTarget) error { return nil }) if err != nil { t.Fatal(err) } // A real permit in context changes nothing. h, err := w.BeginExclusive(ctx, "bab", "demo", "app", AppOperation{ID: "op", Kind: OpAppMutation, AppID: "app"}, testOwner) if err != nil { t.Fatal(err) } permitted, settle, err := h.Runner(clock).Issue(ctx, Effect{ID: "op", Resource: "app-mutation", Action: "test"}) if err != nil { t.Fatal(err) } defer settle("joined", nil) for _, key := range []string{"apps/bab/demo/admission.json", "apps/bab/demo/purge.json", "apps/bab/demo/purge-request.json", MailGateKey} { if _, err := data.Put(permitted, key, []byte(`{}`), ""); !errors.Is(err, ErrControlKey) { t.Fatalf("Put %s: %v", key, err) } if err := data.Delete(permitted, key); !errors.Is(err, ErrControlKey) { t.Fatalf("Delete %s: %v", key, err) } if err := release.DeleteIfMatch(permitted, data, key, `"x"`); !errors.Is(err, ErrControlKey) { t.Fatalf("DeleteIfMatch %s: %v", key, err) } } if _, err := data.Put(permitted, "apps/BAD!/demo/x.json", []byte(`{}`), ""); !errors.Is(err, ErrIdentity) { t.Fatalf("malformed app key: %v", err) } } // Review P1-1: no transition can reopen a completed purge or forge // last_purge / last_restore; the only way to last_purge is CompletePurge. func TestTypedTransitionsCannotReopenOrForge(t *testing.T) { ctx := t.Context() w, raw, _ := testWriter(t) done := openAdmission("old") done.State, done.Revision, done.Epoch = "closed", 8, 3 done.LastPurge = &PurgeMark{ID: "purge", AppID: "old"} seedAdmission(t, raw, "bab", "demo", done) for _, kind := range []string{OpDeleting, OpPlacing, OpAppMutation} { if _, err := w.BeginExclusive(ctx, "bab", "demo", "old", AppOperation{ID: "new-" + kind, Kind: kind, AppID: "old"}, testOwner); !errors.Is(err, ErrPending) { t.Fatalf("%s reopened completed admission: %v", kind, err) } } if _, err := w.BeginExclusive(ctx, "bab", "demo", "old", AppOperation{ID: "p", Kind: OpPurging, AppID: "old"}, testOwner); !errors.Is(err, ErrIdentity) { t.Fatalf("purging through BeginExclusive: %v", err) } if _, err := w.ResumeExclusive(ctx, "bab", "demo", "old", "purge", OpPurging, testOwner); !errors.Is(err, ErrPending) { t.Fatalf("resumed a finished purge: %v", err) } // nextAdmission refuses any last_purge change outside purge completion. before, err := ReadAdmission(ctx, raw, "bab", "demo", "old") if err != nil { t.Fatal(err) } if _, err = nextAdmission(before, func(a *AppAdmission) error { a.LastPurge = &PurgeMark{ID: "forged", AppID: "old"}; return nil }); !errors.Is(err, ErrIdentity) { t.Fatalf("forged last_purge: %v", err) } } // Review P2-3: a crash after the receipt and before (or after) the tombstone // delete is finished by RecoverMutation; nothing stays wedged. func TestRestoreReceiptRecoversEveryCrashPoint(t *testing.T) { for _, crash := range []string{"before-delete", "after-delete"} { t.Run(crash, func(t *testing.T) { ctx := t.Context() w, raw, clock := testWriter(t) seedAdmission(t, raw, "bab", "demo", openAdmission("app")) if err := release.TrashApp(ctx, raw, "bab", "demo", "owner", clock()); err != nil { t.Fatal(err) } _, tag, err := raw.Get(ctx, release.AppTombstoneKey("bab", "demo")) if err != nil { t.Fatal(err) } h, err := w.BeginExclusive(ctx, "bab", "demo", "app", AppOperation{ID: "op", Kind: OpAppMutation, AppID: "app", Intent: map[string]string{"action": "restore"}}, testOwner) if err != nil { t.Fatal(err) } if _, _, err = h.Runner(clock).Issue(ctx, Effect{ID: "op", Resource: "app-mutation", Action: "restore"}); err != nil { t.Fatal(err) } if err = h.PrepareRestore(ctx, tag, clock().Unix()); err != nil { t.Fatal(err) } if crash == "after-delete" { if err = release.DeleteIfMatch(ctx, raw, release.AppTombstoneKey("bab", "demo"), tag); err != nil { t.Fatal(err) } } // The issuer is gone; the effect is stuck at issued. if err = w.RecoverMutation(ctx, "bab", "demo", "app", "op"); err != nil { t.Fatal(err) } a, err := ReadAdmission(ctx, raw, "bab", "demo", "app") if err != nil { t.Fatal(err) } if a.Value.State != "open" || a.Value.Op != nil || a.Value.LastRestore == nil || a.Value.LastRestore.State != "complete" || a.Value.Effects["op"].State != "complete" { t.Fatalf("restore not reconciled: %+v", a.Value) } if _, _, err = raw.Get(ctx, release.AppTombstoneKey("bab", "demo")); err == nil { t.Fatal("tombstone survived a completed restore") } if _, err = w.RestoreProjection(ctx, "bab", "demo", "app"); !errors.Is(err, ErrPending) { t.Fatalf("projection without its own operation: %v", err) } p, err := w.BeginExclusive(ctx, "bab", "demo", "app", AppOperation{ID: "projection", Kind: OpAppMutation, AppID: "app", Intent: map[string]string{"action": "projection"}}, testOwner) if err != nil { t.Fatal(err) } pctx, settle, err := p.Runner(clock).Issue(ctx, Effect{ID: "projection", Resource: "app-mutation", Action: "projection"}) if err != nil { t.Fatal(err) } if g, err := w.RestoreProjection(pctx, "bab", "demo", "app"); err != nil || g.Kind() != ProjectionRestore { t.Fatalf("no projection grant inside its operation: %v", err) } if err = settle("joined", nil); err != nil { t.Fatal(err) } if err = p.FinishMutation(ctx); err != nil { t.Fatal(err) } }) } } // The mail gate cannot be released or checked by a reader of the object. func TestMailGatePossessionIsTheToken(t *testing.T) { ctx := t.Context() w, _, _ := testWriter(t) owner := MailGateOwner{Console: "c", Enrollment: "e", Controller: "one"} l, err := w.AcquireMailGate(ctx, owner, MailGateScope{}) if err != nil { t.Fatal(err) } if _, err = w.AcquireMailGate(ctx, owner, MailGateScope{}); !errors.Is(err, ErrPending) { t.Fatalf("second holder: %v", err) } forged := &MailGateLease{ID: l.ID, Owner: l.Owner, revision: l.revision, token: "guess"} if err = w.ReleaseMailGate(ctx, forged); !errors.Is(err, ErrPending) { t.Fatalf("forged release: %v", err) } if err = w.ReleaseMailGate(ctx, l); err != nil { t.Fatal(err) } if err = w.CheckMailGate(ctx, l); !errors.Is(err, ErrPending) { t.Fatalf("released lease still checks: %v", err) } } // Review P3: a non-matching earlier permit does not hide a valid later one. func TestRequireAppMutationTriesEveryPermit(t *testing.T) { ctx := t.Context() w, raw, clock := testWriter(t) seedAdmission(t, raw, "bab", "demo", openAdmission("app")) h, err := w.BeginExclusive(ctx, "bab", "demo", "app", AppOperation{ID: "op", Kind: OpAppMutation, AppID: "app"}, testOwner) if err != nil { t.Fatal(err) } stale := &AppMutationPermit{account: "bab", app: "demo", appID: "app", epoch: 1, operation: "op", effect: "missing", owner: testOwner, exclusive: true, now: clock} stale.live.Store(true) ctx = context.WithValue(ctx, appMutationPermitKey{}, []*AppMutationPermit{stale}) issued, settle, err := h.Runner(clock).Issue(ctx, Effect{ID: "op", Resource: "app-mutation", Action: "test"}) if err != nil { t.Fatal(err) } if err = RequireAppMutation(issued, raw, "bab", "demo", "app"); err != nil { t.Fatalf("valid second permit ignored: %v", err) } if err = settle("joined", nil); err != nil { t.Fatal(err) } if err = RequireAppMutation(issued, raw, "bab", "demo", "app"); !errors.Is(err, ErrPending) { t.Fatalf("settled permit still live: %v", err) } }