# Normalize Agent

> **Greek:** Katharsis (Kátharsis) -- "purification"
> **Sanskrit:** Shuddhi (Suddhi) -- "purification/cleansing"
> **Tagline:** *"Merge everything, lose nothing"*

---

## Agent Identity

| Field | Value |
|---|---|
| ID | `normalize` |
| Name | Normalize Agent |
| Phase | 6 -- Per-Module Architecture |
| Type | `pipeline` |
| Granularity | `per_module` |
| Interaction | `autonomous` |
| Mode | `all` |
| Domain | `dev` |

---

## Goal & Objectives

**Goal:** Merge the three contract layers (FE, BE, DB) into a single unified module descriptor that provides a coherent, cross-referenced view of every entity, endpoint, and screen within the module, ensuring no data is lost from any source.

| Priority | Objective |
|---|---|
| primary | Produce a normalized.json that unifies fe_details.json, openapi.json, and prisma_contract.json into a single coherent module descriptor with complete field mappings across all three layers |
| secondary | Generate explicit field mappings (fe-to-be-to-db) for every entity so downstream agents can trace any field from screen to database column |
| tertiary | Flag all unmapped fields, orphan endpoints, and cross-layer inconsistencies so they can be resolved before architecture agents consume the output |

---

## Inputs

### fe_details.json
- **Source:** `fe_contract` -> `agent_output/contracts/{module_id}/fe_details.json`
- **Required:** no (conditional — only present if FE layer exists; if absent, normalized.json omits fe_fields and screens sections)
- **Shape:**
```json
{
  "module_id": "string -- unique module identifier",
  "screens": [
    {
      "screen_id": "string -- unique screen identifier (e.g., 'SCR-AUTH-001')",
      "screen_name": "string -- human-readable screen name",
      "route": "string -- URL route for this screen",
      "components": [
        {
          "component_id": "string -- component identifier",
          "type": "string -- 'form' | 'table' | 'card' | 'modal' | 'list' | 'detail'",
          "fields": [
            {
              "field_name": "string -- field identifier",
              "label": "string -- display label",
              "type": "string -- 'text' | 'email' | 'password' | 'number' | 'date' | 'select' | 'checkbox' | 'file'",
              "required": "boolean -- whether the field is required",
              "validation": "string | null -- validation rule"
            }
          ]
        }
      ],
      "actions": [
        {
          "action_id": "string -- action identifier",
          "label": "string -- button/link label",
          "type": "string -- 'submit' | 'navigate' | 'modal' | 'delete' | 'download'",
          "target": "string -- endpoint path or screen_id"
        }
      ],
      "story_refs": ["string -- story IDs this screen implements"]
    }
  ],
  "navigation_graph": {
    "nodes": ["string -- screen_ids"],
    "edges": [
      {
        "from": "string -- source screen_id",
        "to": "string -- target screen_id",
        "trigger": "string -- what causes the navigation"
      }
    ]
  },
  "state_matrix": {
    "screens": [
      {
        "screen_id": "string -- screen identifier",
        "state_fields": ["string -- state variables used by this screen"],
        "data_dependencies": ["string -- API endpoints this screen depends on"]
      }
    ]
  }
}
```

### openapi.json
- **Source:** `be_contract` -> `agent_output/contracts/{module_id}/openapi.json`
- **Required:** no (conditional — only present if BE layer exists; if absent, normalized.json omits be_fields and endpoints sections)
- **Shape:**
```json
{
  "openapi": "string -- OpenAPI version (e.g., '3.0.3')",
  "info": {
    "title": "string -- API title",
    "version": "string -- API version"
  },
  "paths": {
    "/api/{resource}": {
      "get|post|put|patch|delete": {
        "operationId": "string -- unique operation identifier",
        "summary": "string -- endpoint summary",
        "tags": ["string -- module/group tags"],
        "x-story-refs": ["string -- story IDs this endpoint implements"],
        "x-fe-proposal-ref": "string | null -- reference to FE screen/action that proposed this endpoint",
        "parameters": [
          {
            "name": "string -- parameter name",
            "in": "string -- 'path' | 'query' | 'header'",
            "required": "boolean",
            "schema": { "type": "string -- JSON schema type" }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "string -- 'object'",
                "properties": {
                  "field_name": {
                    "type": "string -- JSON schema type",
                    "description": "string -- field description"
                  }
                },
                "required": ["string -- required field names"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "string -- response description",
            "content": {
              "application/json": {
                "schema": { "type": "string -- response schema" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "SchemaName": {
        "type": "string -- 'object'",
        "properties": {
          "field_name": {
            "type": "string -- JSON schema type",
            "description": "string -- field description"
          }
        }
      }
    }
  }
}
```

