feat(suppress): owner-polymorphic suppression repo + reconcile (M1.2)
suppression_repo takes a ScriptOwner (app or group), binding the matching owner column. apply_service load_current / create / prune / suppression_report all thread owner.as_script_owner(); the validate_bundle_for group-suppress rejection and the manifest parse guard are dropped — a [group] may now declare [suppress] to decline a template it inherits from a higher ancestor. No runtime consumption effect yet (the chain filters land in M1.3/M1.4). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -73,16 +73,12 @@ impl Manifest {
|
||||
}
|
||||
// A group node owns scripts + vars (+ secret names) and, as of §11 tail,
|
||||
// ROUTE TEMPLATES + EVENT trigger TEMPLATES (kv/docs/files/pubsub) that
|
||||
// fan out live to descendant apps. The stateful trigger kinds
|
||||
// fan out live to descendant apps. As of §11 tail M1 a group may also
|
||||
// declare `[suppress]` to decline a template it inherits from a higher
|
||||
// ancestor, for its whole subtree. The stateful trigger kinds
|
||||
// (cron/queue/email) stay app-only — they need per-app state/secrets →
|
||||
// materialization.
|
||||
if m.group.is_some() {
|
||||
if !m.suppress.is_empty() {
|
||||
anyhow::bail!(
|
||||
"a [group] cannot declare [suppress] — only an app opts out of \
|
||||
inherited templates; a group simply doesn't declare the template"
|
||||
);
|
||||
}
|
||||
let t = &m.triggers;
|
||||
if !t.cron.is_empty() || !t.queue.is_empty() || !t.email.is_empty() {
|
||||
anyhow::bail!(
|
||||
@@ -808,7 +804,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_suppress_parses_group_suppress_rejected() {
|
||||
fn app_and_group_suppress_parse() {
|
||||
// §11 tail: an [app] declares [suppress] to opt out of inherited
|
||||
// templates — script names (triggers) + paths (routes).
|
||||
let m = Manifest::parse(
|
||||
@@ -819,13 +815,14 @@ mod tests {
|
||||
assert_eq!(m.suppress.triggers, ["audit"]);
|
||||
assert_eq!(m.suppress.routes, ["/hello"]);
|
||||
|
||||
// A [group] cannot suppress — it simply wouldn't declare the template.
|
||||
let err = Manifest::parse(
|
||||
// §11 tail M1: a [group] MAY suppress — it declines a template it
|
||||
// inherits from a higher ancestor, for its whole subtree.
|
||||
let g = Manifest::parse(
|
||||
"[group]\nslug = \"acme\"\nname = \"ACME\"\n\n\
|
||||
[suppress]\ntriggers = [\"audit\"]\n",
|
||||
)
|
||||
.expect_err("group [suppress] is rejected");
|
||||
assert!(err.to_string().contains("suppress"), "got: {err}");
|
||||
.expect("group [suppress] must parse");
|
||||
assert_eq!(g.suppress.triggers, ["audit"]);
|
||||
|
||||
// An unknown key inside [suppress] is a hard error (deny_unknown_fields).
|
||||
let typo = "[app]\nslug = \"x\"\nname = \"X\"\n\n[suppress]\ntrigger = [\"a\"]\n";
|
||||
|
||||
Reference in New Issue
Block a user