Phase 06: Built-in DocTypes & Core Features
Objective: Implement essential built-in DocTypes (User, Role, Permission) and core features like file attachments, audit logging, and printing.
1. Identity & Access Management (PIM)
1.1 Identity Provider Protocol
- Create
IdentityProtocol(Interface):-
get_user(id: str) -> User -
authenticate(credentials) -> Token -
get_attributes(user) -> dict[str, Any](ABAC replacement for get_roles)
-
1.2 User DocTypes (Pluggable)
-
Mode A: Local (Indie):
-
LocalUser(SQL Table): Stores email, password_hash, full_name. -
LocalIdentityAdapter: argon2 password hashing, JWT generation.
-
-
Mode B: Federated (Enterprise):
-
FederatedIdentityAdapter(Proxy):- No SQL Table required.
- Hydrates from Auth Header (
X-User-ID,X-Email) viahydrate_from_headers(). -
UserPreferencesDocType stores settings locally.
-
1.3 User Controller (Adapter)
- Implement
UserManager:- Delegates to configured
IdentityProviderviaIdentityProtocol. - Abstraction layer:
user = await user_manager.get(id). - Additional
create()method for Indie mode with password hashing.
- Delegates to configured
1.4 Auth API (Indie Mode)
- Create
GET /api/v1/auth/me:- Returns current user context (or 401).
- Create
POST /api/v1/auth/login(Indie Only):- Accepts
username,password. - Validates against
LocalUserviaUserManager.authenticate(). - Returns JWT token.
- Accepts
- Create
POST /api/v1/auth/logout:- Returns success message (JWT auth is client-side logout).
1.5 Authentication Strategies (Multi-Method)
[!NOTE] Same user can authenticate via multiple methods (browser, API, CLI). All strategies resolve to the same
UserContext.
-
Create
AuthenticationProtocol(Interface):-
supports(headers: Mapping) -> bool— Can this strategy handle this request? -
authenticate(headers: Mapping) -> UserContext | None— Extract user from request.
-
-
Built-in Strategies:
-
SessionCookieAuth— Browser sessions via cookies. -
BearerTokenAuth— JWT/OAuth2 access tokens. -
ApiKeyAuth—X-API-Keyheader for scripts/integrations. -
HeaderAuth— Federated mode gateway header hydration. -
BasicAuth— HTTP Basic for CLI tools.
-
-
Strategy Chain (
AuthChain):- Try each strategy in priority order.
- First successful match wins.
- Configurable order via
framework_config.toml(create_auth_chain_from_config).
-
API Key DocType (Indie Mode):
-
ApiKeyDocType:-
key_hash: str— Hashed API key (never store plaintext, excluded from serialization). -
user_id: str— Owner of the key. -
name: str— Human-readable label. -
scopes: list[str]— Optional permission scopes. -
expires_at: datetime | None— Expiration. -
last_used_at: datetime | None— Usage tracking.
-
-
POST /api/v1/auth/api-keys— Create new key. -
DELETE /api/v1/auth/api-keys/{id}— Revoke key. -
GET /api/v1/auth/api-keys— List user's keys.
-
1.6 Social Login (OAuth2/OIDC)
[!IMPORTANT] Social login helps Indies avoid password storage and PII liability.
-
Option A: Built-in OAuth2 (Indie):
- Use
authlibfor OAuth2 flows. (Full implementation deferred) - Supported Providers (config structure ready):
- Google (well-known URLs configured)
- GitHub (well-known URLs configured)
- Microsoft (well-known URLs configured)
- Generic OIDC (any compliant provider via
get_oidc_well_known)
- Configuration via
framework_config.toml:[auth.oauth]
enabled = true
providers = ["google", "github"]
[auth.oauth.google]
client_id = "${GOOGLE_CLIENT_ID}"
client_secret = "${GOOGLE_CLIENT_SECRET}"
# Generic OIDC provider example
[auth.oauth.keycloak]
client_id = "${KEYCLOAK_CLIENT_ID}"
client_secret = "${KEYCLOAK_CLIENT_SECRET}"
authorization_url = "https://keycloak.example.com/auth/realms/myrealm/protocol/openid-connect/auth"
token_url = "https://keycloak.example.com/auth/realms/myrealm/protocol/openid-connect/token"
userinfo_url = "https://keycloak.example.com/auth/realms/myrealm/protocol/openid-connect/userinfo"
scope = "openid email profile" - Endpoints:
-
GET /api/v1/auth/oauth/{provider}/start— Redirect to provider. -
GET /api/v1/auth/oauth/{provider}/callback— Handle callback. (Placeholder)
-
- Use
-
Option B: Federated (Enterprise):
- Delegate to Keycloak / Authelia / Auth0 via
HeaderAuthstrategy. - Framework receives headers only (
X-User-ID,X-Email). - Zero PII stored locally.
- Delegate to Keycloak / Authelia / Auth0 via
-
SocialAccount DocType:
-
provider: str— OAuth provider name. -
provider_user_id: str— Unique ID from provider. -
user_id: str— Links to local user. -
email: str | None— For lookup (optional). -
display_name: str— For UI display. - One User can have multiple SocialAccounts.
-
-
Passwordless Option (Requires Section 10: Email):
-
POST /api/v1/auth/magic-link— Send email with login link. -
GET /api/v1/auth/magic-link/{token}— Verify and create session. -
MagicLinkCredentialsadded to identity protocol - Token generation with HMAC-SHA256 signing
-
1.7 Session Management
-
Session Storage:
- Redis (Default):
RedisSessionAdapterwith JSON storage and TTL. - Database (Fallback):
DatabaseSessionAdapterfor indie apps. - Configurable via
framework_config.toml:[auth.session]
backend = "redis" # or "database"
ttl_seconds = 86400 # 24 hours
cookie_name = "session_id"
cookie_secure = true
cookie_httponly = true
cookie_samesite = "lax"
- Redis (Default):
-
Session DocType (database backend):
-
session_id: str— Unique session identifier. -
user_id: str— Links to user. -
expires_at: datetime -
ip_address: str | None -
user_agent: str | None
-
-
SessionProtocol Interface:
-
create()— Create new session -
get()— Retrieve by ID -
delete()— Remove session -
delete_all_for_user()— Logout all -
list_for_user()— List active sessions
-
-
Session API:
-
GET /api/v1/auth/sessions— List active sessions. -
DELETE /api/v1/auth/sessions/{id}— Revoke session. -
DELETE /api/v1/auth/sessions— Logout all.
-
1.8 PII Handling (Indie Guidance)
[!TIP] Store less PII = Less GDPR/compliance burden.
-
Auth Mode Presets (
AuthModeenum incore/pii.py):[auth]
mode = "social_only" # No local passwords, minimal PII
# OR
mode = "passwordless" # Email magic links only
# OR
mode = "local" # Traditional username/password
# OR
mode = "federated" # External IdP, zero local PII -
Minimal PII Pattern:
-
LocalUserstores only:-
email(for login/notification) -
display_name(for UI) -
password_hash(only ifmode = "local")
-
- Do NOT store by default (
PII_SENSITIVE_FIELDS):- Full legal name
- Phone number
- Address
- Date of birth
- Helper:
is_sensitive_pii(field_name)to detect sensitive fields.
-
-
Data Deletion (
DeletionModeenum):-
DELETE /api/v1/auth/me— Delete user account (GDPR right to erasure). -
GET /api/v1/auth/me/data— Export user data (GDPR data portability). - Configurable: Hard delete vs anonymize.
[auth.deletion]
mode = "hard_delete" # or "anonymize"
-
2. Tenancy & Attributes (Multi-Tenant Core)
2.1 Tenant Protocol
- Create
TenantProtocol(Interface incore/interfaces/tenant.py):-
get_current_tenant() -> str -
get_tenant_attributes(tenant_id) -> dict -
TenantContextmodel for request context - Configuration helpers:
is_multi_tenant(),get_default_tenant_id()
-
2.2 Tenancy Adapters
-
Mode A: Single Tenant (Indie):
-
ImplicitTenantAdapter(adapters/tenant.py):-
tenant_id="default"(configurable). -
attributes={"plan": "unlimited", "features": "*"}. -
get_context()returnsTenantContextwithis_default=True.
-
- Factory:
create_tenant_adapter_from_headers()auto-selects.
-
-
Mode B: Multi-Tenant (Enterprise):
-
HeaderTenantAdapter(adapters/tenant.py):- Extracts
X-Tenant-IDfrom Gateway headers. - Extracts
X-Tenant-Attributes(JSON) for feature flags, plan. -
get_context()parses headers intoTenantContext. - Benefit: Feature toggling per tenant without DB lookup.
- Extracts
-
2.3 Attributes (The New Roles)
- Goal: Replace "Roles" with refined "Attributes" (ABAC).
- Implementation (in
UserContext):-
user.attributes(dict): Stores{"department": "sales", "level": 5, "roles": ["admin"]}. -
user.get_attribute(key, default)— Get attribute value. -
user.has_attribute(key, value)— Check attribute equality. - Legacy Support:
user.has_role("x")checks bothroleslist ANDattributes.roles. -
is_system_useruseshas_role("System")for consistency.
-
3. Permission Management (Pluggable)
3.1 Permission Protocol
- Create
PermissionProtocol(core/interfaces/permission.py):-
PolicyEvaluateRequest- Stateless authorization request -
PolicyEvaluateResult- Result with authorized, decision_source, reason -
async def evaluate(request) -> PolicyEvaluateResult -
async def get_permitted_filters(...) -> dict- RLS support -
PermissionActionenum (read, write, create, delete, submit, cancel, amend) -
DecisionSourceenum (rbac, abac, rebac, combo, custom)
-
3.2 Permission Adapters
-
Mode A: Standard (Indie) -
RbacPermissionAdapter:- Reads permissions from
DocType.Meta.permissions. - Checks
CustomPermissionrules from database. - Role matching against
principal_attributes["roles"]. - Admin role bypass (configurable).
- RLS via
get_permitted_filters()usingMeta.rls_field.
- Reads permissions from
-
Mode B: Citadel Policy (Enterprise) -
CitadelPolicyAdapter:-
AuthorizationRequestmodel (Cedar-compatible format):-
Principal:user.id -
Action:doctype:action(e.g.,Invoice:create) -
Resource:doctype:name(e.g.,Invoice:INV-001) -
TenantID:user.tenant_id -
Context:doc.as_dict() -
PrincipalAttributes:user.attributes(Diet Claims)
-
-
AuthorizationResponsemodel withallowed,reason,policy_id - Calls configurable policy endpoint (Cedar/OPA)
- Fallback behavior on error (configurable)
- Configuration via
[permissions.citadel]inframework_config.toml
-
4. File Management
4.1 File DocType
- Create
src/framework_m/core/doctypes/file.py - Define
FileDocType:-
name: str- File ID (inherited from BaseDocType) -
file_name: str- Original filename -
file_url: str- Storage URL -
file_size: int- Size in bytes -
content_type: str- MIME type -
attached_to_doctype: str | None -
attached_to_name: str | None -
is_private: bool- Private/public file (default: True)
-
4.2 File Upload API
- Create
POST /api/v1/file/upload(adapters/web/file_routes.py):- Accept multipart/form-data via
UploadFile - Validate file size (max 10MB default, configurable)
- Validate file type (configurable whitelist)
- Generate unique storage path (YYYY/MM/DD/random_filename)
- Create File DocType record
- Return file URL and metadata
- Accept multipart/form-data via
- Configuration via
[files]inframework_config.toml - Response models:
FileUploadResponse,FileDeleteResponse
4.3 File Download API
- Create
GET /api/v1/file/{file_id}:- Check permissions for private files (RLS on owner)
- Prepare streaming response from storage
- Set correct
Content-Typeheader - Set
Content-Dispositionheader (inline/attachment) - Support
?inline=truefor browser display
- Create
GET /api/v1/file/{file_id}/info:- Return metadata without downloading content
4.4 Storage Adapters
-
Implement
LocalStorageAdapter(adapters/storage/local.py):- Save files to local directory with atomic writes
- Organize by date (YYYY/MM/DD) via path parameter
- Generate unique filenames (handled by file_routes)
- Path traversal protection
- Async operations via
aiofiles
-
Implement
S3StorageAdapter(adapters/storage/s3.py):- Use
aioboto3for async S3 operations - Presigned URLs for direct client uploads (PUT method)
- Multipart upload for large files (>25MB)
- Configure bucket and region from
framework_config.toml - Support S3-compatible services (MinIO, DigitalOcean Spaces)
- Use
-
InMemoryStorageAdapterfor testing (adapters/storage/memory.py)
5. Audit Logging (Pluggable)
5.1 Audit Log Protocol
- Create
AuditLogProtocol(core/interfaces/audit.py):-
AuditEntrymodel (user, action, doctype, document_id, changes, metadata) -
async def log(...)- Record audit entry -
async def query(...)- Query with filters/pagination -
InMemoryAuditAdapterfor testing
-
5.2 Audit Adapters
-
Mode A: Database (Indie):
-
DatabaseAuditAdapter: Writes toActivityLogrecords. - Uses ActivityLog DocType from 5.3
-
-
Mode B: External (Enterprise):
-
FileAuditAdapter: Writes toaudit.log(JSONL) for Splunk/Filebeat. -
ElasticAuditAdapter: Writes directly to Elasticsearch (Phase 10). - Prevents main DB bloat.
-
5.3 Activity Log DocType (Indie Only)
- Create
src/framework_m/core/doctypes/activity_log.py - Define
ActivityLogDocType:-
user_id: str -
action: str(create, read, update, delete) -
doctype: str -
document_id: str -
timestamp: datetime -
changes: dict | None -
metadata: dict | None - Immutable (no write/delete permissions)
-
5.4 Activity Feed API
- Create
GET /api/v1/activity:- List recent activities
- Filter by user, doctype, document, action
- Paginate results (limit, offset)
6. Error Logging
6.1 Error Log DocType
- Create
src/framework_m/core/doctypes/error_log.py - Define
ErrorLogDocType:-
title: str- Short error description -
error_type: str- Exception class name -
error_message: str- Full error message -
traceback: str | None- Full stack trace -
request_url: str | None -
user_id: str | None -
request_id: str | None- For log correlation -
timestamp: datetime -
context: dict | None- Additional context - Admin-only permissions (immutable)
-
6.2 Error Logging Integration
- Add global exception handler (
adapters/web/error_handler.py):- Catch all unhandled exceptions
- Create ErrorLog entry with full context
- Return user-friendly ErrorResponse (no sensitive data)
- Log to console in dev mode (configurable)
-
create_error_handler()factory for Litestar
7. Printing & PDF Generation
7.1 Print Format DocType
- Create
src/framework_m/core/doctypes/print_format.py - Define
PrintFormatDocType:-
name: str- Display name -
doctype: str- Target DocType -
template: str- Jinja2 template path -
is_default: bool -
css: str | None- Custom CSS -
header_html: str | None,footer_html: str | None -
page_size: str(A4, Letter, etc.) -
orientation: str(portrait, landscape)
-
7.2 Jinja Print Adapter
- Create
src/framework_m/adapters/print/jinja_adapter.py - Implement
JinjaPrintAdapter:-
async def render_html(doc, template) -> str- Load Jinja2 template
- Render with document context
- Return HTML string
-
async def render_html_string(template_string, doc) -> str - Custom filters:
currency,date,datetime
-
7.3 Gotenberg PDF Adapter
- Create
src/framework_m/adapters/print/gotenberg_adapter.py - Implement
GotenbergPrintAdapter:-
async def html_to_pdf(html, page_size, orientation, ...) -> bytes- Send HTML to Gotenberg service
- Return PDF bytes
-
async def is_available() -> bool- Health check
-
-
GotenbergConfigwith URL, timeout, page size, margins - Page sizes: A4, Letter, Legal, A3, A5
7.4 Print API
- Create
GET /api/v1/print/{doctype}/{id}:- Query parameters:
-
format- pdf/html (default: pdf) -
print_format- Custom format name
-
- Load document (TODO: integrate with repository - phase 10)
- Check read permission (TODO: integrate with permission - phase 10)
- Render using JinjaPrintAdapter → GotenbergPrintAdapter
- Return PDF or HTML with appropriate headers
- Fallback to HTML if Gotenberg unavailable
- Query parameters:
8. System Settings
8.1 System Settings DocType
- Create
src/framework_m/core/doctypes/system_settings.py - Define
SystemSettingsDocType (singleton):-
name: str- Always "System Settings" (frozen) -
app_name: str- Application display name -
timezone: str- Default timezone -
date_format: str- Date format string -
time_format: str- Time format string -
language: str- Default language code -
enable_signup: bool- Allow registration -
session_expiry: int- Session timeout in minutes -
maintenance_mode: bool- Admin-only access
-
8.2 Settings API
- Create
GET /api/v1/settings:- Return system settings via
SettingsResponse - Cache aggressively (5 min Cache-Control header)
-
invalidate_settings_cache()helper -
set_settings_cache()helper
- Return system settings via
9. Notification System
9.1 Notification DocType
- Create
src/framework_m/core/doctypes/notification.py - Define
NotificationDocType:-
user_id: str- Recipient -
subject: str -
message: str -
notification_type: str(info, success, warning, error, etc.) -
read: bool -
doctype: str | None- Related DocType -
document_id: str | None -
timestamp: datetime -
from_user: str | None -
metadata: dict | None - RLS on user_id field
-
9.2 Notification API
-
Create
GET /api/v1/notifications:- List user's notifications
- Filter by read/unread
-
Create
PATCH /api/v1/notifications/{id}/read- Mark as read -
Create
DELETE /api/v1/notifications/{id}- Delete notification -
Create WebSocket endpoint for real-time notifications:
-
WS /api/v1/notifications/stream- Real-time notification stream -
push_notification(user_id, notification)- Push to connected clients -
create_notification_websocket_router()factory
-
10. Email Integration
10.1 Email Queue DocType
-
Create
src/framework_m/core/doctypes/email_queue.py -
Define
EmailQueueDocType:-
to: list[str]- Recipients -
cc/bcc: list[str] | None -
subject: str -
body: str- HTML body -
text_body: str | None- Plain text alt -
status: str- Queued/Sending/Sent/Failed/Cancelled -
priority: str- low/normal/high -
error: str | None -
retry_count/max_retries: int -
queued_at/sent_at: datetime -
reference_doctype/reference_id- for tracking
-
-
Port-Adapter Pattern:
-
EmailQueueProtocol(interface/port) -
DatabaseEmailQueueAdapter(default - uses EmailQueue DocType) -
InMemoryEmailQueueAdapter(testing) -
queue_email()helper function -
configure_email_queue()for swapping adapters - Ready for
NotificationServiceAdapterintegration
-
10.2 Email Sender
-
Create email sender protocol and adapters:
-
EmailSenderProtocol(interface/port) -
SMTPEmailSender- SMTP delivery adapter -
LogEmailSender- Console logging (development) -
SMTPConfig- Configuration dataclass
-
-
Create background job
EmailProcessor:-
process_queue()- Process pending emails -
process_single(queue_id)- Process single email - Update EmailQueue status
- Retry logic (placeholder)
-
-
Add helper function:
-
queue_email()- Queue for sending -
send_queued_email()- Process single email
-
11. Testing
11.1 Unit Tests
- Test User DocType validation (
core/doctypes/test_user.py,core/services/test_user_manager.py) - Test Role assignment (
adapters/auth/test_rbac_permission.py) - Test Permission checking (8 permission test files)
- Test File upload/download (
core/test_file.py,adapters/web/test_file_routes.py) - Test Print rendering (
core/test_print_format.py,adapters/print/test_*.py)
11.2 Integration Tests
- Test user creation and login (
integration/test_api_endpoints.py) - Test permission enforcement (
core/test_object_level_permissions.py) - Test file storage (local and S3) (
adapters/storage/test_local.py,adapters/storage/test_s3.py) - Test PDF generation with Gotenberg (
adapters/print/test_gotenberg_adapter.py) - Test email sending (
adapters/email/test_sender_processor.py)
Validation Checklist
Before moving to Phase 07, verify:
- User and Role management works
- Permissions are enforced correctly
- Files can be uploaded and downloaded
- Audit logs are created
- PDFs can be generated
- Emails can be sent
Anti-Patterns to Avoid
❌ Don't: Store files in database ✅ Do: Use storage adapter (local/S3)
❌ Don't: Generate PDFs in main process ✅ Do: Use microservice (Gotenberg)
❌ Don't: Send emails synchronously ✅ Do: Queue emails as background jobs
❌ Don't: Hardcode system settings ✅ Do: Store in SystemSettings DocType