Environments & Configuration
The C_AI Platform supports three deployment environments with strict promotion flow and immutability guarantees.
Environment Overview
The Environment enum is defined at shared/schema.ts:283-289:
export const Environment = {
DEV: "DEV",
STAGING: "STAGING",
PROD: "PROD"
} as const;
Environment Behavior
| Environment | Purpose | Mutability |
|---|---|---|
| DEV | Development and iteration | Mutable - bindings can be changed freely |
| STAGING | Pre-production validation | Semi-mutable - changes trigger warnings |
| PROD | Production workloads | Immutable - active bindings cannot be edited in place |
Promotion Flow
┌─────────────────────────────────────────────────────────────────────────────┐
│ Environment Promotion Flow │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ DEV │ ──────► │ STAGING │ ──────► │ PROD │ │
│ │ │ test │ │ approve │ │ │
│ │ mutable │ │ validate │ │ immutable │ │
│ └───────────┘ └───────────┘ └───────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ Iterate on: Verify: Locked: │
│ - Criteria - Assessment - Pack bindings │
│ - Corpus - Retrieval - Corpus activations │
│ - Engine configs - Determinism - Engine versions │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Where Configuration Lives
Environment Enum
Defined in shared/schema.ts:283-289. Used as a column type across binding tables.
Pack Resolver
The pack resolver binds configurations per environment at server/lib/packResolver.ts:28:
export async function resolvePackBinding(
tenantId: string,
packId: string,
environment: EnvironmentType = Environment.DEV
): Promise<EffectivePackBinding>
The resolver queries all binding tables filtered by (tenant_id, pack_id, environment) and returns a unified EffectivePackBinding object containing:
criteriaVersionId- Which criteria set version to usecorpusActivationId- Which regulatory corpus activationretrievalVersionId,promptVersionId,chunkingVersionId,agentVersionId- Engine configs
(Source: server/lib/packResolver.ts:12-23)
Pack Safety Service
Immutability is enforced by server/lib/packSafetyService.ts. Key function:
export async function isPackScopeActive(scope: PackScopeCheck): Promise<PackActiveStatus>
A pack scope (tenant_id, pack_id, environment) is considered ACTIVE if it has any of:
- Active corpus activation (
corpus_activations.is_active = 1) - Criteria binding (
pack_criteria_bindingsrow exists) - Engine binding (
engine_active_bindingsrow exists)
When active, the enforcePackImmutability() function throws PackActiveImmutableError (HTTP 409) on mutation attempts.
(Source: server/lib/packSafetyService.ts:48-76)
Tables with Environment Column
The following tables are keyed by (tenant_id, pack_id, environment):
| Table | Location | Purpose |
|---|---|---|
corpus_activations | shared/schema.ts:1065 | Active corpus documents per environment |
engine_active_bindings | shared/schema.ts:1437 | Active engine config versions per environment |
pack_criteria_bindings | shared/schema.ts:1746 | Active criteria versions per environment |
engine_releases | shared/schema.ts:1569 | Engine config releases with target environment |
Each table enforces the rule: one active binding per (tenant, pack, environment) scope.
Deterministic Replay Requirements
For a run to be deterministically replayable, the following must be identical:
1. Snapshot Pinned Versions
When a run is created, 6 version IDs are captured in the runs table (shared/schema.ts:353-359):
// Run snapshot fields (Step 9) - pinned version IDs for deterministic replay
snapshotCriteriaVersionId: varchar("snapshot_criteria_version_id"),
snapshotCorpusActivationId: varchar("snapshot_corpus_activation_id"),
snapshotRetrievalVersionId: varchar("snapshot_retrieval_version_id"),
snapshotPromptVersionId: varchar("snapshot_prompt_version_id"),
snapshotChunkingVersionId: varchar("snapshot_chunking_version_id"),
snapshotAgentVersionId: varchar("snapshot_agent_version_id"),
The createRunSnapshot() function in server/lib/packResolver.ts:107-116 generates this snapshot from resolved bindings.
2. Same Pack Bindings Active
For determinism, the exact same bindings must be active:
- Same criteria version
- Same corpus activation (same documents, same chunks)
- Same engine configs (retrieval, prompt, chunking, agent)
If any binding changes between runs, results may differ.
Secrets Handling
API Keys
API keys are accessed via environment variables, never stored in the database:
| Secret | Usage |
|---|---|
OPENAI_API_KEY | Embeddings and LLM calls |
ANTHROPIC_API_KEY | Alternative LLM provider |
(Source: accessed via process.env throughout server/lib/)
Database Connection
The database URL is configured via environment variable in drizzle.config.ts:3:
if (!process.env.DATABASE_URL) {
throw new Error("DATABASE_URL, ensure the database is provisioned");
}
Security Rules
- No secrets in database: All API keys remain in environment variables
- No secrets in logs: Tracing allowlist in
server/lib/tracing.ts:7-47prevents PII/secret leakage - No secrets in exports: Export service generates reports without sensitive data
Immutability Enforcement
The Golden Rule
Once a pack is ACTIVE in an environment, do NOT edit in place. Changes require creating a new pack version, then activating it.
HTTP 409 Response
When mutation is blocked, the API returns:
{
"error": "PACK_ACTIVE_IMMUTABLE",
"message": "Pack scope (tenant/pack/PROD) has ACTIVE bindings [corpus, criteria]. In-place binding changes are not allowed.",
"tenantId": "...",
"packId": "...",
"environment": "PROD",
"activeBindings": { "corpus": true, "criteria": true, "engine": false }
}
(Source: server/lib/packSafetyService.ts:137-150 - handlePackImmutabilityError function)
Related Pages
- Architecture Overview - System layers and request flow
- Data Model - Full schema reference
- Determinism and Replay - Snapshot pinning details