From 8fb1261a9679b4fb0b55ad8d3f193ae8ffe483f0 Mon Sep 17 00:00:00 2001 From: Konrad Heimel Date: Sun, 16 Aug 2026 11:42:01 +0200 Subject: [PATCH 1/2] :sparkles: feat(examples): close REF-EX C1-C4 in topic-registry/service-catalog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EX-S06 — extend the S02/S03 topic-registry and service-catalog packs with four generalized reference-derived patterns, each proven both-polarity via a dedicated assent test directory: - C1 list-no-shrink (topic-registry): identity-keyed `consumers` map; a key DELETE requires review (reuses the non-destructive obligation), a key ADD proves silently. Pointer-scoped to /*/consumers/*, vacuous on every S02 golden (none declare consumers). - C2 privilege-tier allow-list (service-catalog): /services/*/tier must be in [1,2,3]; an out-of-range value (e.g. 0) fails on top of the existing only-safe-fields escalation. Reuses the allowed-fields obligation; no unkeyed nested list reintroduced (D-061). - C3 wildcard-grant block (topic-registry): nested acl.grants map keyed by principal; a grant keyed by the literal wildcard "*" BLOCKs, an explicit named principal does not. New `wildcard-grant` obligation added to bindings.yaml require: (safe — coverage marks an obligation covered the instant an enforce rule names it, independent of whether any subject matches). - C4 soft-delete-as-field-add (topic-registry): adding `status: retired` (a field base lacked) requires review instead of silently approving; adding any other status value proves silently. An explicit edge case proves an unrelated field modify does not fire the rule. Verified: `assent test --coverage` green on both packs (8/8 rules covered in both polarities each); all pre-existing S02/S03 goldens unchanged and green; empirically confirmed REQ-EX-S06-05 by deleting each new rule file and observing the pack gate go red (lint for the required wildcard-grant obligation, --coverage for the reused-obligation rules). --- CHANGELOG.md | 1 + .../packs/catalog/rules/privilege-tier.yaml | 33 +++++++++++++ .../base/catalog/prod/core-services.json | 47 +++++++++++++++++++ .../tests/catalog/privilege-tier/expect.yaml | 9 ++++ .../tests/catalog/privilege-tier/facts.yaml | 7 +++ .../head/catalog/prod/core-services.json | 47 +++++++++++++++++++ .../base/catalog/prod/core-services.json | 47 +++++++++++++++++++ .../privilege-tier/negative/expect.yaml | 11 +++++ .../privilege-tier/negative/facts.yaml | 7 +++ .../head/catalog/prod/core-services.json | 47 +++++++++++++++++++ .../topic-registry/.assent/bindings.yaml | 4 +- .../packs/topics/rules/list-no-shrink.yaml | 32 +++++++++++++ .../packs/topics/rules/soft-delete.yaml | 32 +++++++++++++ .../packs/topics/rules/wildcard-grant.yaml | 36 ++++++++++++++ .../topics/prod/notifications.events.v1.yaml | 19 ++++++++ .../tests/topics/list-no-shrink/expect.yaml | 3 ++ .../tests/topics/list-no-shrink/facts.yaml | 7 +++ .../topics/prod/notifications.events.v1.yaml | 20 ++++++++ .../topics/prod/notifications.events.v1.yaml | 19 ++++++++ .../list-no-shrink/negative/expect.yaml | 6 +++ .../topics/list-no-shrink/negative/facts.yaml | 7 +++ .../topics/prod/notifications.events.v1.yaml | 18 +++++++ .../topics/prod/inventory.updates.v1.yaml | 16 +++++++ .../tests/topics/soft-delete/expect.yaml | 3 ++ .../tests/topics/soft-delete/facts.yaml | 7 +++ .../topics/prod/inventory.updates.v1.yaml | 17 +++++++ .../topics/prod/inventory.updates.v1.yaml | 16 +++++++ .../topics/soft-delete/negative/expect.yaml | 6 +++ .../topics/soft-delete/negative/facts.yaml | 7 +++ .../topics/prod/inventory.updates.v1.yaml | 17 +++++++ .../topics/prod/inventory.updates.v1.yaml | 17 +++++++ .../soft-delete/unrelated-modify/expect.yaml | 6 +++ .../soft-delete/unrelated-modify/facts.yaml | 7 +++ .../topics/prod/inventory.updates.v1.yaml | 17 +++++++ .../base/topics/prod/billing.invoices.v1.yaml | 19 ++++++++ .../tests/topics/wildcard-grant/expect.yaml | 3 ++ .../tests/topics/wildcard-grant/facts.yaml | 7 +++ .../head/topics/prod/billing.invoices.v1.yaml | 20 ++++++++ .../base/topics/prod/billing.invoices.v1.yaml | 19 ++++++++ .../wildcard-grant/negative/expect.yaml | 6 +++ .../topics/wildcard-grant/negative/facts.yaml | 7 +++ .../head/topics/prod/billing.invoices.v1.yaml | 20 ++++++++ 42 files changed, 694 insertions(+), 2 deletions(-) create mode 100644 examples/packs/service-catalog/.assent/packs/catalog/rules/privilege-tier.yaml create mode 100644 examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/base/catalog/prod/core-services.json create mode 100644 examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/expect.yaml create mode 100644 examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/facts.yaml create mode 100644 examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/head/catalog/prod/core-services.json create mode 100644 examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/base/catalog/prod/core-services.json create mode 100644 examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/expect.yaml create mode 100644 examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/facts.yaml create mode 100644 examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/head/catalog/prod/core-services.json create mode 100644 examples/packs/topic-registry/.assent/packs/topics/rules/list-no-shrink.yaml create mode 100644 examples/packs/topic-registry/.assent/packs/topics/rules/soft-delete.yaml create mode 100644 examples/packs/topic-registry/.assent/packs/topics/rules/wildcard-grant.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/base/topics/prod/notifications.events.v1.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/expect.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/facts.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/head/topics/prod/notifications.events.v1.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/negative/base/topics/prod/notifications.events.v1.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/negative/expect.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/negative/facts.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/negative/head/topics/prod/notifications.events.v1.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/soft-delete/base/topics/prod/inventory.updates.v1.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/soft-delete/expect.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/soft-delete/facts.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/soft-delete/head/topics/prod/inventory.updates.v1.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/soft-delete/negative/base/topics/prod/inventory.updates.v1.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/soft-delete/negative/expect.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/soft-delete/negative/facts.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/soft-delete/negative/head/topics/prod/inventory.updates.v1.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/soft-delete/unrelated-modify/base/topics/prod/inventory.updates.v1.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/soft-delete/unrelated-modify/expect.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/soft-delete/unrelated-modify/facts.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/soft-delete/unrelated-modify/head/topics/prod/inventory.updates.v1.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/base/topics/prod/billing.invoices.v1.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/expect.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/facts.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/head/topics/prod/billing.invoices.v1.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/negative/base/topics/prod/billing.invoices.v1.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/negative/expect.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/negative/facts.yaml create mode 100644 examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/negative/head/topics/prod/billing.invoices.v1.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index dbee3e98..a624f2ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -119,6 +119,7 @@ repository still gets a decision, never by following the link; no release carrie - :sparkles: feat(examples): thicken service-catalog with nested sla/runtime objects - :sparkles: feat(examples): thicken topic-registry nested YAML and nested-pointer rules - :sparkles: feat(examples): thicken infra-vars nested tfvars maps (EX-S04) +- :sparkles: feat(examples): close REF-EX C1-C4 in topic-registry/service-catalog ### Fixes - :bug: fix(ci): pin ci-audit-test in the AUD-S18 check-stage list diff --git a/examples/packs/service-catalog/.assent/packs/catalog/rules/privilege-tier.yaml b/examples/packs/service-catalog/.assent/packs/catalog/rules/privilege-tier.yaml new file mode 100644 index 00000000..df315408 --- /dev/null +++ b/examples/packs/service-catalog/.assent/packs/catalog/rules/privilege-tier.yaml @@ -0,0 +1,33 @@ +# Privilege-tier allow-list (REF-EX C2) — /services/*/tier already exists on every +# service entry and is already caught by only-safe-fields (any non-/oncall field +# change escalates to require-review, allowed-fields.yaml). This rule adds a +# NARROWER, value-level check on top of that coarse field-level one: a tier value +# outside the allow-list (e.g. 0, or any value not in the declared band) is a +# privilege-escalation attempt, not just an "other field changed" event. Reuses +# the allowed-fields obligation (already required) so no bindings.yaml change is +# needed. D-061: this stays a scalar leaf check on the already-governed keyed-list +# entry field — no unkeyed nested list is reintroduced. +apiVersion: assent.dev/v1alpha1 +kind: MergePolicy +metadata: + name: catalog-privilege-tier +spec: + entries: + catalog-service: + mode: list + root: /services + identity: + pointer: /name + rules: + - name: tier-allow-list + phase: enforce + match: + valueChanges: + pointers: ["/services/*/tier"] + kinds: [modify] + prove: + obligation: allowed-fields + when: "new in [1, 2, 3]" + onFailure: + effect: require-review + code: allowed-fields.tier-out-of-range diff --git a/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/base/catalog/prod/core-services.json b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/base/catalog/prod/core-services.json new file mode 100644 index 00000000..a22f42c7 --- /dev/null +++ b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/base/catalog/prod/core-services.json @@ -0,0 +1,47 @@ +{ + "apiVersion": "catalog/v1", + "services": [ + { + "name": "orders-api", + "owner": "orders-team", + "tier": 1, + "oncall": "orders-rotation", + "sla": { + "slo_percent": 99.9, + "window": "30d" + }, + "runtime": { + "language": "go", + "replicas": 3 + } + }, + { + "name": "payments-gateway", + "owner": "payments-team", + "tier": 1, + "oncall": "payments-rotation", + "sla": { + "slo_percent": 99.9, + "window": "30d" + }, + "runtime": { + "language": "go", + "replicas": 3 + } + }, + { + "name": "storefront-web", + "owner": "storefront-team", + "tier": 2, + "oncall": "storefront-rotation", + "sla": { + "slo_percent": 99.9, + "window": "30d" + }, + "runtime": { + "language": "go", + "replicas": 3 + } + } + ] +} diff --git a/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/expect.yaml b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/expect.yaml new file mode 100644 index 00000000..8f7245c3 --- /dev/null +++ b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/expect.yaml @@ -0,0 +1,9 @@ +# tier 1 -> 3 is within the allow-list ([1,2,3]) -> tier-allow-list proves silently +# (its proving polarity). The pre-existing only-safe-fields rule still escalates +# ANY non-/oncall field change to require-review regardless of value — that is +# unrelated coarse-grained coverage, not this rule's finding. +decision: REVIEW +findings: + - rule: only-safe-fields + obligation: allowed-fields + effect: require-review diff --git a/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/facts.yaml b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/facts.yaml new file mode 100644 index 00000000..940a17ab --- /dev/null +++ b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/facts.yaml @@ -0,0 +1,7 @@ +author: + login: alice + groups: [orders-team] +oncall: + orders_rotation: "ready" +schema: + valid: true diff --git a/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/head/catalog/prod/core-services.json b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/head/catalog/prod/core-services.json new file mode 100644 index 00000000..b18e9fb5 --- /dev/null +++ b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/head/catalog/prod/core-services.json @@ -0,0 +1,47 @@ +{ + "apiVersion": "catalog/v1", + "services": [ + { + "name": "orders-api", + "owner": "orders-team", + "tier": 3, + "oncall": "orders-rotation", + "sla": { + "slo_percent": 99.9, + "window": "30d" + }, + "runtime": { + "language": "go", + "replicas": 3 + } + }, + { + "name": "payments-gateway", + "owner": "payments-team", + "tier": 1, + "oncall": "payments-rotation", + "sla": { + "slo_percent": 99.9, + "window": "30d" + }, + "runtime": { + "language": "go", + "replicas": 3 + } + }, + { + "name": "storefront-web", + "owner": "storefront-team", + "tier": 2, + "oncall": "storefront-rotation", + "sla": { + "slo_percent": 99.9, + "window": "30d" + }, + "runtime": { + "language": "go", + "replicas": 3 + } + } + ] +} diff --git a/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/base/catalog/prod/core-services.json b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/base/catalog/prod/core-services.json new file mode 100644 index 00000000..a22f42c7 --- /dev/null +++ b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/base/catalog/prod/core-services.json @@ -0,0 +1,47 @@ +{ + "apiVersion": "catalog/v1", + "services": [ + { + "name": "orders-api", + "owner": "orders-team", + "tier": 1, + "oncall": "orders-rotation", + "sla": { + "slo_percent": 99.9, + "window": "30d" + }, + "runtime": { + "language": "go", + "replicas": 3 + } + }, + { + "name": "payments-gateway", + "owner": "payments-team", + "tier": 1, + "oncall": "payments-rotation", + "sla": { + "slo_percent": 99.9, + "window": "30d" + }, + "runtime": { + "language": "go", + "replicas": 3 + } + }, + { + "name": "storefront-web", + "owner": "storefront-team", + "tier": 2, + "oncall": "storefront-rotation", + "sla": { + "slo_percent": 99.9, + "window": "30d" + }, + "runtime": { + "language": "go", + "replicas": 3 + } + } + ] +} diff --git a/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/expect.yaml b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/expect.yaml new file mode 100644 index 00000000..d570713c --- /dev/null +++ b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/expect.yaml @@ -0,0 +1,11 @@ +# tier 1 -> 0 is outside the allow-list ([1,2,3]) -> tier-allow-list fails, on top +# of the coarse-grained only-safe-fields escalation (both effects are +# require-review, so the decision stays REVIEW either way). +decision: REVIEW +findings: + - rule: only-safe-fields + obligation: allowed-fields + effect: require-review + - rule: tier-allow-list + obligation: allowed-fields + effect: require-review diff --git a/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/facts.yaml b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/facts.yaml new file mode 100644 index 00000000..940a17ab --- /dev/null +++ b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/facts.yaml @@ -0,0 +1,7 @@ +author: + login: alice + groups: [orders-team] +oncall: + orders_rotation: "ready" +schema: + valid: true diff --git a/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/head/catalog/prod/core-services.json b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/head/catalog/prod/core-services.json new file mode 100644 index 00000000..954b75b6 --- /dev/null +++ b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/head/catalog/prod/core-services.json @@ -0,0 +1,47 @@ +{ + "apiVersion": "catalog/v1", + "services": [ + { + "name": "orders-api", + "owner": "orders-team", + "tier": 0, + "oncall": "orders-rotation", + "sla": { + "slo_percent": 99.9, + "window": "30d" + }, + "runtime": { + "language": "go", + "replicas": 3 + } + }, + { + "name": "payments-gateway", + "owner": "payments-team", + "tier": 1, + "oncall": "payments-rotation", + "sla": { + "slo_percent": 99.9, + "window": "30d" + }, + "runtime": { + "language": "go", + "replicas": 3 + } + }, + { + "name": "storefront-web", + "owner": "storefront-team", + "tier": 2, + "oncall": "storefront-rotation", + "sla": { + "slo_percent": 99.9, + "window": "30d" + }, + "runtime": { + "language": "go", + "replicas": 3 + } + } + ] +} diff --git a/examples/packs/topic-registry/.assent/bindings.yaml b/examples/packs/topic-registry/.assent/bindings.yaml index b27edcfc..36bd58d3 100644 --- a/examples/packs/topic-registry/.assent/bindings.yaml +++ b/examples/packs/topic-registry/.assent/bindings.yaml @@ -5,9 +5,9 @@ bindings: environment: dev packs: [topics] risk: { threshold: 10 } - require: [ownership, bounded-change, non-destructive, schema-valid] + require: [ownership, bounded-change, non-destructive, schema-valid, wildcard-grant] - class: kafka-topic environment: prod packs: [topics] risk: { threshold: 4 } - require: [ownership, bounded-change, non-destructive, schema-valid] + require: [ownership, bounded-change, non-destructive, schema-valid, wildcard-grant] diff --git a/examples/packs/topic-registry/.assent/packs/topics/rules/list-no-shrink.yaml b/examples/packs/topic-registry/.assent/packs/topics/rules/list-no-shrink.yaml new file mode 100644 index 00000000..e8bc9976 --- /dev/null +++ b/examples/packs/topic-registry/.assent/packs/topics/rules/list-no-shrink.yaml @@ -0,0 +1,32 @@ +# List-no-shrink (REF-EX C1) — identity-keyed `consumers` map on a topic entry. +# consumers is keyed by consumer name (not an unkeyed list, ADR-0017 §5), so a +# per-key add/delete is a genuine one-sided scalar change the differ can represent +# (E1-S05: a one-sided ADD of a whole new mapping is opaque, but consumers already +# exists as a map on every topic that declares it — only the LEAF key is one-sided). +# Reuses the no-destruction pattern (non-destructive obligation, cf. +# non-destructive.yaml): a consumer key ADD proves (kind != "delete"), a consumer +# key DELETE fails -> require-review. Pointer-scoped to /*/consumers/* so this rule +# is silent (vacuous, no match at all) on every topic file that never declares +# `consumers` — the S02 goldens stay green untouched. +apiVersion: assent.dev/v1alpha1 +kind: MergePolicy +metadata: + name: topics-list-no-shrink +spec: + entries: + kafka-topic: + mode: map + root: "" + rules: + - name: consumer-removal + phase: enforce + match: + valueChanges: + pointers: ["/*/consumers/*"] + kinds: [add, delete] + prove: + obligation: non-destructive + when: 'kind != "delete"' + onFailure: + effect: require-review + code: destruction.consumer-removed diff --git a/examples/packs/topic-registry/.assent/packs/topics/rules/soft-delete.yaml b/examples/packs/topic-registry/.assent/packs/topics/rules/soft-delete.yaml new file mode 100644 index 00000000..887c7964 --- /dev/null +++ b/examples/packs/topic-registry/.assent/packs/topics/rules/soft-delete.yaml @@ -0,0 +1,32 @@ +# Soft-delete-as-field-add (REF-EX C4) — a topic adding `status: retired` (a field +# base entirely lacked) is a destructive lifecycle transition disguised as an +# innocuous field add, not a silent-APPROVE metadata edit. Reuses the +# non-destructive obligation (cf. non-destructive.yaml / list-no-shrink.yaml): +# matched ONLY on kind add at the dedicated /*/status pointer, so a modify of any +# OTHER field (retention_hours, description, ...) never fires this rule even when +# status is untouched (Edge case in REQ-EX-S06-04) — proven by every existing S02 +# golden, none of which declare `status` at all (vacuous, no match). Both +# polarities exercise the SAME add-kind match: adding status as anything other +# than "retired" proves silently; adding it as exactly "retired" fails. +apiVersion: assent.dev/v1alpha1 +kind: MergePolicy +metadata: + name: topics-soft-delete +spec: + entries: + kafka-topic: + mode: map + root: "" + rules: + - name: status-retired-add + phase: enforce + match: + valueChanges: + pointers: ["/*/status"] + kinds: [add] + prove: + obligation: non-destructive + when: 'new != "retired"' + onFailure: + effect: require-review + code: destruction.soft-delete diff --git a/examples/packs/topic-registry/.assent/packs/topics/rules/wildcard-grant.yaml b/examples/packs/topic-registry/.assent/packs/topics/rules/wildcard-grant.yaml new file mode 100644 index 00000000..7543ab7e --- /dev/null +++ b/examples/packs/topic-registry/.assent/packs/topics/rules/wildcard-grant.yaml @@ -0,0 +1,36 @@ +# Wildcard-grant block (REF-EX C3) — nested acl.grants map, keyed by principal. +# A grant keyed by the literal wildcard principal "*" (i.e. "any principal may act +# on this topic") is a security escalation disguised as an ordinary ACL entry -> +# BLOCK, not a soft require-review. An explicit, named principal key does not +# block. `path` carries the full JSON Pointer of the matched change (mirrors +# allowed-fields.yaml's path.endsWith usage), so the last pointer segment IS the +# principal key; a wildcard grant's pointer always ends in the literal "/*" +# segment. Pointer-scoped to /*/acl/grants/* so this rule never matches (vacuous) +# on a topic that has no acl.grants at all — the S02 goldens stay green. A +# brand-new obligation (`wildcard-grant`, added to bindings.yaml require:) is safe +# to require: coverage marks an obligation "covered" the instant an enforce rule +# names it, independent of whether any subject actually matches it (E2-S04 +# uncovered-obligation guard), so every existing golden — none of which declare +# acl — stays vacuously satisfied. +apiVersion: assent.dev/v1alpha1 +kind: MergePolicy +metadata: + name: topics-wildcard-grant +spec: + entries: + kafka-topic: + mode: map + root: "" + rules: + - name: wildcard-grant-block + phase: enforce + match: + valueChanges: + pointers: ["/*/acl/grants/*"] + kinds: [add, modify] + prove: + obligation: wildcard-grant + when: '!path.endsWith("/*")' + onFailure: + effect: block + code: acl.wildcard-grant diff --git a/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/base/topics/prod/notifications.events.v1.yaml b/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/base/topics/prod/notifications.events.v1.yaml new file mode 100644 index 00000000..c60de974 --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/base/topics/prod/notifications.events.v1.yaml @@ -0,0 +1,19 @@ +notifications.events.v1: + owner: growth-team + partitions: 8 + replication_factor: 3 + retention_hours: 168 + cleanup_policy: delete + schema: + format: avro + subject: notifications.events.v1-value + compatibility: BACKWARD + references: + notifications.common: 1 + compaction: + strategy: delete + description: > + Notification dispatch events consumed by downstream channels. + consumers: + email-service: reader + sms-service: reader diff --git a/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/expect.yaml b/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/expect.yaml new file mode 100644 index 00000000..4b400da2 --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/expect.yaml @@ -0,0 +1,3 @@ +# consumers key ADD (push-service) -> kind != "delete" -> consumer-removal proves silently. +decision: APPROVE +findings: [] diff --git a/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/facts.yaml b/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/facts.yaml new file mode 100644 index 00000000..9f7007bf --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/facts.yaml @@ -0,0 +1,7 @@ +author: + login: alice + groups: [growth-team] +quota: + max_partitions: 24 +schema: + valid: true diff --git a/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/head/topics/prod/notifications.events.v1.yaml b/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/head/topics/prod/notifications.events.v1.yaml new file mode 100644 index 00000000..4c9ae8da --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/head/topics/prod/notifications.events.v1.yaml @@ -0,0 +1,20 @@ +notifications.events.v1: + owner: growth-team + partitions: 8 + replication_factor: 3 + retention_hours: 168 + cleanup_policy: delete + schema: + format: avro + subject: notifications.events.v1-value + compatibility: BACKWARD + references: + notifications.common: 1 + compaction: + strategy: delete + description: > + Notification dispatch events consumed by downstream channels. + consumers: + email-service: reader + sms-service: reader + push-service: reader diff --git a/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/negative/base/topics/prod/notifications.events.v1.yaml b/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/negative/base/topics/prod/notifications.events.v1.yaml new file mode 100644 index 00000000..c60de974 --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/negative/base/topics/prod/notifications.events.v1.yaml @@ -0,0 +1,19 @@ +notifications.events.v1: + owner: growth-team + partitions: 8 + replication_factor: 3 + retention_hours: 168 + cleanup_policy: delete + schema: + format: avro + subject: notifications.events.v1-value + compatibility: BACKWARD + references: + notifications.common: 1 + compaction: + strategy: delete + description: > + Notification dispatch events consumed by downstream channels. + consumers: + email-service: reader + sms-service: reader diff --git a/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/negative/expect.yaml b/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/negative/expect.yaml new file mode 100644 index 00000000..1fe5ee12 --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/negative/expect.yaml @@ -0,0 +1,6 @@ +# consumers key DELETE (sms-service removed) -> consumer-removal fails -> REVIEW. +decision: REVIEW +findings: + - rule: consumer-removal + obligation: non-destructive + effect: require-review diff --git a/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/negative/facts.yaml b/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/negative/facts.yaml new file mode 100644 index 00000000..9f7007bf --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/negative/facts.yaml @@ -0,0 +1,7 @@ +author: + login: alice + groups: [growth-team] +quota: + max_partitions: 24 +schema: + valid: true diff --git a/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/negative/head/topics/prod/notifications.events.v1.yaml b/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/negative/head/topics/prod/notifications.events.v1.yaml new file mode 100644 index 00000000..cc7dd76f --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/list-no-shrink/negative/head/topics/prod/notifications.events.v1.yaml @@ -0,0 +1,18 @@ +notifications.events.v1: + owner: growth-team + partitions: 8 + replication_factor: 3 + retention_hours: 168 + cleanup_policy: delete + schema: + format: avro + subject: notifications.events.v1-value + compatibility: BACKWARD + references: + notifications.common: 1 + compaction: + strategy: delete + description: > + Notification dispatch events consumed by downstream channels. + consumers: + email-service: reader diff --git a/examples/packs/topic-registry/.assent/tests/topics/soft-delete/base/topics/prod/inventory.updates.v1.yaml b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/base/topics/prod/inventory.updates.v1.yaml new file mode 100644 index 00000000..e681d0a2 --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/base/topics/prod/inventory.updates.v1.yaml @@ -0,0 +1,16 @@ +inventory.updates.v1: + owner: inventory-team + partitions: 4 + replication_factor: 3 + retention_hours: 168 + cleanup_policy: delete + schema: + format: avro + subject: inventory.updates.v1-value + compatibility: BACKWARD + references: + inventory.common: 1 + compaction: + strategy: delete + description: > + Inventory level change events for the warehouse domain. diff --git a/examples/packs/topic-registry/.assent/tests/topics/soft-delete/expect.yaml b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/expect.yaml new file mode 100644 index 00000000..167cf2a7 --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/expect.yaml @@ -0,0 +1,3 @@ +# status ADD (base lacked it) as "active" (not "retired") -> status-retired-add proves silently. +decision: APPROVE +findings: [] diff --git a/examples/packs/topic-registry/.assent/tests/topics/soft-delete/facts.yaml b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/facts.yaml new file mode 100644 index 00000000..21c3fc57 --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/facts.yaml @@ -0,0 +1,7 @@ +author: + login: alice + groups: [inventory-team] +quota: + max_partitions: 24 +schema: + valid: true diff --git a/examples/packs/topic-registry/.assent/tests/topics/soft-delete/head/topics/prod/inventory.updates.v1.yaml b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/head/topics/prod/inventory.updates.v1.yaml new file mode 100644 index 00000000..2a9d48fe --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/head/topics/prod/inventory.updates.v1.yaml @@ -0,0 +1,17 @@ +inventory.updates.v1: + owner: inventory-team + partitions: 4 + replication_factor: 3 + retention_hours: 168 + cleanup_policy: delete + schema: + format: avro + subject: inventory.updates.v1-value + compatibility: BACKWARD + references: + inventory.common: 1 + compaction: + strategy: delete + description: > + Inventory level change events for the warehouse domain. + status: active diff --git a/examples/packs/topic-registry/.assent/tests/topics/soft-delete/negative/base/topics/prod/inventory.updates.v1.yaml b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/negative/base/topics/prod/inventory.updates.v1.yaml new file mode 100644 index 00000000..e681d0a2 --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/negative/base/topics/prod/inventory.updates.v1.yaml @@ -0,0 +1,16 @@ +inventory.updates.v1: + owner: inventory-team + partitions: 4 + replication_factor: 3 + retention_hours: 168 + cleanup_policy: delete + schema: + format: avro + subject: inventory.updates.v1-value + compatibility: BACKWARD + references: + inventory.common: 1 + compaction: + strategy: delete + description: > + Inventory level change events for the warehouse domain. diff --git a/examples/packs/topic-registry/.assent/tests/topics/soft-delete/negative/expect.yaml b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/negative/expect.yaml new file mode 100644 index 00000000..14ccb5e6 --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/negative/expect.yaml @@ -0,0 +1,6 @@ +# status ADD as exactly "retired" (base lacked it) -> soft-delete-as-field-add, not silent APPROVE. +decision: REVIEW +findings: + - rule: status-retired-add + obligation: non-destructive + effect: require-review diff --git a/examples/packs/topic-registry/.assent/tests/topics/soft-delete/negative/facts.yaml b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/negative/facts.yaml new file mode 100644 index 00000000..21c3fc57 --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/negative/facts.yaml @@ -0,0 +1,7 @@ +author: + login: alice + groups: [inventory-team] +quota: + max_partitions: 24 +schema: + valid: true diff --git a/examples/packs/topic-registry/.assent/tests/topics/soft-delete/negative/head/topics/prod/inventory.updates.v1.yaml b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/negative/head/topics/prod/inventory.updates.v1.yaml new file mode 100644 index 00000000..d5862c7e --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/negative/head/topics/prod/inventory.updates.v1.yaml @@ -0,0 +1,17 @@ +inventory.updates.v1: + owner: inventory-team + partitions: 4 + replication_factor: 3 + retention_hours: 168 + cleanup_policy: delete + schema: + format: avro + subject: inventory.updates.v1-value + compatibility: BACKWARD + references: + inventory.common: 1 + compaction: + strategy: delete + description: > + Inventory level change events for the warehouse domain. + status: retired diff --git a/examples/packs/topic-registry/.assent/tests/topics/soft-delete/unrelated-modify/base/topics/prod/inventory.updates.v1.yaml b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/unrelated-modify/base/topics/prod/inventory.updates.v1.yaml new file mode 100644 index 00000000..2a9d48fe --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/unrelated-modify/base/topics/prod/inventory.updates.v1.yaml @@ -0,0 +1,17 @@ +inventory.updates.v1: + owner: inventory-team + partitions: 4 + replication_factor: 3 + retention_hours: 168 + cleanup_policy: delete + schema: + format: avro + subject: inventory.updates.v1-value + compatibility: BACKWARD + references: + inventory.common: 1 + compaction: + strategy: delete + description: > + Inventory level change events for the warehouse domain. + status: active diff --git a/examples/packs/topic-registry/.assent/tests/topics/soft-delete/unrelated-modify/expect.yaml b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/unrelated-modify/expect.yaml new file mode 100644 index 00000000..6384283d --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/unrelated-modify/expect.yaml @@ -0,0 +1,6 @@ +# Edge (REQ-EX-S06-04): retention_hours modify with status already present and +# UNCHANGED ("active") must not fire status-retired-add — the rule is scoped to +# the dedicated /*/status ADD pointer only, never a general field-add trigger. +decision: APPROVE +findings: [] +absent: [status-retired-add] diff --git a/examples/packs/topic-registry/.assent/tests/topics/soft-delete/unrelated-modify/facts.yaml b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/unrelated-modify/facts.yaml new file mode 100644 index 00000000..21c3fc57 --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/unrelated-modify/facts.yaml @@ -0,0 +1,7 @@ +author: + login: alice + groups: [inventory-team] +quota: + max_partitions: 24 +schema: + valid: true diff --git a/examples/packs/topic-registry/.assent/tests/topics/soft-delete/unrelated-modify/head/topics/prod/inventory.updates.v1.yaml b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/unrelated-modify/head/topics/prod/inventory.updates.v1.yaml new file mode 100644 index 00000000..6d0551bb --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/soft-delete/unrelated-modify/head/topics/prod/inventory.updates.v1.yaml @@ -0,0 +1,17 @@ +inventory.updates.v1: + owner: inventory-team + partitions: 4 + replication_factor: 3 + retention_hours: 336 + cleanup_policy: delete + schema: + format: avro + subject: inventory.updates.v1-value + compatibility: BACKWARD + references: + inventory.common: 1 + compaction: + strategy: delete + description: > + Inventory level change events for the warehouse domain. + status: active diff --git a/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/base/topics/prod/billing.invoices.v1.yaml b/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/base/topics/prod/billing.invoices.v1.yaml new file mode 100644 index 00000000..97f9a5dd --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/base/topics/prod/billing.invoices.v1.yaml @@ -0,0 +1,19 @@ +billing.invoices.v1: + owner: billing-team + partitions: 6 + replication_factor: 3 + retention_hours: 168 + cleanup_policy: delete + schema: + format: avro + subject: billing.invoices.v1-value + compatibility: BACKWARD + references: + billing.common: 1 + compaction: + strategy: delete + description: > + Invoice lifecycle events for the billing domain. + acl: + grants: + billing-team: read diff --git a/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/expect.yaml b/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/expect.yaml new file mode 100644 index 00000000..2cef084e --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/expect.yaml @@ -0,0 +1,3 @@ +# acl.grants ADD keyed by an explicit principal (finance-team) -> not "*" -> wildcard-grant-block proves silently. +decision: APPROVE +findings: [] diff --git a/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/facts.yaml b/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/facts.yaml new file mode 100644 index 00000000..cd3976b6 --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/facts.yaml @@ -0,0 +1,7 @@ +author: + login: alice + groups: [billing-team] +quota: + max_partitions: 24 +schema: + valid: true diff --git a/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/head/topics/prod/billing.invoices.v1.yaml b/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/head/topics/prod/billing.invoices.v1.yaml new file mode 100644 index 00000000..cf894987 --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/head/topics/prod/billing.invoices.v1.yaml @@ -0,0 +1,20 @@ +billing.invoices.v1: + owner: billing-team + partitions: 6 + replication_factor: 3 + retention_hours: 168 + cleanup_policy: delete + schema: + format: avro + subject: billing.invoices.v1-value + compatibility: BACKWARD + references: + billing.common: 1 + compaction: + strategy: delete + description: > + Invoice lifecycle events for the billing domain. + acl: + grants: + billing-team: read + finance-team: read diff --git a/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/negative/base/topics/prod/billing.invoices.v1.yaml b/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/negative/base/topics/prod/billing.invoices.v1.yaml new file mode 100644 index 00000000..97f9a5dd --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/negative/base/topics/prod/billing.invoices.v1.yaml @@ -0,0 +1,19 @@ +billing.invoices.v1: + owner: billing-team + partitions: 6 + replication_factor: 3 + retention_hours: 168 + cleanup_policy: delete + schema: + format: avro + subject: billing.invoices.v1-value + compatibility: BACKWARD + references: + billing.common: 1 + compaction: + strategy: delete + description: > + Invoice lifecycle events for the billing domain. + acl: + grants: + billing-team: read diff --git a/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/negative/expect.yaml b/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/negative/expect.yaml new file mode 100644 index 00000000..c8ac23b1 --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/negative/expect.yaml @@ -0,0 +1,6 @@ +# acl.grants ADD keyed by the literal wildcard principal "*" -> BLOCK. +decision: BLOCK +findings: + - rule: wildcard-grant-block + obligation: wildcard-grant + effect: block diff --git a/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/negative/facts.yaml b/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/negative/facts.yaml new file mode 100644 index 00000000..cd3976b6 --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/negative/facts.yaml @@ -0,0 +1,7 @@ +author: + login: alice + groups: [billing-team] +quota: + max_partitions: 24 +schema: + valid: true diff --git a/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/negative/head/topics/prod/billing.invoices.v1.yaml b/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/negative/head/topics/prod/billing.invoices.v1.yaml new file mode 100644 index 00000000..39f9d1ec --- /dev/null +++ b/examples/packs/topic-registry/.assent/tests/topics/wildcard-grant/negative/head/topics/prod/billing.invoices.v1.yaml @@ -0,0 +1,20 @@ +billing.invoices.v1: + owner: billing-team + partitions: 6 + replication_factor: 3 + retention_hours: 168 + cleanup_policy: delete + schema: + format: avro + subject: billing.invoices.v1-value + compatibility: BACKWARD + references: + billing.common: 1 + compaction: + strategy: delete + description: > + Invoice lifecycle events for the billing domain. + acl: + grants: + billing-team: read + "*": read From 2a3f874d17195e0d0ef527c3609eabb2056acb3d Mon Sep 17 00:00:00 2001 From: Konrad Heimel Date: Sun, 16 Aug 2026 11:59:17 +0200 Subject: [PATCH 2/2] :bug: fix(examples): make privilege-tier's proving fixture load-bearing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review finding F1: the C2 privilege-tier positive fixture (tier 1->3) was decorative, not load-bearing — deleting it left `assent test --coverage` still reporting tier-allow-list COVERED, because the pre-existing S02 allowed-fields/negative golden coincidentally moves tier 1->2, which was already inside the old [1,2,3] allow-list and silently supplied the rule's proving-polarity credit on its own. Narrow the allow-list to [1, 3] (tier 2 deprecated/consolidated, existing tier-2 services grandfathered since they never trigger a tier MODIFY). This flips that S02 fixture's 1->2 change to a tolerated failing firing instead of a proving one, making this pack's own privilege-tier/ positive fixture (1->3, unaffected by the narrower list) the sole source of proving-polarity credit. Empirically re-verified: with only the positive fixture deleted, `--coverage` now reports "MISSING catalog/tier-allow-list: no proving case" (red); restored, green again (8/8 rules, both polarities). Full `assent test`/`--coverage`/`lint` clean on both packs; `task check` green. --- CHANGELOG.md | 1 + .../packs/catalog/rules/privilege-tier.yaml | 25 ++++++++++++++----- .../tests/catalog/privilege-tier/expect.yaml | 11 +++++--- .../privilege-tier/negative/expect.yaml | 2 +- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a624f2ab..448771d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -125,6 +125,7 @@ repository still gets a decision, never by following the link; no release carrie - :bug: fix(ci): pin ci-audit-test in the AUD-S18 check-stage list - :bug: specs(p5-ex): make S10 schema freeze and S07 fence non-vacuous - :bug: fix(docs-gates): fail on unmapped format tokens and de-confound the tests-dir mutation (EX-S01 review) +- :bug: fix(examples): make privilege-tier's proving fixture load-bearing ### Refactoring - :recycle: refactor(examples): discover dogfood packs instead of a hardcoded 3-name loop diff --git a/examples/packs/service-catalog/.assent/packs/catalog/rules/privilege-tier.yaml b/examples/packs/service-catalog/.assent/packs/catalog/rules/privilege-tier.yaml index df315408..eb75427e 100644 --- a/examples/packs/service-catalog/.assent/packs/catalog/rules/privilege-tier.yaml +++ b/examples/packs/service-catalog/.assent/packs/catalog/rules/privilege-tier.yaml @@ -2,11 +2,24 @@ # service entry and is already caught by only-safe-fields (any non-/oncall field # change escalates to require-review, allowed-fields.yaml). This rule adds a # NARROWER, value-level check on top of that coarse field-level one: a tier value -# outside the allow-list (e.g. 0, or any value not in the declared band) is a -# privilege-escalation attempt, not just an "other field changed" event. Reuses -# the allowed-fields obligation (already required) so no bindings.yaml change is -# needed. D-061: this stays a scalar leaf check on the already-governed keyed-list -# entry field — no unkeyed nested list is reintroduced. +# outside the allow-list is a privilege-escalation attempt, not just an "other +# field changed" event. Reuses the allowed-fields obligation (already required) +# so no bindings.yaml change is needed. D-061: this stays a scalar leaf check on +# the already-governed keyed-list entry field — no unkeyed nested list is +# reintroduced. +# +# Allow-list is [1, 3], deliberately EXCLUDING 2: tier 2 is a deprecated +# mid-band value being consolidated away (existing tier-2 services, e.g. +# storefront-web in the S02 fixtures, are grandfathered — they simply never +# trigger a tier MODIFY — but no NEW assignment to 2 is permitted). This is +# also what makes the rule's proving polarity load-bearing: a flat [1,2,3] +# allow-list would let the pre-existing S02 allowed-fields/negative golden +# (which coincidentally moves tier 1->2) silently double as this rule's +# proving-polarity witness, so deleting THIS pack's own privilege-tier/ +# positive fixture would leave --coverage falsely green. With 2 excluded, that +# S02 fixture's 1->2 change instead becomes a (tolerated, unasserted) FAILING +# firing of this rule — proving-polarity coverage comes ONLY from this pack's +# own privilege-tier/ fixture (tier 1->3). apiVersion: assent.dev/v1alpha1 kind: MergePolicy metadata: @@ -27,7 +40,7 @@ spec: kinds: [modify] prove: obligation: allowed-fields - when: "new in [1, 2, 3]" + when: "new in [1, 3]" onFailure: effect: require-review code: allowed-fields.tier-out-of-range diff --git a/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/expect.yaml b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/expect.yaml index 8f7245c3..680c01b0 100644 --- a/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/expect.yaml +++ b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/expect.yaml @@ -1,7 +1,10 @@ -# tier 1 -> 3 is within the allow-list ([1,2,3]) -> tier-allow-list proves silently -# (its proving polarity). The pre-existing only-safe-fields rule still escalates -# ANY non-/oncall field change to require-review regardless of value — that is -# unrelated coarse-grained coverage, not this rule's finding. +# tier 1 -> 3 is within the allow-list ([1,3]) -> tier-allow-list proves silently +# (its proving polarity — and, since 2 is deliberately excluded from the +# allow-list, this fixture is the SOLE source of that proving credit in the +# pack: deleting it genuinely reddens --coverage, see the rule file comment). +# The pre-existing only-safe-fields rule still escalates ANY non-/oncall field +# change to require-review regardless of value — that is unrelated +# coarse-grained coverage, not this rule's finding. decision: REVIEW findings: - rule: only-safe-fields diff --git a/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/expect.yaml b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/expect.yaml index d570713c..6c77f1ab 100644 --- a/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/expect.yaml +++ b/examples/packs/service-catalog/.assent/tests/catalog/privilege-tier/negative/expect.yaml @@ -1,4 +1,4 @@ -# tier 1 -> 0 is outside the allow-list ([1,2,3]) -> tier-allow-list fails, on top +# tier 1 -> 0 is outside the allow-list ([1,3]) -> tier-allow-list fails, on top # of the coarse-grained only-safe-fields escalation (both effects are # require-review, so the decision stays REVIEW either way). decision: REVIEW