Skip to content

Change Impact Analysis

Module dependency chains, modification consequences

What breaks when you change a major Sillo module. For each module: direct dependents, indirect dependents, behaviour changes, test failures, API surface, risk level, and migration guidance.


graph TD
    APP["SilloApp<br/>application.py"]
    ROUTER["Router / Route<br/>core/routing"]
    MW["BaseMiddleware<br/>middleware/base"]
    REQ["Request / Response<br/>core/http"]
    DI["Depend / DI<br/>core/dependencies"]
    AUTH["useAuth<br/>auth/use_auth"]
    ABE["AuthenticationBackend<br/>auth/backend"]
    USER["UserBaseModel / UserProtocol<br/>users/"]
    PERM["PermissionMixin<br/>users/permissions"]
    MODEL["Model / Record<br/>record/models"]
    RM["RecordManager / RecordQuerySet<br/>record/scopes"]
    TASK["Task / Queue<br/>work/"]
    EVENT["EventEmitter / Transports<br/>events/"]
    SESS["SessionMiddleware<br/>session/"]
    CACHE["Cache Backends<br/>cache/"]

    APP --> ROUTER
    APP --> MW
    APP --> AUTH
    APP --> EVENT
    APP --> CACHE
    APP --> TASK

    ROUTER --> REQ
    ROUTER --> DI
    ROUTER --> AUTH
    ROUTER --> MW

    MW --> REQ

    AUTH --> ABE
    AUTH --> USER
    AUTH --> PERM

    ABE --> REQ

    USER --> MODEL
    USER --> PERM

    MODEL --> RM
    MODEL --> DI

    RM --> MODEL

    SESS --> MW
    SESS --> REQ

    TASK --> EVENT
    TASK --> CACHE

    style APP fill:#ffcdd2,stroke:#C62828,stroke-width:3px
    style ROUTER fill:#ffcdd2,stroke:#C62828,stroke-width:2px
    style DI fill:#ffcdd2,stroke:#C62828,stroke-width:2px
    style REQ fill:#ffcdd2,stroke:#C62828,stroke-width:2px
    style AUTH fill:#fff9c4,stroke:#F9A825,stroke-width:2px
    style MODEL fill:#fff9c4,stroke:#F9A825,stroke-width:2px
    style MW fill:#c8e6c9,stroke:#2E7D32
    style CACHE fill:#c8e6c9,stroke:#2E7D32
    style EVENT fill:#c8e6c9,stroke:#2E7D32
    style TASK fill:#c8e6c9,stroke:#2E7D32
    style SESS fill:#c8e6c9,stroke:#2E7D32

Legend:

  • 🔴 Red: Critical: changes cascade to nearly everything
  • 🟡 Yellow: High: changes cascade to auth, ORM, or all routes
  • 🟢 Green: Medium: changes are more contained

File: core/sillo/application.py Risk level: 🔴 CRITICAL

DependentHow
Every route handlerRegistered via app.get/post/...
Every middlewareRegistered via app.use()
All lifecycle hooksapp.on_startup/on_shutdown
CLI consoleapp.add_command()
OpenAPI generationapp.build_openapi()
Event emitterapp.events
Auth configurationapp.auth_backends, app.auth_user_model
Custom encodersapp.add_encoder()
Frontend SPA mountingapp.frontend()
  • Every test that creates a SilloApp instance
  • Every integration/ASGI test
  • Any script that uses the CLI
  • OpenAPI client generation (downstream)
  • Deployment scripts (lifespan events)
ChangeImpact
__call__ signature (scope, receive, send)All ASGI servers fail
use() method signatureAll middleware registration breaks
get/post/... decorator signaturesAll route definitions break
url_for() logicAll url_for() calls return wrong URLs
build_openapi() outputClient generation breaks
Lifecycle hook orderingStartup/shutdown side effects change
state attribute type/semanticsAll ctx.app.state access breaks
  • All route tests (via test client)
  • All middleware tests
  • All integration tests
  • OpenAPI snapshot tests
  • CLI tests
  • SilloApp.__call__(scope, receive, send)
  • SilloApp.use(middleware)
  • SilloApp.get/post/put/patch/delete(path, handler, ...)
  • SilloApp.add_route(route)
  • SilloApp.mount_router(router, name)
  • SilloApp.url_for(_name, **path_params)
  • SilloApp.build_openapi()
  • SilloApp.on_startup/on_shutdown(handler)

