Die Pressemitteilung soll Montag um neun raus, der Aktionsbanner am Monatsende weg. Niemand will dafür den Wecker stellen. In RustyCMS trägt der Eintrag sein Datum selbst; ein Hintergrundlauf schaltet ihn um, und für das Frontend sieht das aus wie jeder andere Statuswechsel.
Drei Zustände, eine Regel
_status ist draft, published oder archived. Anonyme Leser bekommen ausschließlich published — Drafts und Archiviertes bleiben unsichtbar, es sei denn, ein gültiger Preview-Token schaltet sie frei. Eingeloggte Nutzer und API-Keys filtern nach Bedarf. In der Admin-UI wechselt ein Klick auf den Badge den Zustand, Bulk-Aktionen erledigen viele Einträge auf einmal.
Datum setzen, fertig
_publishAt auf einem Draft genügt. Der Hintergrundlauf prüft alle 30 Sekunden (RUSTYCMS_SCHEDULE_INTERVAL_SECS, 0 schaltet ihn ab), setzt fällige Drafts auf published und verschiebt das Datum in seinen Vergangenheits-Zwilling _publishedAt — so bleibt nachvollziehbar, was geplant war und wann es tatsächlich passierte.
PATCH /api/content/post/my-post/status
{ "_status": "draft", "_publishAt": "2099-01-01T09:00:00+01:00" }
# Termin löschen: explizites null
PATCH /api/content/post/my-post/status
{ "_status": "draft", "_publishAt": null }
Der Übergang läuft über denselben Pfad wie ein manueller Statuswechsel: Versions-Snapshot, Suchindex, Webhook, Git-Autocommit — alles feuert. Im Audit-Log steht der Actor system:scheduler, damit man Automatik von Hand unterscheiden kann.
Ablaufen lassen
_unpublishAt ist das Spiegelbild: ein veröffentlichter Eintrag wird nach dem Datum archiviert. Beide Felder sind unabhängig und lassen sich zusammen setzen — auf einem Draft wartet das Ablaufdatum, bis der Eintrag live ist. Liegen beide Daten in der Vergangenheit, geht ein Durchlauf draft → published → archived und schreibt zwei Snapshots, keinen stillen Sprung.
Was ansteht, auf einen Blick
?_scheduled=true listet in jeder Collection die Einträge, bei denen noch ein Übergang feuern kann. GET /api/schedule liefert eine flache, nach Fälligkeit sortierte Liste über alle Collections — eine Zeile pro Übergang, filterbar nach kind=publish|expire und until. Die Admin-UI baut ihre Schedule-Seite darauf. Was schon passiert ist, steht im Audit-Log unter actor=system:scheduler; ob der Lauf überhaupt läuft, verraten vier Zähler unter /api/metrics.
Was nicht passiert
Ein Datum in der Vergangenheit publiziert beim nächsten Tick — kein Rückdatieren, keine Nachholrunde darüber hinaus. Ein _publishAt auf einem bereits veröffentlichten Eintrag ist wirkungslos, Archiviertes wird nie wieder angefasst. Ein einmal gefeuerter Termin kann nicht erneut auslösen.