How ASA Works
Technical deep-dive into slice structure, contracts, and enforcement.
How ASA Works
The mental model, mechanics, and workflow of ASA.
The Mental Model
Think of ASA as a contract-driven architecture where:
- Specs are your intent (what you want)
- Contracts are the agreement (what the system will do)
- Slices are the implementation (how it's done)
The key insight: Separate intent from implementation.
What Is a Slice?
A Slice is a self-contained vertical feature unit. Everything needed for one feature lives in one folder.
Anatomy of a Slice
domains/auth/login/
├── slice.spec.md # Human-readable specification
├── slice.contract.json # Machine-readable contract
├── handler.py # FastAPI endpoint
├── service.py # Business logic
├── repository.py # Data access
├── schemas.py # Pydantic models
└── tests/
└── test_slice.py # Tests
What Makes It "Atomic"
- Self-contained — All code for login lives here
- Regenerable — Can be rewritten without touching other slices
- Testable — Tests run in isolation
- Deployable — Could theoretically deploy independently
The Spec: Your Intent Document
The spec (slice.spec.md) is a structured markdown file with 7 required sections:
# Purpose
User logs in with email and password. Returns JWT token.
## Inputs
- email: string
- password: string
## Outputs
- jwt_token: string
- expires_in: int
## Behaviour
- Verify user exists in database.
- Verify password matches.
- Generate JWT token.
- Return token and expiration.
## Errors
- INVALID_CREDENTIALS: Invalid email or password.
- USER_NOT_FOUND: User does not exist.
## Side Effects
None
## Dependencies
- shared/utils/jwt_generator
Why This Structure?
- Purpose — One sentence forces clarity
- Inputs/Outputs — Explicit data contracts
- Behaviour — Step-by-step logic
- Errors — All failure modes documented
- Side Effects — External impacts visible
- Dependencies — Shared modules declared
This is readable by humans and parseable by machines.
The Contract: Machine Interface
The contract (slice.contract.json) is the machine-readable version:
{
"version": "1.0",
"domain": "auth",
"slice": "login",
"inputs": {
"email": "string",
"password": "string"
},
"outputs": {
"jwt_token": "string",
"expires_in": "int"
},
"behaviour": [
"Verify user exists in database.",
"Verify password matches.",
"Generate JWT token.",
"Return token and expiration."
],
"errors": [
{
"code": "INVALID_CREDENTIALS",
"message": "Invalid email or password."
},
{
"code": "USER_NOT_FOUND",
"message": "User does not exist."
}
],
"side_effects": [],
"dependencies": [
"shared/utils/jwt_generator"
]
}
Why Contracts Matter
- Deterministic — Same spec always produces same contract
- Versionable — Track changes in git
- Auditable — See exactly what changed
- Toolable — Linters and generators consume this
Boundaries: What Slices Can and Cannot Do
Allowed
# ✅ Import from shared modules
from shared.utils.jwt_generator import generate_jwt
from shared.database import get_session
# ✅ Import from same slice
from .schemas import LoginRequest, LoginResponse
from .repository import LoginRepository
Forbidden
# ❌ Import from other slices
from domains.auth.register import RegisterService
# ❌ Import from other domains
from domains.billing.invoice import InvoiceService
Why These Rules?
Allowed imports create explicit, auditable dependencies.
Forbidden imports create hidden coupling that leads to:
- Circular dependencies
- Cascade failures
- Unpredictable regeneration
- Architecture drift
Regeneration: How Code Survives
The Problem
You change the spec. You need to update the code. But you've written custom logic. How do you regenerate without losing it?
The Solution: Markers
# Generated structure (regenerated)
class LoginService:
def __init__(self) -> None:
self.repo = LoginRepository()
# === BEGIN USER CODE ===
def execute(self, request: LoginRequest) -> LoginResponse:
# Your implementation here
user = self.repo.get_user_by_email(request.email)
if not user:
raise HTTPException(status_code=404, detail="USER_NOT_FOUND")
if not verify_password(user.password_hash, request.password):
raise HTTPException(status_code=401, detail="INVALID_CREDENTIALS")
token = generate_jwt(user.id)
return LoginResponse(jwt_token=token, expires_in=3600)
# === END USER CODE ===
What Happens on Regeneration
- Parser reads the spec
- Generator creates new skeleton
- Marker regions are preserved from old file
- New structure + old logic = updated slice
What Gets Regenerated
- Imports
- Class structure
- Method signatures (outside markers)
- Type hints
- Pydantic schemas
What Gets Preserved
- Everything between
BEGIN USER CODEandEND USER CODE - Your business logic
- Your comments
- Your optimizations
The Workflow
Initial Creation
- Create the slice directory with the standard file structure
- Write the spec (
slice.spec.md) — define intent in structured markdown - Generate the contract (
slice.contract.json) — machine-readable version of the spec - Generate the skeleton — handler, service, repository, schemas from the contract
- Implement business logic — write code between
USER CODEmarkers - Run the linter — verify boundary compliance
Updating After Changes
- Edit the spec — modify inputs, outputs, behaviour, or errors
- Regenerate the contract — spec changes propagate to machine-readable format
- Regenerate the skeleton — structure updates, user code is preserved via markers
- Run the linter — verify no boundary violations were introduced
Boundary Enforcement (Linting)
The linter uses AST (Abstract Syntax Tree) analysis to catch violations:
Passing check:
- Slice structure valid (all required files present)
- Contract matches spec
- No boundary violations (no forbidden imports)
- All imports allowed
Failing check example:
Boundary violation in service.py:
Line 15: Illegal import 'domains.billing.invoice'
→ Cannot import from other domains.
Missing contract file:
Expected: domains/auth/login/slice.contract.json
The linter checks slice structure, contract validity, boundary violations, and marker integrity.
Integration with FastAPI
Generated slices integrate directly with FastAPI:
# domains/auth/login/handler.py (generated)
from fastapi import APIRouter, HTTPException
from .schemas import LoginRequest, LoginResponse
from .service import LoginService
router = APIRouter()
@router.post("/login", response_model=LoginResponse)
def handle_login(request: LoginRequest) -> LoginResponse:
service = LoginService()
return service.execute(request)
# main.py (you write)
from fastapi import FastAPI
from domains.auth.login.handler import router as login_router
app = FastAPI()
app.include_router(login_router, prefix="/auth", tags=["auth"])
Shared Modules: When to Use Them
Use Shared Modules For
- Infrastructure — Database connections, config
- External adapters — Email, SMS, payment gateways
- Pure utilities — Date formatting, validation helpers
shared/
├── database.py # DB session management
├── config.py # Environment config
└── adapters/
├── email.py # Email sending
└── sms.py # SMS sending
Never Use Shared Modules For
- Business logic — Belongs in slices
- Domain calculations — Duplicate instead
- Cross-slice communication — Use explicit contracts
Type System
ASA uses a simple type system that maps to Python:
| ASA Type | Python Type | Example |
|---|---|---|
string |
str |
"user@example.com" |
int |
int |
42 |
float |
float |
3.14 |
boolean |
bool |
True |
datetime |
datetime |
datetime.now() |
date |
date |
date.today() |
list<string> |
List[str] |
["a", "b"] |
dict |
Dict |
{"key": "value"} |
optional<string> |
Optional[str] |
None or "value" |
Error Handling
Errors are defined in the spec and enforced in the contract:
## Errors
- INVALID_CREDENTIALS: Invalid email or password.
- USER_NOT_FOUND: User does not exist.
- ACCOUNT_LOCKED: Account locked due to failed attempts.
Generated code includes error constants:
# schemas.py (generated)
class LoginErrors:
INVALID_CREDENTIALS = "INVALID_CREDENTIALS"
USER_NOT_FOUND = "USER_NOT_FOUND"
ACCOUNT_LOCKED = "ACCOUNT_LOCKED"
Your implementation raises them:
# service.py (your code)
if not user:
raise HTTPException(
status_code=404,
detail=LoginErrors.USER_NOT_FOUND
)
Testing Strategy
Each slice has its own test file:
# domains/auth/login/tests/test_slice.py
from ..service import LoginService
from ..schemas import LoginRequest
def test_login_success():
service = LoginService()
request = LoginRequest(
email="user@example.com",
password="correct_password"
)
response = service.execute(request)
assert response.jwt_token is not None
assert response.expires_in == 3600
def test_login_invalid_credentials():
service = LoginService()
request = LoginRequest(
email="user@example.com",
password="wrong_password"
)
with pytest.raises(HTTPException) as exc:
service.execute(request)
assert exc.value.status_code == 401
Tests are isolated. They don't depend on other slices.
Scaling ASA
Small Projects (1-10 slices)
domains/
├── auth/
│ ├── login/
│ └── register/
└── posts/
├── create/
└── list/
Medium Projects (10-50 slices)
domains/
├── auth/
│ ├── login/
│ ├── register/
│ ├── logout/
│ └── reset_password/
├── users/
│ ├── get_profile/
│ ├── update_profile/
│ └── delete_account/
└── billing/
├── create_invoice/
├── list_invoices/
└── process_payment/
Large Projects (50+ slices)
Add subdomains:
domains/
├── auth/
│ ├── core/
│ │ ├── login/
│ │ └── register/
│ └── oauth/
│ ├── google_login/
│ └── github_login/
└── billing/
├── invoices/
│ ├── create/
│ └── list/
└── payments/
├── process/
└── refund/
Common Patterns
Read-Only Slice
# Purpose
Get user profile by ID.
## Inputs
- user_id: string
## Outputs
- user_id: string
- email: string
- name: string
## Behaviour
- Fetch user from database.
- Return user data.
## Errors
- USER_NOT_FOUND: User does not exist.
## Side Effects
None
## Dependencies
None
Write Slice with Side Effects
# Purpose
Create new user account.
## Inputs
- email: string
- password: string
- name: string
## Outputs
- user_id: string
- created_at: datetime
## Behaviour
- Validate email format.
- Hash password.
- Create user record.
- Send welcome email.
## Errors
- EMAIL_ALREADY_EXISTS: Email is already registered.
- INVALID_EMAIL: Email format is invalid.
## Side Effects
- Creates user record in database.
- Sends welcome email.
## Dependencies
- shared/utils/email_sender
- shared/utils/password_hasher
Summary
ASA works through:
- Specs — Human-readable intent
- Contracts — Machine-readable agreements
- Slices — Self-contained implementations
- Boundaries — Enforced isolation
- Markers — Safe regeneration
- Linter — Automatic validation
The result: Maintainable AI-generated code.
Next Steps
- Problems ASA Solves — See real-world impact
- FAQ — Common questions answered
- ASA Standard — Specification on GitHub