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:
@@ -1420,6 +1420,11 @@ impl ApplyService {
|
||||
.push("route table refresh failed; it will self-heal".into());
|
||||
}
|
||||
}
|
||||
// §4.5 M5: re-materialize stateful (cron/queue) group templates into
|
||||
// per-descendant app rows. Post-commit + best-effort like the route
|
||||
// rebuild; per-app skips (e.g. a queue slot already taken) surface as
|
||||
// warnings.
|
||||
report.warnings.extend(self.rematerialize().await);
|
||||
Ok(report)
|
||||
}
|
||||
|
||||
@@ -1559,6 +1564,8 @@ impl ApplyService {
|
||||
.warnings
|
||||
.push("route table refresh failed; it will self-heal".into());
|
||||
}
|
||||
// §4.5 M5: one post-commit materialization pass covers the whole tree.
|
||||
report.warnings.extend(self.rematerialize().await);
|
||||
Ok(report)
|
||||
}
|
||||
|
||||
@@ -1851,6 +1858,19 @@ impl ApplyService {
|
||||
.map_err(|e| ApplyError::Backend(e.to_string()))
|
||||
}
|
||||
|
||||
/// §4.5 M5: reconcile materialized stateful-template copies. Best-effort —
|
||||
/// a failure is logged (the copies self-heal on the next mutation) and any
|
||||
/// per-app skip warnings are returned for the apply report.
|
||||
async fn rematerialize(&self) -> Vec<String> {
|
||||
match crate::materialize::rematerialize_stateful_templates(&self.pool).await {
|
||||
Ok(warnings) => warnings,
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %e, "apply: stateful-template materialization failed");
|
||||
vec!["stateful-template materialization failed; it will self-heal".into()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a referenced secret to plaintext, then re-seal it for an
|
||||
/// email trigger's inbound-secret column. The value never appears in
|
||||
/// the manifest — only the secret's name does.
|
||||
|
||||
Reference in New Issue
Block a user