Any change to SilloApp should be treated as a major version bump. If changing method signatures, provide a compatibility shim that accepts both old and new signatures for at least one release cycle.


File: core/sillo/core/routing/ Risk level: 🔴 CRITICAL

DependentHow
SilloAppCreates and owns the root Router
Every route handlerWrapped in a Route object
url_for()Walks router tree to find named routes
Mounted sub-routersrouter.mount_router()
DI resolutionRoute.dependant drives parameter extraction
useAuthRoute.auth gates authentication
  • All middleware (routes determine which middleware applies)
  • Test client (resolves routes by path)
  • OpenAPI schema (routes → operations)
  • Warder, the admin panel (mounts its own router)
  • Frontend app (mounted as a sub-router)
ChangeImpact
compile_path() regex patternsURL matching breaks silently (most dangerous)
Route.match(scope) logic404s for valid URLs
Dependant constructionDI parameter extraction fails
Route orderingFirst-match vs last-match semantics change
url_for() parameter handlingReverse URL generation breaks
Middleware application orderSecurity middleware may not run
route_class attributeCustom route classes stop working
  • All routing tests
  • URL generation tests
  • Parameter extraction tests
  • Mounted router tests
  • OpenAPI operation ID tests
  • Router.get/post/put/patch/delete(path, handler, ...)
  • Router.add_route(route)
  • Router.mount_router(app)
  • Router.url_for(_name, **path_params)
  • Route.match(scope)
  • Route.handle(scope, receive, send)
  • compile_path(path): internal but critical

Route pattern changes are the most dangerous because they can cause silent failures (requests going to wrong handlers). Always add regression tests for all existing URL patterns before changing compile_path().


File: core/sillo/middleware/base.py Risk level: 🟡 HIGH

DependentHow
SessionMiddlewareSubclass
AuthenticationMiddlewareSubclass
ETagMiddlewareSubclass
DatabaseManager.ensure_contextUses middleware pattern
All user-defined middlewareSubclass
  • All authenticated routes (via AuthenticationMiddleware)
  • All session-dependent routes (via SessionMiddleware)
  • CSRF protection (via session)
  • Warder (via session + auth)
ChangeImpact
dispatch signatureAll middleware subclasses break
call_next taking argumentsEvery middleware body breaks
__call__ forwarding to dispatchSubclasses overriding __call__ break
Error handling in middleware chainExceptions may propagate differently
  • All middleware unit tests
  • Auth integration tests
  • Session tests
  • CSRF tests

dispatch(ctx, call_next) is a core contract, and the one thing every middleware in and outside the framework is written against. Changing its shape is a major-version change; there is no way to do it compatibly, because the signature is the interface.


File: core/sillo/core/http/ Risk level: 🔴 CRITICAL

DependentHow
Every handlerReceives HttpContext, returns a response
Every middlewaredispatch(ctx, call_next)
useAuthReads ctx.user, ctx.session
Test clientConstructs HttpContext objects
Form parsingawait ctx.form, await ctx.files
DI systemExtracts parameters from the context
  • All tests
  • All middleware
  • All auth backends
  • Session middleware
  • Warder
ChangeImpact
ctx.body caching/asyncDouble-read semantics change
ctx.json() parsingAll JSON API handlers affected
ctx.form() parsingAll form handlers affected
ctx.user propertyAuth integration breaks
Response.set_cookie() paramsSession cookies break
Response.status_code typeTest assertions fail
sillo.responses buildersEvery json() / text() / html() call site breaks
FileResponse range supportStreaming breaks
  • All handler tests
  • All middleware tests
  • Test client tests
  • Form parsing tests
  • File upload tests
  • Cookie tests
  • Request.body (async property)
  • Request.json() (async method)
  • Request.form() / Request.files()
  • Request.session
  • Request.user
  • Response.set_cookie() / delete_cookie()
  • Response.set_header() / remove_header()
  • JSONResponse, HTMLResponse, FileResponse, etc.
  • The sillo.responses builders

HttpContext and BaseResponse are the two most widely used classes. Any change should be backward-compatible. If adding new required parameters, use keyword-only arguments with defaults.


File: core/sillo/core/dependencies/ Risk level: 🔴 CRITICAL

