This document defines Carbon's stability surfaces. Every AI agent and human contributor MUST respect these contracts when making changes.
| Level | Meaning | What You Can Do |
|---|---|---|
| FROZEN | Never change once shipped | Nothing — add new, don't modify existing |
| STABLE | Additive changes only, deprecation protocol for removals | Add new fields/params. To remove: deprecate → bridge → wait one release |
| ADDITIVE-ONLY | Only add, never rename or remove | Add new columns/tables. Never rename, drop, or narrow types |
- Never rename a column or table in a migration
- Never drop a column that might contain data
- Never narrow a column type (e.g.,
TEXT→VARCHAR(50)) - Adding columns, tables, indexes, and constraints is always safe
- Use
ALTER TABLE ... ADD COLUMNwith defaults or nullable columns
Permission strings like "purchasing", "inventory", "sales", "production" are stored in the database and referenced across the entire codebase as string literals.
- Never rename a permission scope
- Adding new scopes is safe
- If a scope must change, create a new one and keep the old one working via alias
Standard policy names follow the pattern {table}_{SELECT|INSERT|UPDATE|DELETE}.
- Never rename an existing policy without a migration that drops old + creates new
- Keep the naming convention consistent across all tables
Service functions in {module}.service.ts are called from route loaders/actions across the app.
- Add optional parameters (with defaults) freely
- Never remove a parameter or change its type without updating all callers
- Never change return shape without updating all consumers
Route paths are used in redirects, links, and external integrations.
- Never rename a route path without adding a redirect from the old path
- Adding new routes is always safe
Edge functions deployed to Supabase are referenced by name in configuration and triggers.
One remains: embedding.
- Never rename an edge function
- Adding new edge functions is safe
The business functions (get-method, issue, convert, create, the post-* family,
…) are no longer edge functions: they run in the app as @carbon/server-functions and
are reached through the Carbon API's operations (for example
POST /api/v1/sales/upsertQuoteLineMethod). functions.invoke("<name>") for any of
them no longer works.
Event type strings like "purchasing.create" are used in trigger() calls and Inngest function definitions.
- Never rename an event type
- Adding new event types is safe
- Event payload shapes are STABLE — add fields, don't remove them
UI components in @carbon/react are consumed across all apps.
- Add new optional props freely
- Never remove a prop without deprecation
- Never change a prop's type in a breaking way
Import paths like @carbon/auth/auth.server and @carbon/database are used everywhere.
- If an internal file moves, re-export from the old path with
@deprecatedJSDoc - Keep the re-export for at least one release cycle
Zod schemas in {module}.models.ts define form validation and API contracts.
- Add new optional fields freely
- Never remove a required field without making it optional first
- Never change validation rules in a way that rejects previously valid input
When a STABLE surface must change:
- Never remove in a single release
- Add
@deprecatedJSDoc with migration guidance and target removal date - Provide a bridge (re-export, alias, accept old format) for at least one minor version
- Update all internal callers before deprecating
- Document the change in the spec and/or PR description
When an AI agent generates code:
- Read this contract before renaming, removing, or restructuring anything
- If a change touches a FROZEN surface, stop and ask the human
- If a change touches a STABLE surface, follow the deprecation protocol
- If adding new things to an ADDITIVE-ONLY surface, proceed freely
- When in doubt, add new rather than modify existing