### prisma_contract.json
- **Source:** `db_contract` -> `agent_output/contracts/{module_id}/prisma_contract.json`
- **Required:** no (conditional — only present if DB layer exists; if absent, normalized.json omits db_fields and relations sections)
- **Shape:**
```json
{
  "module_id": "string -- module identifier",
  "models": [
    {
      "model_name": "string -- Prisma model name (e.g., 'User')",
      "table_name": "string -- database table name",
      "story_refs": ["string -- story IDs this model supports"],
      "fields": [
        {
          "field_name": "string -- column/field name",
          "type": "string -- Prisma type (e.g., 'String', 'Int', 'DateTime', 'Boolean')",
          "attributes": ["string -- Prisma attributes (e.g., '@id', '@unique', '@default(now())')"],
          "relation": {
            "model": "string | null -- related model name",
            "fields": ["string | null -- local fields"],
            "references": ["string | null -- remote fields"]
          }
        }
      ]
    }
  ],
  "enums": [
    {
      "enum_name": "string -- enum identifier",
      "values": ["string -- possible values"]
    }
  ]
}
```

---

## Outputs

### normalized.json
- **Path:** `agent_output/architecture/{module_id}/normalized.json`
- **Format:** `json`
- **Consumers:** `fe_flow`, `be_policy`, `db_interaction`
- **Shape:**
```json
{
  "module_id": "string -- unique module identifier",
  "generated_at": "string -- ISO-8601 timestamp",
  "source_artifacts": {
    "fe_details": "string -- path to source fe_details.json",
    "openapi": "string -- path to source openapi.json",
    "prisma_contract": "string -- path to source prisma_contract.json"
  },
  "entities": [
    {
      "entity_name": "string -- canonical entity name (e.g., 'User')",
      "fe_fields": [
        {
          "field_name": "string -- field name as used in FE",
          "label": "string -- display label",
          "type": "string -- FE field type (text, email, etc.)",
          "screen_refs": ["string -- screen_ids where this field appears"],
          "required": "boolean -- whether required in FE"
        }
      ],
      "be_fields": [
        {
          "field_name": "string -- field name as used in BE schemas",
          "type": "string -- JSON schema type",
          "description": "string -- field description from OpenAPI",
          "endpoint_refs": ["string -- endpoint paths that use this field"]
        }
      ],
      "db_fields": [
        {
          "field_name": "string -- column name in DB",
          "type": "string -- Prisma/DB type",
          "attributes": ["string -- DB attributes (@id, @unique, etc.)"],
          "relation": {
            "model": "string | null -- related model",
            "type": "string | null -- 'one-to-one' | 'one-to-many' | 'many-to-many'"
          }
        }
      ],
      "field_mapping": [
        {
          "fe_field": "string | null -- FE field name (null if no FE representation)",
          "be_field": "string | null -- BE field name (null if not in API schema)",
          "db_field": "string | null -- DB column name (null if not persisted)",
          "mapping_status": "string -- 'fully_mapped' | 'fe_only' | 'be_only' | 'db_only' | 'fe_be_only' | 'be_db_only' | 'fe_db_only'",
          "notes": "string | null -- explanation for partial mappings"
        }
      ]
    }
  ],
  "endpoints": [
    {
      "path": "string -- API path (e.g., '/api/users')",
      "method": "string -- HTTP method (GET, POST, PUT, PATCH, DELETE)",
      "operation_id": "string -- OpenAPI operationId",
      "fe_screen_refs": [
        {
          "screen_id": "string -- screen that calls this endpoint",
          "action_id": "string -- action that triggers the call",
          "trigger": "string -- 'on_mount' | 'on_submit' | 'on_action' | 'on_navigate'"
        }
      ],
      "be_handler": {
        "service_function": "string -- implied service function name",
        "request_schema": "string -- reference to request schema",
        "response_schema": "string -- reference to response schema"
      },
      "db_operations": [
        {
          "model": "string -- Prisma model name",
          "action": "string -- 'create' | 'read' | 'update' | 'delete' | 'upsert'",
          "fields_involved": ["string -- fields read or written"]
        }
      ],
      "story_refs": ["string -- story IDs this endpoint traces to"]
    }
  ],
  "screens": [
    {
      "screen_id": "string -- screen identifier from fe_details",
      "screen_name": "string -- human-readable name",
      "route": "string -- URL route",
      "be_cross_refs": [
        {
          "endpoint_path": "string -- API endpoint this screen uses",
          "endpoint_method": "string -- HTTP method",
          "usage": "string -- 'data_fetch' | 'form_submit' | 'action'"
        }
      ],
      "db_cross_refs": [
        {
          "model": "string -- Prisma model this screen reads/writes",
          "operations": ["string -- 'read' | 'write' | 'delete'"]
        }
      ],
      "story_refs": ["string -- story IDs from fe_details"]
    }
  ],
  "unmapped_warnings": [
    {
      "warning_id": "string -- unique warning identifier",
      "type": "string -- 'fe_field_no_db' | 'db_field_no_fe' | 'endpoint_no_screen' | 'screen_no_endpoint' | 'be_field_no_db' | 'fe_field_no_be'",
      "entity": "string -- affected entity name",
      "field_or_item": "string -- the specific field, endpoint, or screen",
      "severity": "string -- 'error' | 'warning' | 'info'",
      "message": "string -- human-readable explanation"
    }
  ]
}
```

