# Environment Reconciliation — Implementation Plan (repository-based)

> Plan only. **Nothing is implemented.** Extends `ENVIRONMENT_RECONCILIATION_ANALYSIS.md`
> with a concrete, additive, backward-compatible sequence. The repository is the
> source of truth. No new architecture, service, or pipeline.

## Goal

Unify the two environment vocabularies onto a single canonical set:

```
Current →  knowledge/runtime : dev | staging | production   (default production)
           decision/policy    : dev | test    | prod          (default prod)
Canonical: dev | test | prod   (adopt the pipeline set; `staging` optional, knowledge-only)
```

**Non-blocking:** the split is fail-safe today (a wrong value 422/409s, never a
silent cross-env action), so this runs **after** Make integration is proven. It is
a clarity/maintainability improvement, not a correctness fix.

## Why canonical = `dev / test / prod`

The pipeline set is the system of record — seeded rows (`AgentsSeeder`), model
constants (`Agent::ENV_PROD`), two table columns, and `AgentDecisionService`/
`ActionPolicyService` scope all use it. Migrating the smaller, newer knowledge/
runtime layer (`production → prod`) is far lower-risk than renaming seeded policy/
agent data + the whole decision pipeline.

## Ordered steps (additive; each independently revertable)

**Step 1 — Dual-accept (backward-compatible read).** In the knowledge/runtime env
resolvers, treat `production` and `prod` as equivalent on read (normalize
`production → prod`). No data change; unblocks everything and lets in-flight
callers keep using `production`.
- Files: `AgentKnowledgeReader::env()`, `AgentKnowledgeLoader::env()`,
  `RuntimeRunner::environment()`, `AgentKnowledgeController::env()`.

**Step 2 — Data migration (additive, guarded).** New migration:
`UPDATE agent_knowledge_cache SET environment='prod' WHERE environment='production'`.
Safe against `unique(agent_key, environment, version)` — no `prod` knowledge rows
exist today (verify 0 collisions in-migration first). Decide `staging`: keep as a
knowledge-only allowed value, or migrate/retire if no staging deployment exists
(`NOT VERIFIED IN REPOSITORY` that staging is provisioned).
- Pre-flight (same discipline as G8/Handoff): prove 0 rows would collide.

**Step 3 — Config flip.** `config/agent_knowledge.php`:
`default_environment → 'prod'`, `environments → ['dev','test','prod']` (+`staging`
if kept). Update the service/controller default fallbacks (`'production' → 'prod'`).

**Step 4 — Remove dual-accept.** Once all rows + callers are on `prod`, drop the
Step-1 normalization.

**Step 5 — Docs + tests.** Update `knowledge-api-contract.md`,
`MAKE_NUMU_ORCHESTRATION_CONTRACT.md` (§8 mapping), `AgentRuntimeRunTest`,
`AgentKnowledgeCacheTest`, and `BuildsAgentSchema` env strings.

## Affected files (exhaustive, for planning)

Knowledge/runtime only — the pipeline set needs **no** change:
`config/agent_knowledge.php`; `AgentKnowledgeReader`, `AgentKnowledgeLoader`,
`RuntimeRunner`, `AgentKnowledgeController`, `AgentConsoleController`; a new data
migration + the `2026_07_02_140000` column comment; tests
`AgentRuntimeRunTest`, `AgentKnowledgeCacheTest`, `BuildsAgentSchema`.

## Backward compatibility

- The **dual-accept** window (Step 1) means callers using `production` keep working
  while data migrates.
- The decision endpoint already rejects `production` (`in:dev,test,prod`), so **no**
  transition shim is needed there — only the knowledge/runtime layer needs it.
- Historical `agent_knowledge_cache` rows stay reproducible: the migration only
  renames the `environment` label; `version`/`checksum`/`files_json` are unchanged,
  and any AgentRun's `knowledge_checksum` still resolves cross-environment via
  `AgentKnowledgeReader::byChecksum()`.

## Rollback strategy

- Step 1/3/4 are code/config — revert the commit.
- Step 2 (data) is reversible: `UPDATE agent_knowledge_cache SET environment='production' WHERE environment='prod'`
  (scoped to rows the migration touched, e.g. by `loaded_at`/id range captured pre-migration), or restore from backup.
- No content is destroyed at any step.

## Compatibility

- MariaDB 10.4 + sqlite: plain `UPDATE` + config/string changes — no engine-specific features.
- No generated columns or new indexes required (unlike G8/Handoff/Decision guards).

## Verification (when implemented — NOT now)

1. Pre-flight: 0 `agent_knowledge_cache` rows would collide on `unique(agent_key,'prod',version)`.
2. After migration: `AgentKnowledgeReader::active(agent,'prod')` returns the packages formerly under `production`.
3. Runtime + knowledge endpoints accept `prod`; `production` still accepted during the dual-accept window.
4. Full Agents suite green; the live E2E chain re-run with `environment=prod` on both hops (single namespace).

## Recommendation

Schedule **after** Make integration is proven (the current fail-safe split does not
block it). Implement as its own additive change on `feat/agent-knowledge-cache`
with the same verify-before-migrate discipline used for the G8, Handoff, and
Decision guards. **Do not implement until explicitly approved.**
