Progressive Ownership
Owner
@engineering
Last verified
2025-10-13
Sources of truth
- doc: docs/ownership-manifest.md
- test: tests/unit/ownership-manifest.test.ts
- test: tests/unit/extended-merge.test.ts
---
Table of Contents
---
Overview
Progressive Ownership is Weldr's core innovation that solves the vendor lock-in problem in AI code generation.The Problem
Traditional AI code generators force binary choices:
The Solution
Progressive Ownership gives you three modes per surface, allowing granular control:
Key Principles
---
The Three Ownership Modes
Mode 1: Managed
Definition: Weldr owns the code and regenerates it freely. When to use:# weldr.ownership.yaml
surfaces:
db: managed
// drizzle/schema.ts
/** @weldr surface: db mode: managed **/
import { pgTable, uuid, text, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
email: text('email').notNull().unique(),
name: text('name').notNull(),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow()
});
avatarUrl: text('avatar_url')
to schema---
Mode 2: Extended
Definition: Shared ownership - generated code in@weldr
blocks, custom code in @custom
blocks.
When to use:
surfaces:
api:
mode: extended
// lib/server/actions/users.ts
/** @weldr surface: api mode: extended **/
// @weldr:begin crud-user
export async function createUser(input: CreateUserInput) {
// Weldr regenerates this block
return await db.insert(users).values(input);
}
export async function getUser(id: string) {
return await db.query.users.findFirst({
where: eq(users.id, id)
});
}
export async function updateUser(id: string, input: UpdateUserInput) {
return await db.update(users)
.set(input)
.where(eq(users.id, id));
}
// @weldr:end crud-user
// @custom:begin user-helpers
export async function createUserWithWorkspace(input: CreateUserInput) {
// YOUR code - this block stays untouched by Weldr
const user = await createUser(input);
// Create default workspace
await db.insert(workspaces).values({
userId: user.id,
name: `${user.name}'s Workspace`,
isDefault: true
});
// Send welcome email
await sendEmail({
to: user.email,
template: 'welcome',
data: { name: user.name }
});
return user;
}
export async function getUserWithWorkspaces(id: string) {
return await db.query.users.findFirst({
where: eq(users.id, id),
with: { workspaces: true }
});
}
// @custom:end user-helpers
@weldr:begin crud-user
blockresetPassword
function in @weldr
block@custom
blocks---
Mode 3: Ejected
Definition: You own the code completely. Weldr validates contracts but skips regeneration. When to use:surfaces:
domain_logic:
mode: ejected
entrypoints:
- src/domain/pricing.ts
contracts:
- id: domain.pricing
surface: domain_logic
module: src/domain/pricing.ts
exports:
- name: calculatePrice
kind: value
type: '(params: PriceParams) => Promise<number>'
- name: PricingTier
kind: type
// src/domain/pricing.ts (YOU own this)
export type PricingTier = 'free' | 'starter' | 'pro' | 'enterprise';
export interface PriceParams {
tier: PricingTier;
seats: number;
billingCycle: 'monthly' | 'annual';
}
export async function calculatePrice(params: PriceParams): Promise<number> {
// YOUR complex pricing logic
// Weldr leaves this file untouched
const basePrices = {
free: 0,
starter: 49,
pro: 199,
enterprise: 999
};
let total = basePrices[params.tier];
if (params.seats > 1) {
total += (params.seats - 1) * getSeatPrice(params.tier);
}
if (params.billingCycle === 'annual') {
total = total * 12 * 0.83; // 17% discount
}
return total;
}
function getSeatPrice(tier: PricingTier): number {
// Helper function (not exported, not in contract)
const prices = { free: 0, starter: 10, pro: 30, enterprise: 50 };
return prices[tier];
}
$ weldr contracts check
✅ domain.pricing
✅ Export 'calculatePrice' exists
✅ Type signature matches: (params: PriceParams) => Promise<number>
✅ Export 'PricingTier' exists
✅ Type matches
// lib/contracts/domain/pricing.ts (auto-generated by Weldr)
/** @weldr surface: domain_logic mode: managed **/
export { calculatePrice, PricingTier } from '@/src/domain/pricing';
lib/server/workflows/checkout.ts
import { domainPricing } from '@/lib/contracts'
src/domain/pricing.ts
still satisfies contract---
The Nine Surfaces
Weldr organizes code into 9 surfaces, each with independent ownership mode:
1. app
- Application Routing
Content:
layout.tsx
)page.tsx
)managed
Why: Routing is boilerplate, let Weldr handle it
Example:
app/
├── (auth)/
│ ├── login/
│ │ └── page.tsx # Login page
│ └── signup/
│ └── page.tsx # Signup page
├── (dashboard)/
│ ├── layout.tsx # Dashboard layout
│ ├── projects/
│ │ └── page.tsx # Projects list
│ └── tasks/
│ └── page.tsx # Tasks list
└── layout.tsx # Root layout
---
2. db
- Database Schema & Migrations
Content:
drizzle/schema.ts
)drizzle/migrations/
)drizzle/schema-relations.ts
)managed
(unless exotic types needed)
Why: Schema changes should propagate everywhere (CRUD, forms, types)
Example:
// drizzle/schema.ts
export const projects = pgTable('projects', {
id: uuid('id').primaryKey().defaultRandom(),
name: text('name').notNull(),
status: text('status', {
enum: ['active', 'archived', 'on_hold']
}).default('active'),
ownerId: uuid('owner_id').references(() => users.id),
createdAt: timestamp('created_at').defaultNow()
});
---
3. auth
- Authentication
Content:
lib/auth/
)managed
(unless HIPAA/custom requirements)
Why: Auth is standardized, security-critical (better generated)
When to eject:
---
4. api
- HTTP Endpoints & Server Actions
Content:
lib/server/actions/
)app/api/
)extended
(most common customization point)
Why: Need both CRUD (generated) and custom logic (your code)
Example:
surfaces:
api:
mode: extended
// lib/server/actions/projects.ts
/** @weldr surface: api mode: extended **/
// @weldr:begin crud-project
export async function createProject(input: CreateProjectInput) {
return await db.insert(projects).values(input);
}
// @weldr:end crud-project
// @custom:begin project-archive
export async function archiveProject(id: string) {
// Soft delete + cleanup logic
await db.update(projects)
.set({ status: 'archived', archivedAt: new Date() })
.where(eq(projects.id, id));
// Unassign all tasks
await db.update(tasks)
.set({ assigneeId: null })
.where(eq(tasks.projectId, id));
// Notify team
await notifyTeam(id, 'project_archived');
}
// @custom:end project-archive
---
5. domain_logic
- Business Logic & Algorithms
Content:
ejected
(your competitive advantage)
Why: Business logic IS your product, you should own it
Example:
surfaces:
domain_logic:
mode: ejected
entrypoints:
- src/domain/pricing.ts
- src/domain/matching.ts
- src/domain/analytics.ts
---
6. ui
- React Components
Content:
components/
)extended
(mix standard + custom components)
Why: Standard components (forms, tables) can be generated, custom components are yours
Example:
surfaces:
ui:
mode: extended
// components/project-card.tsx
/** @weldr surface: ui mode: extended **/
// @weldr:begin project-card-base
export function ProjectCard({ project }: { project: Project }) {
return (
<Card>
<CardHeader>
<CardTitle>{project.name}</CardTitle>
</CardHeader>
<CardContent>
<p>Status: {project.status}</p>
<p>Owner: {project.owner.name}</p>
</CardContent>
</Card>
);
}
// @weldr:end project-card-base
// @custom:begin project-card-actions
export function ProjectCardWithActions({ project }: { project: Project }) {
const [isArchiving, setIsArchiving] = useState(false);
const handleArchive = async () => {
setIsArchiving(true);
await archiveProject(project.id);
setIsArchiving(false);
toast.success('Project archived');
};
return (
<Card>
<ProjectCard project={project} />
<CardFooter>
<Button onClick={handleArchive} disabled={isArchiving}>
Archive
</Button>
</CardFooter>
</Card>
);
}
// @custom:end project-card-actions
---
7. styling
- CSS & Design System
Content:
ejected
(your brand)
Why: Branding is unique, design systems are custom
Example:
surfaces:
styling:
mode: ejected
entrypoints:
- tailwind.config.ts
- app/globals.css
---
8. infra
- Infrastructure & Middleware
Content:
middleware.ts
)lib/utils/
)managed
(unless custom monitoring)
Why: Infrastructure is boilerplate
When to extend:
---
9. ci
- CI/CD & Deployment
Content:
.github/workflows/
)vercel.json
, Dockerfile
)managed
(unless custom pipeline)
Why: CI/CD is standardized
When to eject:
---
Ownership Manifest
The ownership manifest (weldr.ownership.yaml
) is the single source of truth for ownership decisions.
Schema
version: ownership/v2
surfaces:
# Simple surface (basic mode)
db: managed
# Surface with options
api:
mode: extended
# Ejected surface with entrypoints
domain_logic:
mode: ejected
entrypoints:
- src/domain/pricing.ts
- src/domain/matching.ts
contracts:
# Contract definition
- id: domain.pricing
surface: domain_logic
module: src/domain/pricing.ts
exports:
- name: calculatePrice
kind: value
type: '(params: PriceParams) => Promise<number>'
- name: PricingTier
kind: type
Validation
Weldr validates the manifest on every operation:
$ weldr doctor
✅ Ownership Manifest (weldr.ownership.yaml)
✅ Version: ownership/v2
✅ 9 surfaces defined
✅ 2 contracts defined
✅ Surface Status
✅ app: managed (12 files)
✅ db: managed (4 files)
✅ auth: managed (8 files)
✅ api: extended (15 files, 6 custom blocks)
✅ domain_logic: ejected (3 modules, 2 contracts)
✅ ui: extended (24 files, 12 custom blocks)
✅ styling: ejected (2 files)
✅ infra: managed (5 files)
✅ ci: managed (3 files)
✅ Contracts
✅ domain.pricing (src/domain/pricing.ts)
✅ calculatePrice: (params: PriceParams) => Promise<number>
✅ PricingTier: type
✅ domain.matching (src/domain/matching.ts)
✅ findMatches: (userId: string) => Promise<Match[]>
---
File Lease System
Every generated file has an ownership header (lease) that declares its surface and mode.
Lease Format
/** @weldr surface: api mode: managed **/
or
/** @weldr surface: ui mode: extended **/
Lease Extraction
Weldr reads leases to determine ownership:
// lib/ownership/lease.ts
export interface FileLease {
surface: SurfaceId;
mode: OwnershipMode;
}
export function extractLease(content: string): FileLease | null {
const regex = /\/\*\*\s*@weldr\s+surface:\s*(\w+)\s+mode:\s*(\w+)\s*\*\*\//;
const match = content.match(regex);
if (!match) return null;
return {
surface: match[1] as SurfaceId,
mode: match[2] as OwnershipMode
};
}
Lease Verification
$ weldr leases verify
✅ All file leases match manifest
Checked 78 files:
✅ 45 managed files
✅ 23 extended files
✅ 10 ejected files (no leases, as expected)
---
Extended Merge Engine
The Extended merge engine preserves @custom
blocks during regeneration.
Block Syntax
// @weldr:begin block-name
// Generated code
// @weldr:end block-name
// @custom:begin custom-block-name
// Your code
// @custom:end custom-block-name
Merge Algorithm
3-way merge:@custom
blocks from current file@weldr
blocks from new generated version@weldr
blocks, preserve @custom
blocksConflict Detection
Orphaned block:// @custom:begin old-function
// This function was removed from generated code
// @custom:end old-function
⚠️ Orphaned custom block detected: old-function
File: lib/server/actions/users.ts
Suggestion: Remove or refactor this block
// @custom:begin missing-end
// Code with no closing tag
❌ Malformed custom block: missing-end
File: lib/server/actions/users.ts
Error: No closing tag found
Example Merge
Before regeneration:/** @weldr surface: api mode: extended **/
// @weldr:begin crud-user
export async function createUser(input: CreateUserInput) {
return await db.insert(users).values(input);
}
// @weldr:end crud-user
// @custom:begin user-helpers
export async function createUserWithWorkspace(input: CreateUserInput) {
const user = await createUser(input);
await createWorkspace(user.id);
return user;
}
// @custom:end user-helpers
// @weldr:begin crud-user
export async function createUser(input: CreateUserInput) {
// New validation logic
validateEmail(input.email);
return await db.insert(users).values({
...input,
avatarUrl: input.avatarUrl || '/default-avatar.png' // New field
});
}
// @weldr:end crud-user
/** @weldr surface: api mode: extended **/
// @weldr:begin crud-user
export async function createUser(input: CreateUserInput) {
// New validation logic
validateEmail(input.email);
return await db.insert(users).values({
...input,
avatarUrl: input.avatarUrl || '/default-avatar.png' // New field
});
}
// @weldr:end crud-user
// @custom:begin user-helpers
export async function createUserWithWorkspace(input: CreateUserInput) {
const user = await createUser(input);
await createWorkspace(user.id);
return user;
}
// @custom:end user-helpers
@weldr
block updated, @custom
block preserved.
---
Contract Validation
For Ejected surfaces, Weldr uses the TypeScript compiler API to validate contracts.
Contract Definition
contracts:
- id: domain.pricing
surface: domain_logic
module: src/domain/pricing.ts
exports:
- name: calculatePrice
kind: value
type: '(params: PriceParams) => Promise<number>'
- name: PricingTier
kind: type
Validation Process
Step 1: Create virtual module that imports contract// temp-contract-validator.ts (in-memory)
import { calculatePrice, PricingTier } from './src/domain/pricing';
// Type assertions to validate signatures
const _calculatePriceCheck: (params: PriceParams) => Promise<number> = calculatePrice;
const _pricingTierCheck: PricingTier = 'free';
import ts from 'typescript';
const program = ts.createProgram([
'temp-contract-validator.ts'
], {
moduleResolution: ts.ModuleResolutionKind.Bundler,
strict: true,
skipLibCheck: true
});
const diagnostics = ts.getPreEmitDiagnostics(program);
if (diagnostics.length > 0) {
// Type mismatch detected
return {
valid: false,
errors: diagnostics.map(d => ({
message: d.messageText,
contractId: 'domain.pricing',
exportName: 'calculatePrice'
}))
};
}
return { valid: true };
Example Validation
Contract satisfied:$ weldr contracts check
✅ domain.pricing
✅ Export 'calculatePrice' exists
✅ Type signature matches
// src/domain/pricing.ts
export async function calculatePrice(params: PriceParams): Promise<string> {
// Returns string instead of number
return "$49";
}
$ weldr contracts check
❌ domain.pricing
❌ Export 'calculatePrice' type mismatch
Expected: (params: PriceParams) => Promise<number>
Actual: (params: PriceParams) => Promise<string>
Suggestion: Update src/domain/pricing.ts to return Promise<number>
---
Contract Adapters
Weldr auto-generates adapters for ejected modules to integrate them with generated code.
Adapter Structure
For contract:contracts:
- id: domain.pricing
surface: domain_logic
module: src/domain/pricing.ts
exports:
- name: calculatePrice
- name: PricingTier
// lib/contracts/domain/pricing.ts (auto-generated)
/** @weldr surface: domain_logic mode: managed **/
// Re-export from ejected module
export { calculatePrice, PricingTier } from '@/src/domain/pricing';
// lib/contracts/index.ts (auto-generated)
/** @weldr surface: domain_logic mode: managed **/
export * as domainPricing from './domain/pricing';
export * as domainMatching from './domain/matching';
Usage in Generated Code
// lib/server/workflows/checkout.ts (generated by Weldr)
import { domainPricing } from '@/lib/contracts';
export async function checkoutFlow(input: CheckoutInput) {
// Uses adapter (which re-exports from src/domain/pricing.ts)
const price = await domainPricing.calculatePrice({
tier: input.tier,
seats: input.seats,
billingCycle: input.billingCycle
});
// Create Stripe session with calculated price
const session = await stripe.checkout.sessions.create({
line_items: [{
price_data: {
currency: 'usd',
unit_amount: Math.round(price * 100)
},
quantity: 1
}]
});
return session;
}
lib/contracts
, not src/domain
src/domain/pricing.ts
, update adapter, not all importsdomainPricing
, domainMatching
, etc.)---
CLI Tools
weldr doctor
Shows ownership status and diagnostics
$ weldr doctor
✅ Ownership Manifest
✅ Version: ownership/v2
✅ 9 surfaces defined
✅ 2 contracts defined
✅ Surface Status
✅ app: managed (12 files)
✅ db: managed (4 files)
✅ api: extended (15 files, 6 custom blocks)
✅ domain_logic: ejected (3 modules)
✅ Contracts
✅ domain.pricing ✅
✅ domain.matching ✅
⚠️ Warnings
⚠️ api surface has 2 orphaned custom blocks
- lib/server/actions/users.ts: old-function
- lib/server/actions/projects.ts: deprecated-helper
---
weldr leases verify
Verifies file leases match manifest
$ weldr leases verify
✅ All file leases match manifest
Checked 78 files:
✅ 45 managed files have correct lease headers
✅ 23 extended files have correct lease headers
✅ 10 ejected files (no leases required)
Files by surface:
app: 12 files (all managed)
db: 4 files (all managed)
auth: 8 files (all managed)
api: 15 files (all extended)
domain_logic: 3 files (all ejected)
ui: 24 files (20 extended, 4 managed)
styling: 2 files (all ejected)
infra: 5 files (all managed)
ci: 3 files (all managed)
---
weldr contracts check
Validates all contracts using TypeScript compiler
$ weldr contracts check
Checking 2 contracts...
✅ domain.pricing (src/domain/pricing.ts)
✅ Export 'calculatePrice' exists
✅ Type: (params: PriceParams) => Promise<number>
✅ Export 'PricingTier' exists
✅ Type: type
✅ domain.matching (src/domain/matching.ts)
✅ Export 'findMatches' exists
✅ Type: (userId: string, limit?: number) => Promise<Match[]>
✅ All contracts satisfied
---
weldr sync
Syncs ownership state and regenerates
$ weldr sync
🔄 Syncing ownership state...
1. Loading manifest...
2. Verifying leases...
3. Checking contracts...
4. Regenerating managed surfaces...
✅ app (12 files regenerated)
✅ db (1 migration generated)
✅ auth (0 changes)
5. Merging extended surfaces...
✅ api (15 files merged, 6 custom blocks preserved)
✅ ui (24 files merged, 12 custom blocks preserved)
6. Validating ejected contracts...
✅ domain.pricing
✅ domain.matching
7. Generating contract adapters...
✅ lib/contracts/domain/pricing.ts
✅ lib/contracts/domain/matching.ts
✅ lib/contracts/index.ts
✅ Sync complete
---
Implementation Details
Codegen Pipeline
Phase 1-4 implementation:// scripts/codegen/pipeline.ts
async function generateApplication(dsl: DSL, manifest: OwnershipManifest) {
const artifacts: Artifact[] = [];
// 1. Database planner (managed)
if (manifest.surfaces.db === 'managed') {
artifacts.push(...await generateDatabaseArtifacts(dsl));
}
// 2. API planner (managed/extended)
if (manifest.surfaces.api === 'managed' || manifest.surfaces.api.mode === 'extended') {
artifacts.push(...await generateAPIArtifacts(dsl));
}
// 3. Contract planner (for ejected surfaces)
const ejectedSurfaces = Object.entries(manifest.surfaces)
.filter(([_, mode]) => mode === 'ejected' || mode.mode === 'ejected');
if (ejectedSurfaces.length > 0) {
artifacts.push(...await generateContractAdapters(manifest.contracts));
}
// 4. Apply leases
for (const artifact of artifacts) {
artifact.content = applyLease(artifact.content, artifact.surface, artifact.mode);
}
// 5. Write files (with merge for extended)
for (const artifact of artifacts) {
if (artifact.mode === 'extended') {
await writeWithMerge(artifact);
} else {
await writeFile(artifact);
}
}
return artifacts;
}
Extended Merge Implementation
// lib/ownership/extended-merge.ts
export function mergeExtended(
base: string,
theirs: string,
yours: string
): MergeResult {
// 1. Extract custom blocks from "yours"
const customBlocks = extractCustomBlocks(yours);
// 2. Extract weldr blocks from "theirs"
const weldrBlocks = extractWeldrBlocks(theirs);
// 3. Merge
let merged = theirs; // Start with new generated version
// Replace weldr blocks (already in 'theirs')
// Inject custom blocks back
for (const block of customBlocks) {
merged = injectCustomBlock(merged, block);
}
// 4. Detect conflicts
const conflicts = detectConflicts(customBlocks, weldrBlocks);
return {
content: merged,
conflicts,
customBlocksPreserved: customBlocks.length
};
}
Contract Validator Implementation
// lib/ownership/contracts.ts
export async function validateContract(
contract: Contract,
rootDir: string
): Promise<ValidationResult> {
// 1. Create virtual module
const validatorCode = generateValidatorCode(contract);
// 2. Write temp file
const tempFile = path.join(rootDir, '.weldr', 'temp-validator.ts');
await fs.writeFile(tempFile, validatorCode);
// 3. Compile with TypeScript
const program = ts.createProgram([tempFile], {
moduleResolution: ts.ModuleResolutionKind.Bundler,
strict: true,
skipLibCheck: true,
baseUrl: rootDir
});
// 4. Get diagnostics
const diagnostics = ts.getPreEmitDiagnostics(program);
// 5. Parse errors
const errors = diagnostics.map(d => ({
message: ts.flattenDiagnosticMessageText(d.messageText, '\n'),
contractId: contract.id,
exportName: extractExportName(d)
}));
// 6. Cleanup
await fs.unlink(tempFile);
return {
valid: errors.length === 0,
errors
};
}
function generateValidatorCode(contract: Contract): string {
const imports = contract.exports.map(e => e.name).join(', ');
const assertions = contract.exports.map(e => {
if (e.kind === 'value') {
return `const _${e.name}Check: ${e.type} = ${e.name};`;
} else {
return `const _${e.name}Check: ${e.name} = null as any;`;
}
}).join('\n');
return `
import { ${imports} } from './${contract.module}';
${assertions}
`;
}
---
Advanced Patterns
Pattern 1: Gradual Ejection
Start with everything managed, eject incrementally as needs evolve# Month 1: Everything managed
surfaces:
db: managed
api: managed
domain_logic: managed
ui: managed
# Month 3: Eject pricing (competitive advantage)
surfaces:
db: managed
api: extended # Started adding custom helpers
domain_logic:
mode: ejected
entrypoints:
- src/domain/pricing.ts
ui: managed
# Month 6: Eject more as complexity grows
surfaces:
db: managed
api: extended
domain_logic:
mode: ejected
entrypoints:
- src/domain/pricing.ts
- src/domain/recommendations.ts
- src/domain/analytics.ts
ui: extended # Custom components now
styling: ejected # Brand customization
---
Pattern 2: Contract-First Development
Define contracts before implementing# Define contracts upfront
contracts:
- id: domain.payments
surface: domain_logic
module: src/domain/payments.ts
exports:
- name: processPayment
kind: value
type: '(input: PaymentInput) => Promise<PaymentResult>'
- id: domain.notifications
surface: domain_logic
module: src/domain/notifications.ts
exports:
- name: sendNotification
kind: value
type: '(input: NotificationInput) => Promise<void>'
// src/domain/payments.ts
export async function processPayment(input: PaymentInput): Promise<PaymentResult> {
// TODO: Implement
throw new Error('Not implemented');
}
// lib/server/workflows/checkout.ts (generated)
import { domainPayments } from '@/lib/contracts';
export async function checkoutFlow(input: CheckoutInput) {
// Uses contract (currently throws, but type-safe)
const result = await domainPayments.processPayment({
amount: input.amount,
currency: 'usd',
source: input.paymentMethod
});
return result;
}
// src/domain/payments.ts
export async function processPayment(input: PaymentInput): Promise<PaymentResult> {
// Real Stripe implementation
const charge = await stripe.charges.create({
amount: input.amount,
currency: input.currency,
source: input.source
});
return {
success: true,
transactionId: charge.id,
amount: charge.amount
};
}
---
Pattern 3: Multi-Module Ejection
Eject multiple related modules togethersurfaces:
domain_logic:
mode: ejected
entrypoints:
- src/domain/pricing/**/*.ts
- src/domain/billing/**/*.ts
- src/domain/subscriptions/**/*.ts
contracts:
# Pricing contracts
- id: domain.pricing.calculate
surface: domain_logic
module: src/domain/pricing/calculate.ts
exports:
- name: calculatePrice
kind: value
type: '(params: PriceParams) => Promise<number>'
- id: domain.pricing.tiers
surface: domain_logic
module: src/domain/pricing/tiers.ts
exports:
- name: PricingTier
kind: type
# Billing contracts
- id: domain.billing.invoice
surface: domain_logic
module: src/domain/billing/invoice.ts
exports:
- name: generateInvoice
kind: value
type: '(subscription: Subscription) => Promise<Invoice>'
# Subscription contracts
- id: domain.subscriptions.manage
surface: domain_logic
module: src/domain/subscriptions/manage.ts
exports:
- name: upgradeSubscription
kind: value
type: '(id: string, tier: PricingTier) => Promise<Subscription>'
// lib/contracts/domain/pricing-calculate.ts
export { calculatePrice } from '@/src/domain/pricing/calculate';
// lib/contracts/domain/pricing-tiers.ts
export { PricingTier } from '@/src/domain/pricing/tiers';
// lib/contracts/domain/billing-invoice.ts
export { generateInvoice } from '@/src/domain/billing/invoice';
// lib/contracts/domain/subscriptions-manage.ts
export { upgradeSubscription } from '@/src/domain/subscriptions/manage';
// lib/contracts/index.ts
export * as domainPricingCalculate from './domain/pricing-calculate';
export * as domainPricingTiers from './domain/pricing-tiers';
export * as domainBillingInvoice from './domain/billing-invoice';
export * as domainSubscriptionsManage from './domain/subscriptions-manage';
---
Troubleshooting
Issue 1: Orphaned Custom Blocks
Problem:⚠️ Orphaned custom block detected: old-function
File: lib/server/actions/users.ts
// @custom:begin old-function
// Delete this entire block
// @custom:end old-function
// @custom:begin old-function-refactored
export async function oldFunction() {
// Rewrite to not use removed generated function
}
// @custom:end old-function-refactored
---
Issue 2: Contract Validation Failure
Problem:❌ domain.pricing
❌ Export 'calculatePrice' type mismatch
Expected: (params: PriceParams) => Promise<number>
Actual: (params: PriceParams) => number
Promise
).
Solution: Update implementation to match contract:
// Before (wrong)
export function calculatePrice(params: PriceParams): number {
return 49;
}
// After (correct)
export async function calculatePrice(params: PriceParams): Promise<number> {
return 49;
}
---
Issue 3: Merge Conflict
Problem:⚠️ Merge conflict detected in lib/server/actions/users.ts
- @weldr block 'crud-user' was modified manually
@weldr
block (regeneration overwrites it).
Solution: Move changes to @custom
block:
// Before (wrong - editing weldr block)
// @weldr:begin crud-user
export async function createUser(input: CreateUserInput) {
// Your custom validation (will be overwritten!)
validateEmail(input.email);
return await db.insert(users).values(input);
}
// @weldr:end crud-user
// After (correct - use custom block)
// @weldr:begin crud-user
export async function createUser(input: CreateUserInput) {
return await db.insert(users).values(input);
}
// @weldr:end crud-user
// @custom:begin user-validation
export async function createUserWithValidation(input: CreateUserInput) {
// Your custom validation (safe from regeneration)
validateEmail(input.email);
validatePasswordStrength(input.password);
return await createUser(input);
}
// @custom:end user-validation
---
Issue 4: Missing Contract Adapter
Problem:Module not found: Can't resolve '@/lib/contracts/domain/pricing'
$ weldr sync
🔄 Generating contract adapters...
✅ lib/contracts/domain/pricing.ts
✅ lib/contracts/index.ts
---
Summary
Progressive Ownership gives you:---
Next Steps:---
"Progressive Ownership is the only way to build with AI without vendor lock-in. You're in control, not the platform."