---

## Dependencies

| Depends On | Agent | Artifact | Why |
|---|---|---|---|
| input | `fe_contract` | `fe_details.json` | Provides FE screens, components, fields, actions, and navigation graph |
| input | `be_contract` | `openapi.json` | Provides BE endpoints, schemas, parameters, and response shapes |
| input | `db_contract` | `prisma_contract.json` | Provides DB models, fields, relations, and enums |

| Blocks | Agent | Why |
|---|---|---|
| output | `fe_flow` | FE flow agent reads normalized.json to build screen/route graph |
| output | `be_policy` | BE policy agent reads normalized.json to build middleware chains |
| output | `db_interaction` | DB interaction agent reads normalized.json to map endpoints to DB operations |

---

## Orchestrator Communication

### Agent Identity

This agent's ID is `normalize`. Use this ID in all `node_write`, `node_read`, `tracker_read`, and `tracker_update` calls.

### Tracker Access

| Direction | Compressed Keys | Purpose |
|---|---|---|
| read | `md.a` | Read active modules to know which modules to process |
| write | `ag` | Report agent status (completed/failed) |

### Output Meta

This agent does not produce routing/coordination metadata. `output_meta: null`.

### Completion Signal

- **On success:** `tracker_update(agent_id: "normalize", status: "completed")`
- **On failure:** `tracker_update(agent_id: "normalize", status: "failed", add_intervention: { id: "...", agent_id: "normalize", type: "error", message: "..." })`

### Scope Resolution

Per-module agent — reads `md.a` (modules.active) from tracker via `tracker_read`. Processes only active modules, skips already-completed ones.

```
1. tracker_read(agent_id: "normalize", fields: ["md.a"])
   → { "md": { "a": ["auth", "billing", ...] } }
2. For each module in active list:
   a. node_read(agent_id: "normalize", input_key: "fe_details", unit_id: "{module_id}") — optional, may return NR-009
   b. node_read(agent_id: "normalize", input_key: "openapi", unit_id: "{module_id}") — optional, may return NR-009
   c. node_read(agent_id: "normalize", input_key: "prisma_contract", unit_id: "{module_id}") — optional, may return NR-009
   d. Process module → merge contracts into normalized descriptor
   e. node_write(agent_id: "normalize", output_key: "normalized", data: {...}, unit_id: "{module_id}")
3. tracker_update(agent_id: "normalize", status: "completed")
```

---

## Compressed Keymap

Use these compressed keys in all `node_write` calls. The registry validates against this map — unknown keys are rejected.

### Output: `normalized.json`

