feat(stateful-templates): schema + dispatch guards + allow cron/queue on group (M5.1)
0062 adds `materialized_from` on triggers (a managed app-owned copy links back to its group template; CASCADE). The scheduler + queue-consumer queries gain `AND t.app_id IS NOT NULL` so a group-owned stateful TEMPLATE is never dispatched directly — only the per-descendant materialized app rows are. validate_bundle_for + manifest parse now allow cron/queue on a group (email stays rejected pending its per-app inbound-secret handling in M5.5). The materialization reconcile that expands templates into app rows lands in M5.2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -756,22 +756,16 @@ impl ApplyService {
|
||||
if matches!(owner, ApplyOwner::Group(_)) {
|
||||
// §11 tail: a group MAY declare ROUTE TEMPLATES (validated as binding
|
||||
// a group-owned endpoint by `validate_bundle` via the inherited
|
||||
// targets) and TRIGGER TEMPLATES — but trigger templates are limited
|
||||
// to the stateless EVENT kinds (kv/docs/files/pubsub), which resolve
|
||||
// live via the chain union at dispatch. cron/queue/email carry
|
||||
// per-app state (last_fired_at, the one-consumer lock, a sealed
|
||||
// secret) and would need materialization; reject them, deferred.
|
||||
// targets) and TRIGGER TEMPLATES. The stateless EVENT kinds
|
||||
// (kv/docs/files/pubsub) resolve live via the chain union at
|
||||
// dispatch; §4.5 M5 the stateful CRON + QUEUE kinds are allowed and
|
||||
// MATERIALIZED into a per-descendant-app row. Only `email` (a per-app
|
||||
// sealed inbound secret) is still rejected on a group.
|
||||
for t in &bundle.triggers {
|
||||
if !matches!(
|
||||
t,
|
||||
BundleTrigger::Kv { .. }
|
||||
| BundleTrigger::Docs { .. }
|
||||
| BundleTrigger::Files { .. }
|
||||
| BundleTrigger::Pubsub { .. }
|
||||
) {
|
||||
if matches!(t, BundleTrigger::Email { .. }) {
|
||||
return Err(ApplyError::Invalid(
|
||||
"a group trigger template must be an event kind \
|
||||
(kv, docs, files, pubsub); cron/queue/email are app-only"
|
||||
"a group email trigger template is not yet supported \
|
||||
(per-app inbound secret); cron/queue/event kinds are allowed"
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ async fn tick(pool: &PgPool, now: DateTime<Utc>) -> Result<usize, sqlx::Error> {
|
||||
d.schedule, d.timezone, d.last_fired_at \
|
||||
FROM cron_trigger_details d \
|
||||
JOIN triggers t ON t.id = d.trigger_id \
|
||||
WHERE t.enabled = TRUE \
|
||||
WHERE t.enabled = TRUE AND t.app_id IS NOT NULL \
|
||||
FOR UPDATE OF d SKIP LOCKED",
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
|
||||
@@ -1769,7 +1769,8 @@ impl TriggerRepo for PostgresTriggerRepo {
|
||||
FROM triggers t \
|
||||
JOIN queue_trigger_details d ON d.trigger_id = t.id \
|
||||
JOIN scripts s ON s.id = t.script_id \
|
||||
WHERE t.kind = 'queue' AND t.enabled = TRUE AND s.enabled = TRUE",
|
||||
WHERE t.kind = 'queue' AND t.enabled = TRUE AND s.enabled = TRUE \
|
||||
AND t.app_id IS NOT NULL",
|
||||
)
|
||||
.fetch_all(&self.pool)
|
||||
.await?;
|
||||
|
||||
Reference in New Issue
Block a user