feat(stateful-templates): materialize cron+queue templates + hooks (M5.2-M5.4)
materialize::rematerialize_stateful_templates reconciles a group cron/queue template into one app-owned copy per descendant (materialized_from = template), via the all-apps app_chain CTE ⋈ group-owned stateful templates. A precise create/delete diff preserves cron last_fired_at on unchanged copies; a queue copy is skipped-with-warning when the app already fills that queue's consumer slot (the one-consumer invariant). Called full-live at the route-rebuild chokepoints: apply (single + tree), app create/delete (apps_api), group reparent (groups_api) — AppsState/GroupsState gain a pool. Pinned by tests/stateful_templates.rs (per-app copy, idempotent, new-app materializes, reparent de-materializes). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,10 @@ pub struct GroupsState {
|
||||
/// apps inherit — so the route snapshot is rebuilt after a reparent.
|
||||
pub routes: Arc<dyn RouteRepository>,
|
||||
pub route_table: Arc<RouteTable>,
|
||||
/// §4.5 M5: reparenting moves a subtree, changing which ancestor-group
|
||||
/// stateful templates its apps inherit — their materialized cron/queue
|
||||
/// copies are reconciled after a reparent.
|
||||
pub pool: sqlx::PgPool,
|
||||
}
|
||||
|
||||
pub fn groups_router(state: GroupsState) -> Router {
|
||||
@@ -295,6 +299,11 @@ async fn reparent_group(
|
||||
{
|
||||
tracing::warn!(error = %e, "groups: route table refresh after reparent failed; it will self-heal");
|
||||
}
|
||||
// §4.5 M5: the moved subtree inherits a different set of ancestor-group
|
||||
// stateful templates — reconcile its materialized copies.
|
||||
if let Err(e) = crate::materialize::rematerialize_stateful_templates(&s.pool).await {
|
||||
tracing::warn!(error = %e, "groups: stateful-template materialization after reparent failed; it will self-heal");
|
||||
}
|
||||
Ok(Json(moved))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user