DependentHow
Every route with parametersRoute.dependant is built by the DI system
useAuthUser loading is a dependency
Parameter validationPydantic validators run in the DI pipeline
  • All handlers with Depend() markers
  • All handlers with path/query/body parameters
  • Every handler, which takes an HttpContext
  • Validation error responses
ChangeImpact
get_dependant() signature analysisParameters extracted incorrectly
_build_execution_plan() orderingDependencies resolve in wrong order
solve_dependencies() cachinguse_cache=True breaks
_collect_kwargs() parameter bindingWrong values injected
Pydantic validation integrationValidation errors change format
  • All handler tests with parameters
  • DI resolution tests
  • Validation tests
  • Error response tests

The DI system is the most complex part of Sillo. Changes should be extremely careful. Always test with:

  • Simple path parameters
  • Query parameters with defaults
  • Nested Depend() chains
  • use_cache=True dependencies
  • Generator dependencies (sync and async)
  • Mixed sync/async handlers

File: core/sillo/auth/use_auth.py Risk level: 🟡 HIGH

DependentHow
All authenticated routes@app.get(..., auth=useAuth(...))
OpenAPI securityauth.security_requirements()
Permission checksauth.permissions list
  • All routes requiring login
  • All role/permission-gated routes
  • OpenAPI securitySchemes and security fields
  • Warder’s authentication
ChangeImpact
authenticate() return type (bool)Route gating breaks
security_requirements() outputOpenAPI security schemes break
Permission matching logicAccess control changes
all_of vs any_of semanticsPermission combinations break
Backend iteration orderAuth resolution changes
  • All auth-gated route tests
  • OpenAPI security schema tests
  • Permission combination tests
  • useAuth(permissions=[], backends=[], user_model=..., required=True)
  • useAuth.authenticate(request)
  • useAuth.security_requirements(available)

authenticate() returning bool is a strict contract. If you need more information (e.g. which backend succeeded), add it as a new method rather than changing the return type.


File: core/sillo/auth/backend.py Risk level: 🟡 HIGH

DependentHow
AuthenticationMiddlewareIterates backends
useAuth per-route backendsauth=useAuth(backends=[...])
OpenAPI describe()Security scheme generation
JWTAuthBackend, SessionAuthBackend, APIKeyAuthBackendSubclasses
  • All authenticated routes
  • OpenAPI spec security definitions
  • Token validation
  • Session validation
ChangeImpact
authenticate() return type (AuthResult)Middleware breaks
describe() return type (`SecuritySchemeNone`)
handle_exception() signatureError handling changes
name attribute usageBackend identification changes
  • Auth backend unit tests
  • Middleware integration tests
  • OpenAPI security tests
  • Token validation tests

Changing authenticate() to return something other than AuthResult would break every auth backend ever written. Add new fields to AuthResult as optional instead.


File: core/sillo/users/ Risk level: 🟡 HIGH

DependentHow
AuthenticationMiddlewareLoads user via UserProtocol.load_user()
useAuthauth.user_model is a UserProtocol type
WarderReads the user model to authenticate
Permission systemhas_perm(), has_perms()
  • All authenticated routes (via ctx.user)
  • All permission checks
  • Warder access
  • Password hashing/verification
  • User management commands
ChangeImpact
load_user(identity) classmethodUser loading fails
is_authenticated / is_anonymousAuth checks break
has_perm() / has_perms()Permission checks break
set_password() / check_password()Auth flow breaks
UserManager methodsUser creation/lookup breaks
Model fields (email, username, password)All user queries affected
  • User model tests
  • Auth flow tests
  • Permission tests
  • User creation tests

User model changes are extremely high-risk because they affect both authentication and authorization. Field renames should go through a migration + compatibility property. Method signature changes should support both old and new signatures for one release.


File: core/sillo/users/permissions/mixins.py Risk level: 🟡 HIGH

DependentHow
useAuth permission checksauth.permissions checked via mixin methods
Warder access controlPermission gates
UserBaseModelInherits PermissionMixin
  • All permission-gated routes
  • Warder
  • Group-based access control
ChangeImpact
load_permissions() return typePermission set changes
has_permission() logicAccess control changes
get_groups() / is_in_group()Group-based checks break
get_group_permissions()Inherited permissions change
  • Permission unit tests
  • Group permission tests

File: core/sillo/record/models.py Risk level: 🟡 HIGH