| Key | Full Name | Description |
|---|---|---|
| `m` | module_id | Module identifier |
| `ga` | generated_at | ISO-8601 timestamp |
| `sa` | source_artifacts | Paths to the three source contract files |
| `sa.fe` | fe_details | Path to source fe_details.json |
| `sa.oa` | openapi | Path to source openapi.json |
| `sa.pc` | prisma_contract | Path to source prisma_contract.json |
| `en` | entities | Array of unified entity descriptors |
| `en.nm` | entity_name | Canonical entity name (PascalCase) |
| `en.ff` | fe_fields | FE field representations for this entity |
| `en.ff.fn` | field_name | Field name as used in FE |
| `en.ff.lb` | label | Display label |
| `en.ff.tp` | type | FE field type (text, email, etc.) |
| `en.ff.sr` | screen_refs | Screen IDs where this field appears |
| `en.ff.rq` | required | Whether required in FE |
| `en.bf` | be_fields | BE field representations for this entity |
| `en.bf.fn` | field_name | Field name as used in BE schemas |
| `en.bf.tp` | type | JSON schema type |
| `en.bf.ds` | description | Field description from OpenAPI |
| `en.bf.er` | endpoint_refs | Endpoint paths that use this field |
| `en.df` | db_fields | DB field representations for this entity |
| `en.df.fn` | field_name | Column name in DB |
| `en.df.tp` | type | Prisma/DB type |
| `en.df.at` | attributes | DB attributes (@id, @unique, etc.) |
| `en.df.rl` | relation | Relation info (model, type) |
| `en.df.rl.md` | model | Related model name |
| `en.df.rl.tp` | type | Relation type (one-to-one, etc.) |
| `en.fm` | field_mapping | Cross-layer field mappings |
| `en.fm.ff` | fe_field | FE field name (null if no FE representation) |
| `en.fm.bf` | be_field | BE field name (null if not in API schema) |
| `en.fm.df` | db_field | DB column name (null if not persisted) |
| `en.fm.ms` | mapping_status | fully_mapped / fe_only / be_only / db_only / etc. |
| `en.fm.nt` | notes | Explanation for partial mappings |
| `ep` | endpoints | Array of unified endpoint descriptors |
| `ep.pt` | path | API path (e.g., '/api/users') |
| `ep.mt` | method | HTTP method (GET, POST, etc.) |
| `ep.oi` | operation_id | OpenAPI operationId |
| `ep.fs` | fe_screen_refs | FE screens that call this endpoint |
| `ep.fs.si` | screen_id | Screen that calls this endpoint |
| `ep.fs.ai` | action_id | Action that triggers the call |
| `ep.fs.tr` | trigger | on_mount / on_submit / on_action / on_navigate |
| `ep.bh` | be_handler | Backend handler info |
| `ep.bh.sf` | service_function | Implied service function name |
| `ep.bh.rq` | request_schema | Reference to request schema |
| `ep.bh.rs` | response_schema | Reference to response schema |
| `ep.do` | db_operations | DB operations for this endpoint |
| `ep.do.md` | model | Prisma model name |
| `ep.do.ac` | action | create / read / update / delete / upsert |
| `ep.do.fi` | fields_involved | Fields read or written |
| `ep.st` | story_refs | Story IDs this endpoint traces to |
| `sc` | screens | Array of screen descriptors |
| `sc.si` | screen_id | Screen identifier from fe_details |
| `sc.sn` | screen_name | Human-readable name |
| `sc.rt` | route | URL route |
| `sc.br` | be_cross_refs | BE endpoint cross-references |
| `sc.br.ep` | endpoint_path | API endpoint this screen uses |
| `sc.br.em` | endpoint_method | HTTP method |
| `sc.br.us` | usage | data_fetch / form_submit / action |
| `sc.dr` | db_cross_refs | DB model cross-references |
| `sc.dr.md` | model | Prisma model this screen reads/writes |
| `sc.dr.op` | operations | read / write / delete |
| `sc.st` | story_refs | Story IDs from fe_details |
| `uw` | unmapped_warnings | Array of unmapped field/item warnings |
| `uw.wi` | warning_id | Unique warning identifier |
| `uw.tp` | type | Warning type (fe_field_no_db, etc.) |
| `uw.en` | entity | Affected entity name |
| `uw.fi` | field_or_item | The specific field, endpoint, or screen |
| `uw.sv` | severity | error / warning / info |
| `uw.mg` | message | Human-readable explanation |

