App Configuration
ContractSpec uses a three-tier configuration model that separates global app definitions from tenant-specific settings and runtime resolution.
AppBlueprintSpec
The AppBlueprintSpec is the global, versioned definition of your application. It contains no tenant-specific information and is stored in version control.
type AppBlueprintSpec = {
id: string;
version: string;
name: string;
description: string;
// Core specs
capabilities: CapabilitySpec[];
dataViews: DataViewSpec[];
workflows: WorkflowSpec[];
policies: PolicySpec[];
// Required integrations
requiredIntegrations: {
integrationId: string;
category: IntegrationCategory;
purpose: string;
optional?: boolean;
}[];
// Expected knowledge spaces
knowledgeSpaces: {
spaceId: string;
category: KnowledgeCategory;
required: boolean;
purpose: string;
}[];
// UI/UX
theme: ThemeSpec;
overlays: OverlaySpec[];
// Observability
telemetry: TelemetrySpec;
// Schema evolution
migrations: MigrationSpec[];
};TenantAppConfig
The TenantAppConfig is the per-tenant, per-environment configuration that customizes how a specific tenant uses the app.
type TenantAppConfig = {
tenantId: string;
blueprintId: string;
blueprintVersion: string;
environment: "sandbox" | "staging" | "production";
status: "draft" | "preview" | "published" | "archived" | "superseded";
// Integration bindings
integrationBindings: AppIntegrationBinding[];
// Knowledge bindings
knowledgeBindings: AppKnowledgeBinding[];
// Tenant-specific overrides
featureFlags: Record<string, boolean>;
limits: {
maxUsers?: number;
maxStorage?: number;
rateLimit?: number;
};
// Tenant customization
branding: {
logo?: string;
colors?: Record<string, string>;
domain?: string;
};
metadata: Record<string, unknown>;
createdAt: string;
updatedAt: string;
};AppIntegrationBinding
Defines how a tenant connects specific integration instances to satisfy capabilities and workflows.
type AppIntegrationBinding = {
slotId: string; // References AppIntegrationSlot.slotId
connectionId: string; // References IntegrationConnection.meta.id
scope?: {
workflows?: string[];
operations?: string[];
features?: string[];
};
priority?: number; // Lower number = higher priority
};
// Example:
{
slotId: "payments.primary",
connectionId: "conn_stripe_acme_prod",
scope: {
workflows: ["checkout", "subscription-renewal"],
operations: ["payments.stripe.*"]
},
priority: 1
}AppKnowledgeBinding
Defines which knowledge spaces a tenant's app can access and how.
type AppKnowledgeBinding = {
spaceId: string;
enabled: boolean;
// Which workflows/agents can read this space
allowedConsumers: {
workflowIds?: string[];
agentIds?: string[];
roles?: string[];
};
// Category-based access control
allowedCategories: KnowledgeCategory[];
// Sources feeding this space for this tenant
sources: string[]; // References KnowledgeSourceConfig IDs
metadata?: Record<string, unknown>;
};
// Example:
{
spaceId: "product-canon",
enabled: true,
allowedConsumers: {
workflowIds: ["invoice-generation", "quote-creation"],
agentIds: ["support-agent"]
},
allowedCategories: ["canonical", "operational"],
sources: ["src_notion_product_docs", "src_database_schema"]
}ResolvedAppConfig
The ResolvedAppConfig is the runtime result of merging AppBlueprintSpec with TenantAppConfig. It's computed on-demand and never persisted.
type ResolvedAppConfig = {
appId: string;
tenantId: string;
blueprintName: string;
blueprintVersion: number;
environment?: string;
configVersion: number;
capabilities: { enabled: CapabilityRef[]; disabled: CapabilityRef[] };
features: { include: FeatureRef[]; exclude: FeatureRef[] };
dataViews: Record<string, SpecPointer>;
workflows: Record<string, SpecPointer>;
policies: PolicyRef[];
theme?: AppThemeBinding;
telemetry?: TelemetryBinding;
experiments: {
catalog: ExperimentRef[];
active: ExperimentRef[];
paused: ExperimentRef[];
};
featureFlags: FeatureFlagState[];
routes: AppRouteConfig[];
integrations: ResolvedIntegration[]; // [{ slot, binding, connection, spec }]
knowledge: ResolvedKnowledge[]; // [{ binding, space, sources }]
branding: ResolvedBranding; // { appName, assets, colors, domain }
notes?: string;
};Configuration flow
Here's how configuration flows from definition to runtime:
- Development - Define AppBlueprintSpec with required integrations and knowledge spaces
- Deployment - Deploy blueprint to environment (sandbox, staging, production)
- Tenant Setup - Create TenantAppConfig with specific integration connections and knowledge sources
- Runtime - Resolve configuration on-demand when tenant accesses the app
- Execution - Use ResolvedAppConfig to execute capabilities, workflows, and enforce policies
Best practices
- Keep AppBlueprintSpec environment-agnostic - no secrets or tenant-specific data
- Use TenantAppConfig for all tenant-specific settings and connections
- Cache ResolvedAppConfig per request to avoid repeated resolution
- Version blueprints carefully - migrations affect all tenants
- Test blueprint changes in sandbox before promoting to production