DependentHow
All application modelsInherit from Model
UserBaseModelInherits from Model
MigrationsSchema generation
FactoriesModel instantiation
WarderModel registration
Fixtures/SeedersModel creation
  • All database operations
  • All ORM queries
  • All test fixtures
  • Warder’s CRUD
  • Bulk operations
  • Soft delete logic
ChangeImpact
save() methodAll writes affected
soft_delete() / restore()Soft-delete logic changes
to_dict() / to_json()Serialization changes
get_or_none() / get_or_create()Lookup semantics change
bulk_create() / bulk_upsert()Batch operations change
Auto fields (created_at, updated_at, deleted_at)All models affected
HasCasts integrationField encoding changes
HasScopes integrationQuery scoping changes
  • All model unit tests
  • Migration tests
  • Factory tests
  • Fixture tests
  • Bulk operation tests
  • Soft-delete tests

Model changes require coordinated database migrations. Always:

  1. Create a migration for schema changes
  2. Update factories/fixtures
  3. Update anything that names fields by hand — Warder resources, serializers
  4. Run full test suite

File: core/sillo/record/scopes.py Risk level: 🟡 HIGH

DependentHow
All model queriesModel.objects is a RecordManager
Global scopesApplied on every get_queryset()
Chainable scopesRecordQuerySet.__getattr__
  • All queries across the application
  • Soft-delete filtering (if using global scope)
  • Multi-tenancy filtering (if using global scope)
  • Warder’s queries
ChangeImpact
get_queryset() scope applicationAll queries affected
__getattr__ scope forwardingChainable scopes break
without_global_scopes()Anything relying on an unscoped read may break
QuerySet method signaturesAll query chains affected
  • All query tests
  • Scope tests
  • Global scope tests

Changes to scope application are silent breaking changes. Queries may return different results without any visible error. Always add tests that verify expected record counts.


File: core/sillo/work/ Risk level: 🟢 MEDIUM

DependentHow
@task decorated functionsRegistered as tasks
dispatch() callsEnqueue jobs
QueueWorker / WorkerPoolProcess jobs
Batch / JobChainTrack job groups
SchedulerManagerScheduled execution
  • All background processing
  • Scheduled jobs
  • Email sending (if queued)
  • Report generation (if queued)
  • Any async background work
ChangeImpact
Task.serialize() formatQueue persistence breaks
MemoryBackend / RedisBackend protocolBackend swap fails
dispatch() argumentsAll job submissions break
Worker lifecycleJobs may not process
Batch.wait() timeout semanticsBatch tracking breaks
Retry/timeout middlewareError handling changes
  • Task unit tests
  • Queue integration tests
  • Worker tests
  • Batch tests
  • Scheduler tests

Queue system changes affect background processing which is hard to test in isolation. Use integration tests with MemoryBackend for fast feedback, then test with RedisBackend for production parity.


File: core/sillo/events/ Risk level: 🟢 MEDIUM

DependentHow
app.eventsApplication-level emitter
All event listenersevents.on(name, handler)
Cross-instance communicationVia transports (Redis, etc.)
  • Real-time features (WebSocket broadcasts)
  • Cache invalidation (if event-driven)
  • Audit logging (if event-driven)
  • Inter-service communication
ChangeImpact
emit() argumentsAll listeners break
Wire format envelopeCross-instance communication breaks
Transport protocolBackend swap fails
on() / once() semanticsListener registration changes
EventNamespace prefix logicNamespaced events break
Dedup logic in _deliver()Duplicate events or missed events
  • Event unit tests
  • Transport tests
  • Namespace tests
  • Cross-instance tests (Redis transport)

Event system changes can cause silent failures in distributed deployments if the wire format changes. Always version the envelope format and support old formats during migration.


File: core/sillo/session/ Risk level: 🟢 MEDIUM

DependentHow
ctx.sessionAccessed in handlers
SessionAuthBackendReads session for auth
CSRF middlewareStores/reads CSRF token
WarderSession-based admin auth
  • All session-dependent authentication
  • CSRF protection
  • Flash messages (if using sessions)
  • Shopping carts / user state (if using sessions)
ChangeImpact
Session cookie name/attributesExisting sessions invalidated
Signing/encryptionSession data unreadable
Session.__getitem__ / __setitem__All session access breaks
Session.save() timingData loss or stale data
Expiry handlingSessions expire unexpectedly
Backend protocolBackend swap fails
  • Session unit tests
  • Auth session tests
  • CSRF tests

Session changes can invalidate all existing sessions in production. If changing the session format, support reading old format for one release while always writing new format.