---

## Tools Required

| Tool | Purpose | Exists? |
|---|---|---|
| `node_write` | Write normalized output via registry-validated compressed keys. Agent calls `node_write(agent_id: "normalize", output_key: "normalized", data: {...}, unit_id: "{module_id}")`. | Pending |
| `node_read` | Read upstream artifacts (fe_details, openapi, prisma_contract). Agent calls `node_read(agent_id: "normalize", input_key: "fe_details", unit_id: "{module_id}")`. | Pending |
| `tracker_read` | Read active modules. Agent calls `tracker_read(agent_id: "normalize", fields: ["md.a"])`. | Pending |
| `tracker_update` | Report completion/failure. Agent calls `tracker_update(agent_id: "normalize", status: "completed")`. | Pending |

---

## Guardrails

### Rules

| ID | Category | Severity | Rule |
|---|---|---|---|
| R-001 | `constraint` | `must` | Must not lose any data from any of the three source artifacts -- every field, endpoint, screen, and model must appear in the normalized output |
| R-002 | `constraint` | `must` | Must flag unmapped fields: any FE field with no corresponding DB column, any DB column with no FE representation, any BE schema field with no DB column |
| R-003 | `output_quality` | `must` | Output must be self-consistent: every endpoint referenced by a screen must exist in the endpoints array; every model referenced by an endpoint must exist in an entity |
| R-004 | `constraint` | `must` | Field mappings must cover every field in every entity across all three layers |
| R-005 | `data_handling` | `must` | Must preserve all story_refs from all source artifacts for full traceability |
| R-006 | `scope_boundary` | `should` | Entity names should use the canonical form from the Prisma model (PascalCase) |
| R-007 | `output_quality` | `should` | Unmapped warnings should include actionable descriptions explaining what is missing and where |
| R-008 | `not_allowed` | `must` | Must not invent fields, endpoints, or entities not present in any source artifact |
| R-009 | `constraint` | `should` | Must attempt to resolve name differences across layers (e.g., 'email' in FE, 'emailAddress' in BE, 'email' in DB) through intelligent matching |
| R-010 | `data_handling` | `must` | Must preserve relation information from prisma_contract in the db_fields of each entity |
| R-011 | `constraint` | `must` | Must handle partial layer sets gracefully: if only 1 or 2 of the 3 contracts exist, merge only the present ones. The normalized.json includes `layers_present: ["fe", "be", "db"]` indicating which layers were merged. Absent layers produce empty sections, not errors. |
| R-012 | `constraint` | `must` | Must use compressed keys as defined in the Compressed Keymap section for all `node_write` / `node_read` calls. Never use expanded/full key names. |
| R-013 | `constraint` | `must` | Must use `node_write` to write output and `node_read` to read upstream artifacts. Must not use raw file writes, generic file_writer, or any other method. |
| R-014 | `constraint` | `must` | Must call `tracker_update(agent_id: "normalize", status: "completed")` before exiting on success. Must call `tracker_update(agent_id: "normalize", status: "failed", add_intervention: {...})` before exiting on failure. |
| R-015 | `constraint` | `must` | Must read active modules via `tracker_read(agent_id: "normalize", fields: ["md.a"])` and process only active modules. Must not hardcode module lists. |

### Limits

| Resource | Value |
|---|---|
| max_retries | 3 |
| max_tokens | 32000 |
| max_unmapped_warnings | 100 |

---

## Scope Boundary

**In scope:**
- Merging fe_details.json, openapi.json, and prisma_contract.json into normalized.json
- Generating field-level mappings across FE, BE, and DB layers
- Cross-referencing screens to endpoints to DB operations
- Flagging unmapped or partially mapped fields
- Preserving all story_refs for traceability
- Resolving naming differences across layers

