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)