Skip to main content

Command Palette

Search for a command to run...

Authorization in Django: From Permissions to Policies — Part 12 — When Boundaries Collapse

Responsibility Drift and the Loss of System Meaning

Updated
5 min read
Authorization in Django: From Permissions to Policies — Part 12 — When Boundaries Collapse

So far, the system has behaved correctly not because every check succeeded, but because each layer understood where its responsibility ended.

This part examines what happens when those boundaries erode—not through negligence, but through convenience.

Most authorization failures in mature systems are not dramatic. They emerge slowly, as responsibilities drift—when one layer begins answering questions it was never designed to ask.

The failures that follow are subtle, survivable at first, and eventually systemic.

When Permissions Begin to Model State

The first collapse happens quietly.

A permission meant to express capability is modified to express condition. Names begin to encode workflow and state:

  • publish_draft_article

  • publish_own_article

  • publish_article_after_review

At a glance, this feels reasonable. The permission name documents intent. The check feels complete.

But something critical has changed. The permission is no longer stable.

Its meaning now depends on article state, ownership, workflow position, or time. Every state transition becomes an authorization event. Permissions must be revoked and re-granted as records move. Migrations begin to encode business rules. Historical meaning dissolves.

Soon, the system can no longer answer a basic question: what does this permission mean independent of today’s workflow?

This is not an access-control failure. It is a loss of identity.

When Policies Attempt to Guarantee Truth

The second collapse is more dangerous because it feels principled.

Policies expand until they resemble proofs. Conditions accumulate:

  • the article is a draft

  • the user is the owner

  • no other publish is in progress

  • metadata is complete.

The conclusion follows cleanly. All checks are correct. The logic is sound.

And still, the system breaks.

The failure is not in the reasoning, but in the assumption behind it. Policies run before mutation. They operate in a world that has not yet changed. They cannot see concurrent requests, defend against retries, or account for alternate execution paths that bypass the expected flow.

A policy can assert that something should be safe. It cannot ensure that it is safe.

When policies are treated as guarantees, systems fail under load—not because the rules were wrong, but because enforcement was placed too early.

When Invariants Become Optional

The final collapse is the most catastrophic—and the most common.

Invariant checks are omitted for performance. Constraints are removed temporarily. Transactions are narrowed to avoid deadlocks. Each change is justified in isolation, framed as a pragmatic compromise.

The system still works. Most of the time.

Until it doesn’t.

A published article reverts to draft. A finalized record is half-written. Conflicting states coexist. At this point, failure is no longer attributable to a request. There is no user to blame, no policy to revise, no permission to revoke.

The system has violated its own reality.

Recovery becomes forensic rather than corrective.

The Pattern Behind the Failures

Each collapse follows the same shape.

A layer begins answering questions outside its mandate.

  • Permissions start explaining when.

  • Policies attempt to enforce truth.

  • Invariants are treated as advisory rather than absolute.

The system continues to run. Tests still pass. Authorization still appears to work.

But meaning has blurred.

When failures occur, responses become confused. Permission errors surface as business rule violations. Policy failures corrupt data. Invariant violations are silently persisted.

The architecture no longer tells you why something failed—only that it did.

Why This Is Hard to See Early

These failures do not announce themselves.

They emerge during

  • feature acceleration

  • refactors under time pressure

  • background jobs added temporarily

  • internal tooling that bypasses request flows

Each change is defensible in isolation, often framed as a local optimization or a short-term necessity.

Only later does the pattern become visible—when every authorization decision feels fragile, and no layer can be trusted on its own.

Restoring the Boundary

Systems recover not by adding more checks, but by restoring responsibility:

Permissions return to expressing capability, and nothing more. They define who may attempt an action, without encoding state, timing, or outcome.

Policies return to evaluating context. They determine whether a request is valid in the moment, without pretending to guarantee what will happen next.

Invariants return to the mutation boundary. They are non-negotiable, unavoidable, and final—the last line of defense where reality is enforced, not inferred.

When this separation is restored, failures regain meaning.

A permission failure signals lack of authority. A policy failure signals invalid intent. An invariant failure signals a system defect.

Each failure points to a specific layer. Each can be handled deliberately. Each can be reasoned about in isolation, without ambiguity or overlap.

Where We Go Next (Part 13 Preview)

By the end of this part, the lesson is no longer abstract:

Authorization fails not when checks are missing,
but when guarantees are enforced in the wrong place.

The next—and final—part steps back from mechanics entirely.

It treats authorization not as request logic, but as a long-lived system contract:
one that must survive refactors, scaling, new execution paths, and years of change.

That is where architecture either endures—or decays.

Bibliography / References

  1. Eric Evans (2003). Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley.
    https://www.domainlanguage.com/ddd/

  2. Martin Fowler (2003). Patterns of Enterprise Application Architecture. Addison-Wesley.
    https://martinfowler.com/books/eaa.html

  3. Martin Kleppmann (2017). Designing Data-Intensive Applications. O’Reilly Media.
    https://dataintensive.net/

  4. Pat Helland (2007). Life Beyond Distributed Transactions: An Apostate’s Opinion. ACM Queue.
    https://queue.acm.org/detail.cfm?id=1295698

  5. Michael T. Nygard (2018). Release It! Design and Deploy Production-Ready Software. Pragmatic Bookshelf.
    https://pragprog.com/titles/mnee2/release-it-second-edition/

  6. Django Software Foundation. Django Authentication and Authorization System. Official Django Documentation.
    https://docs.djangoproject.com/en/stable/topics/auth/

Thinking in Django & DRF

Part 4 of 13

Thinking in Django & DRF is a series about Django & DRF by understanding why things are designed the way they are. It explores insights from mastering Django & DRF, like syntax, shortcuts, abstraction, invariants, and architectural boundaries.

Up next

Authorization in Django: From Permissions to Policies — Part 11 — A Full Workflow, End to End

Tracing a Single Request Through Authority, Validity, and State

More from this blog

A

Abhilash PS — Engineering Thought & Software Architecture

23 posts