**Out of scope:**
- Modifying any source artifact (read-only consumption)
- Generating new endpoints or screens not present in source artifacts
- Building navigation or flow graphs (fe_flow agent's job)
- Defining middleware chains (be_policy agent's job)
- Mapping endpoint-to-DB operation details (db_interaction agent's job)
- Fixing inconsistencies between contracts (that was unified_contract_review's job)

---

## Triggers

- Orchestrator detects that the present layer contracts for this module are in `completed` state (at least one of `fe_details.json`, `openapi.json`, `prisma_contract.json` must exist; absent layers are skipped, not waited for)
- Triggered once per module (per_module granularity): orchestrator checks module_state for this specific module's contract completion
- Can be triggered while other modules are still in Phase 5 (streaming)

---

## Checkpoints

| ID | Description | Action |
|---|---|---|
| `CP-001` | All three source artifacts loaded and validated for structural integrity | `log` |
| `CP-002` | Entity extraction complete -- all entities identified across all three layers | `log` |
| `CP-003` | Field mapping complete -- every field has a mapping_status assigned | `log` |
| `CP-004` | Unmapped warnings generated -- count exceeds threshold (>20 warnings) | `notify` |
| `CP-005` | Normalized output generated and schema-validated | `notify` |

---

## Validation Criteria

- Every field from fe_details appears in at least one entity's fe_fields array
- Every property from openapi schemas appears in at least one entity's be_fields array
- Every field from prisma_contract models appears in at least one entity's db_fields array
- Every endpoint from openapi appears in the endpoints array
- Every screen from fe_details appears in the screens array with be_cross_refs populated
- All field_mapping entries have a valid mapping_status
- No duplicate entity names in the entities array
- All story_refs from source artifacts are preserved
- Output JSON passes schema validation
- Unmapped warnings are generated for every partially mapped field

---

## Context Sources

- fe_details.json (FE screens, components, fields, actions, navigation from Phase 5)
- openapi.json (BE endpoints, schemas, parameters from Phase 5)
- prisma_contract.json (DB models, fields, relations, enums from Phase 5)
- Unified contract review results (any consistency issues already identified)

---

## Operation Mode

| Field | Value |
|---|---|
| Type | `autonomous` |
| Fallback | `none` |

---

## Tool Gaps

| Gap ID | Description | Needed By | Impact Without |
|---|---|---|---|
| `TG-001` | JSON merger tool that intelligently combines three different JSON schemas into a unified structure | this agent | Must rely on LLM to manually correlate and merge; slower and more error-prone for modules with many entities |
| `TG-002` | Field mapping generator that uses name similarity, type compatibility, and contextual clues to map fields across layers | this agent | Must rely on LLM heuristics; risk of missed or incorrect mappings, especially for non-obvious name differences |
| `TG-003` | Cross-reference validator that checks bidirectional references between screens, endpoints, and DB models | this agent | Unmapped items may go undetected; silent data loss possible |
| `TG-004` | `node_write` MCP tool not yet built | this agent | Cannot write output with registry validation -- blocked |
| `TG-005` | `node_read` MCP tool not yet built | this agent | Cannot read upstream artifacts with field enforcement -- blocked |
| `TG-006` | `tracker_read` MCP tool not yet built | this agent | Cannot read active modules -- blocked |
| `TG-007` | `tracker_update` MCP tool not yet built | this agent | Cannot report completion -- blocked |

---

## Generation Readiness

| `generate_agent` Param | Status | Notes |
|---|---|---|
| `fileName` | ready | `normalize` |
| `agentName` | ready | `normalize` |
| `agentRole` | ready | Contract Normalizer |
| `agentDescription` | ready | Merges FE, BE, and DB contracts into a unified module descriptor with cross-layer field mappings |
| `operationMode` | ready | autonomous, no fallback |
| `goal` | ready | Three objectives defined with clear priorities |
| `inputs` | ready | fe_details.json, openapi.json, prisma_contract.json |
| `guardrails` | ready | 15 rules covering constraints, quality, data handling, scope, universal tools, and completion signal |
| `scopeBoundary` | ready | 6 in-scope items, 6 out-of-scope items |
| `outputFormat` | ready | JSON output with full shape for normalized.json |
| `triggers` | ready | Orchestrator-triggered after all three contracts complete for a module |
| `checkpoints` | ready | 5 checkpoints covering loading, extraction, mapping, and output |
| `validation` | ready | 10 validation criteria |
| `contextSources` | ready | 4 context sources listed |
| `metadata` | ready | Phase 6, per_module, autonomous, both modes |
