Exemption Workflow
Time-boxed, audited, owner-approved waiver path for a specific rule
assignment against a specific scope. Backed by the schema in
rule-catalog/schema/exemption.json
and a CI validator that runs on every PR that touches
rule-catalog/exemptions/.
When to use an exemption
Section titled “When to use an exemption”An exemption suppresses enforce for a specific rule against a specific scope. It is the right tool when all of the following hold:
- The rule is correct in general but wrong for this scope.
- The scope is narrowed to a resource group (or narrower).
- There is a plan to remove the exemption - an exemption is a stall, not a fix.
- The impact scope of leaving the rule off is understood and bounded.
If the rule is wrong in general, retire the rule via the rule-catalog
pipeline instead. If the wrong dimension is auto-vs-human approval, tune
risk-classification, not the rule.
- Requester - any member of the
aw-contributorsEntra group (or above) MAY open an exemption PR. - Approver - MUST be an
aw-ownersmember. Approver ≠ Requester- branch protection enforces “author ≠ reviewer”, and the exemption
artifact carries
requested_byandapproved_byfields the CI check inspects for distinct values.
- branch protection enforces “author ≠ reviewer”, and the exemption
artifact carries
- Auditor - every state transition (active / expired / revoked) writes an audit-log entry with the actor principal.
Procedure
Section titled “Procedure”- Open a PR using the
Exemption Requesttemplate. - Fill the artifact at
rule-catalog/exemptions/<id>.jsonaccording to the schema. - CI runs:
- Schema validation (
exemption-checkjob). - Author-≠-reviewer branch-protection rule (repo settings).
requested_by≠approved_bymodel invariant.expires_at > created_atmodel invariant.
- Schema validation (
- Owner-tier review + merge. The merge has no side effect on the live Azure resources today; enforcement suppression takes effect once the catalog pipeline (Phase 2) picks the exemption up.
- Auto-expiry. A scheduled job (
scripts/governance/exemption-expire.py, moved to a Container Apps Job after W4.1) flips each artifact tostate=expiredthe momentexpires_atpasses, and re-applies the underlying rule assignment. The event is audit-logged.
Time-boxing
Section titled “Time-boxing”expires_atMUST be strictly aftercreated_at.- No hard maximum window is codified here; longer windows MUST be justified in the PR body.
- A lookahead notification fires on the default A1 channel 14 days before
expires_atvia theexemption_expiry_lookahead_weeklyroute (W5.4 - depends on the channels adapter; tracked separately).
Revocation
Section titled “Revocation”An owner MAY revoke an active exemption by:
- Editing the artifact to
state=revoked, settingrevoked_atandrevoked_by. - Merging the revocation PR - Owner-tier review, no self-approval.
Revocation flips enforce back on immediately (the moment the catalog pipeline observes the state change).
Escalation
Section titled “Escalation”- If CI is flapping on a request that plainly satisfies the schema, page
aw-ownerson the default A1 channel with the CI log attached. - If an exemption is denied but the environment is materially at risk,
escalate to
aw-break-glass- under Conditional Access, this is a short-lived, audited grant, not a bypass.
References
Section titled “References”| Artifact | Path |
|---|---|
| Design: Human Override | ../../.github/instructions/architecture.instructions.md#human-override |
| Exemption schema | ../../services/core-control-plane/src/fdai/rule_catalog/schema/exemption.schema.json |
CI check (exemption-check job) | ../../.github/workflows/ci.yml |
| Expiry CLI | ../../scripts/governance/exemption-expire.py |
| PR template | ../../.github/PULL_REQUEST_TEMPLATE/exemption.md |