File: core/sillo/cache/ Risk level: 🟢 MEDIUM

DependentHow
@cache decoratorCaches function results
All cached operationsExplicit cache.get/set
Tag-based invalidationcache.invalidate_tags()
  • Response caching
  • Query caching
  • Template fragment caching
  • Rate-limit state (if using cache backend)
ChangeImpact
BaseCache abstract method signaturesAll backends break
Serialization formatExisting cache entries unreadable
Key formatCache misses (stale data)
TTL handlingCache expires differently
Tag invalidation logicStale cache served
CacheStats trackingMonitoring breaks
  • Cache unit tests
  • Cache decorator tests
  • Tag invalidation tests
  • Cache stats tests

Cache changes are least dangerous because cache is a transparent optimisation. A cache miss just means a slower response. However, serialization changes will cause brief spikes of cache misses after deployment.


quadrantChart
    title Module Risk: Impact vs Blast Radius
    x-axis Low Blast Radius --> High Blast Radius
    y-axis Low Impact --> High Impact
    quadrant-1 "Test thoroughly"
    quadrant-2 "Major version bump"
    quadrant-3 "Ship with confidence"
    quadrant-4 "Monitor after deploy"
    SilloApp: [0.95, 0.95]
    Router/Route: [0.90, 0.90]
    Request/Response: [0.85, 0.90]
    Depend/DI: [0.80, 0.85]
    useAuth: [0.65, 0.75]
    AuthenticationBackend: [0.60, 0.70]
    UserBaseModel: [0.70, 0.70]
    PermissionMixin: [0.55, 0.65]
    Model/Record: [0.75, 0.60]
    RecordManager: [0.65, 0.55]
    BaseMiddleware: [0.50, 0.60]
    SessionMiddleware: [0.40, 0.50]
    Task/Queue: [0.45, 0.40]
    EventEmitter: [0.35, 0.35]
    Cache: [0.25, 0.30]
ModuleRiskDirect depsIndirect depsSilent breakage?
SilloApp🔴 CriticalAll routes, middleware, CLI, OpenAPIEverythingNo (fails fast)
Router / Route🔴 CriticalAll routes, url_for, DIEverythingYes (wrong handler)
Request / Response🔴 CriticalAll handlers, middlewareEverythingNo (fails fast)
Depend / DI🔴 CriticalAll parameterised handlersEverythingYes (wrong values)
useAuth🟡 HighAll auth routes, OpenAPISecurity surfaceYes (wrong access)
AuthenticationBackend🟡 HighAuth middleware, per-route backendsAll authNo (fails fast)
UserBaseModel / UserProtocol🟡 HighAuth middleware, Warder, permissionsAll authYes (wrong user)
PermissionMixin🟡 HighPermission checks, WarderAuth systemYes (wrong access)
Model / Record🟡 HighAll models, migrations, factoriesAll DB opsYes (wrong data)
RecordManager / QuerySet🟡 HighAll queries, scopesAll DB opsYes (wrong results)
BaseMiddleware🟢 MediumUser middleware, ASGI bridgeMiddleware chainNo
SessionMiddleware🟢 MediumSession auth, CSRF, WarderSession usersYes (lost sessions)
Task / Queue🟢 MediumBackground work, schedulerAsync opsYes (lost jobs)
EventEmitter🟢 MediumEvent listeners, transportsReal-timeYes (missed events)
Cache🟢 MediumCache decorator, cached opsPerformanceYes (stale data)

The most dangerous changes are those marked “Yes” for silent breakage. These changes don’t cause immediate errors: they cause wrong data, wrong access, or missed operations. For these modules:

  1. Always add regression tests before changing.
  2. Add runtime assertions (e.g. assert isinstance(result, expected_type)).
  3. Log unexpected values at debug level.
  4. Consider feature flags for gradual rollout.
  5. Monitor metrics after deployment (error rates, cache hit rates, auth failure rates).

Before merging any change to a module listed above:

  • Run unit tests for the module
  • Run integration tests that use the module
  • Run the full test suite (catch indirect breakage)
  • Check for silent breakage risks (see matrix above)
  • If 🔴 Critical: get two reviewer approvals
  • If changing wire format: support old format for one release
  • If changing session format: communicate to ops team
  • If changing auth: security review required
  • If changing DB model: migration required
  • Update this document if new dependencies are introduced