Skip to main content

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

EnvironmentPurposeMutability
DEVDevelopment and iterationMutable - bindings can be changed freely
STAGINGPre-production validationSemi-mutable - changes trigger warnings
PRODProduction workloadsImmutable - 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 use
  • corpusActivationId - Which regulatory corpus activation
  • retrievalVersionId, 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_bindings row exists)
  • Engine binding (engine_active_bindings row 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):

TableLocationPurpose
corpus_activationsshared/schema.ts:1065Active corpus documents per environment
engine_active_bindingsshared/schema.ts:1437Active engine config versions per environment
pack_criteria_bindingsshared/schema.ts:1746Active criteria versions per environment
engine_releasesshared/schema.ts:1569Engine 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:

SecretUsage
OPENAI_API_KEYEmbeddings and LLM calls
ANTHROPIC_API_KEYAlternative 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-47 prevents 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)