ASAASA Standard

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

  1. Parser reads the spec
  2. Generator creates new skeleton
  3. Marker regions are preserved from old file
  4. 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 CODE and END USER CODE
  • Your business logic
  • Your comments
  • Your optimizations

The Workflow

Initial Creation

  1. Create the slice directory with the standard file structure
  2. Write the spec (slice.spec.md) — define intent in structured markdown
  3. Generate the contract (slice.contract.json) — machine-readable version of the spec
  4. Generate the skeleton — handler, service, repository, schemas from the contract
  5. Implement business logic — write code between USER CODE markers
  6. Run the linter — verify boundary compliance

Updating After Changes

  1. Edit the spec — modify inputs, outputs, behaviour, or errors
  2. Regenerate the contract — spec changes propagate to machine-readable format
  3. Regenerate the skeleton — structure updates, user code is preserved via markers
  4. 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:

  1. Specs — Human-readable intent
  2. Contracts — Machine-readable agreements
  3. Slices — Self-contained implementations
  4. Boundaries — Enforced isolation
  5. Markers — Safe regeneration
  6. Linter — Automatic validation

The result: Maintainable AI-generated code.


Next Steps