# GraphQL Pre-Flight Validation Protocol

**Rule: Never execute or embed a GraphQL query/mutation without validating it against the live schema first.**

This applies to ALL skills — backend and frontend — that construct, embed, or execute GraphQL operations.

## Why

One invalid field in a Fluent Commerce GraphQL query poisons the entire response — you get `null` for the whole query, not just the bad field. There is no partial success. A typo in a field name (`createdOn` vs `createdDate`) silently nullifies the response.

The MCP tools `graphql.validate`, `graphql.buildQuery`, and `graphql.generateFull` exist on both MCP servers (extension and official CLI) specifically to prevent this.

## When to Validate

| Scenario | Required? | Tool |
|---|---|---|
| LLM constructs a query from scratch | **MANDATORY** | `graphql.validate` or `graphql.buildQuery` |
| LLM constructs a mutation | **MANDATORY** | `graphql.validate` |
| Embedding a query in a Mystique manifest `data.query` | **MANDATORY** | `graphql.validate` |
| Executing an entity existence probe | **MANDATORY** | `graphql.buildQuery` (construct) or `graphql.validate` (check) |
| Discovery queries (locations, products, catalogues) | **RECOMMENDED** | `graphql.validate` on first use per session |
| Re-running a previously validated query | Not required | N/A |
| MCP not connected (offline mode) | Cannot validate | Skip with warning |

## How to Validate

### Option 1: Validate a query string

```
graphql.validate({ query: "query { orders(first: 10) { edges { node { id ref status } } } }" })
```

Response: `{ ok: true, valid: true|false, errors?: [...] }`

**CRITICAL:** Check the `valid` field, NOT `ok`. The `ok` field means the tool ran successfully. The `valid` field means the query passed schema validation. A response `{ ok: true, valid: false }` means the query has schema errors.

### Option 2: Build a query from structured inputs

```
graphql.buildQuery({ rootField: "orders", fields: ["edges { node { id ref status } }", "pageInfo { hasNextPage }"], args: { first: 1 } })
```

Returns a schema-validated query string. The tool auto-validates against the live schema (`valid: true/false`).

**Relay connection gotcha:** Fluent Commerce uses Relay-style connections. Flat fields like `["id", "ref"]` will fail with `Cannot query field "id" on type "OrderConnection"`. You MUST use the relay pattern: `["edges { node { id ref status } }", "pageInfo { hasNextPage }"]`.

### Option 3: Generate a full query for exploration

```
graphql.generateFull({ root: "orders", depth: 2 })
```

Returns a maximal-selection query with all available fields up to the specified depth. Use when exploring what fields are available, then trim to what you need.

## Validation Gate Pattern

Skills that construct GraphQL should follow this pattern:

```
1. Construct the query (LLM or template)
2. graphql.validate({ query }) → check valid field
3. If valid: false → read errors, fix, re-validate
4. If valid: true → execute with graphql.query
```

For entity probes (existence checks):

```
1. graphql.buildQuery({ root: "<entity>", fields: ["id"], first: 1 })
2. Execute the returned query
```

This avoids constructing queries by hand entirely.

## Anti-Patterns

| Anti-pattern | Fix |
|---|---|
| Hardcoding query strings without validation | Use `graphql.buildQuery` or `graphql.validate` |
| Assuming field names exist (e.g., `createdDate` vs `createdOn`) | Use `graphql.introspect` to discover fields; `validate` returns "Did you mean...?" suggestions |
| Guessing filter argument names (`status` vs `statuses`) | Use `graphql.introspect({ type: "<RootType>" })` |
| Using flat fields on connection types (`fields: ["id"]`) | Use relay pattern: `fields: ["edges { node { id } }", "pageInfo { hasNextPage }"]` |
| Using queries from one Fluent version on another | Re-validate when switching environments |
| Skipping validation because "it's just a simple query" | Simple queries fail too — one bad field nullifies everything |

## DynamicUtils Field Path Validation (Backend Rules)

DynamicUtils builds GraphQL queries at runtime from field path strings like `"items.ref"`, `"items.product.name"`. These are NOT full GraphQL queries — they're shorthand that DynamicUtils translates to `items { edges { node { ref } } }` etc. (it adds `edges`/`node` automatically).

**When to validate:** BEFORE generating Java rule code that uses DynamicUtils paths.

**How to validate DynamicUtils field paths:**

```
Step 1: Identify the entity type the rule operates on (ORDER, FULFILMENT, etc.)

Step 2: Introspect the entity type to discover available fields and edges
   graphql.introspect({ type: "Order" })
   → Returns fields: id, ref, status, type, items (connection), fulfilments (connection), ...

Step 3: For each DynamicUtils field path in the planned rule:
   a. Split path on dots: "items.product.name" → ["items", "product", "name"]
   b. Verify first segment exists as a field/edge on the root entity type
   c. For connection fields (items, fulfilments, etc.), introspect the connected type:
      graphql.introspect({ type: "OrderItem" })
   d. Verify each subsequent path segment exists on the nested type

Step 4: For cross-entity queries (DynamicUtils.query with explicit root):
   graphql.introspect({ type: "Fulfilment" })
   → Verify the root field and its field paths

Step 5: If ANY path segment is not found:
   → STOP. Report invalid field path. Suggest correct field name from introspection.
   → Do NOT generate code with unvalidated paths.
```

**Example validation for `DynamicUtils.query(context, ImmutableList.of("ref", "status", "items.product.name"), null, null)`:**

```
1. graphql.introspect({ type: "Order" })
   → Confirm: ref ✓, status ✓, items (connection to OrderItem) ✓
2. graphql.introspect({ type: "OrderItem" })
   → Confirm: product (edge to VariantProduct) ✓
3. graphql.introspect({ type: "VariantProduct" })
   → Confirm: name ✓
Result: All paths valid → proceed with code generation
```

**Common DynamicUtils field path mistakes caught by this check:**
- `items.edges.node.ref` → WRONG (DynamicUtils adds edges/node automatically, use `items.ref`)
- `createdOn` vs `createdDate` → varies by entity type — introspect to verify
- `fulfilmentItems` → WRONG if the actual field is `items` on the Fulfilment type
- `customer.email` → WRONG if customer is not a direct edge (may need `customer.primaryEmail`)

## Apollo-Generated Classes Validation (Backend Rules)

For rules using Apollo codegen (typed GraphQL classes), validation targets the `.graphql` files:

```
Step 1: Locate .graphql files in the module:
   accounts/<PROFILE>/SOURCE/backend/<repo>/plugins/rules/<alias>/src/main/graphql/**/*.graphql

Step 2: For each .graphql file, read its content and validate:
   graphql.validate({ query: "<contents of .graphql file>" })

Step 3: Check response.valid (NOT response.ok)
   → If valid: true → Apollo codegen will generate correct typed classes
   → If valid: false → Apollo codegen will fail or generate broken classes
      Report errors and fix before running Maven build

Step 4: Verify schema.json freshness (if present):
   ls <module>/plugins/rules/<alias>/src/main/graphql/schema.json
   → If missing: "Apollo codegen requires schema.json. Run schema download."
   → If present: compare timestamp against environment schema version
```

## Manifest Query Validation (Frontend)

For Mystique manifest page queries (inline GraphQL in JSON manifests):

```
Step 1: Extract the query string from the manifest page definition:
   data.query or queries[].query

Step 2: Validate directly:
   graphql.validate({ query: "<extracted query string>" })

Step 3: Check response.valid
   → If valid: false → BLOCK manifest deployment. One bad field nullifies the entire page.

Step 4: For queries built from scratch, prefer schema-first construction:
   graphql.generateFull({ root: "orderById", depth: 2 })
   → Returns all valid fields. Remove what you don't need (never add fields by guessing).
```

## Component-Embedded Query Validation (Frontend SDK)

For `useQuery()` calls in Mystique SDK component `.tsx` files:

```
Step 1: Extract the GraphQL template string from the component source:
   const ORDER_QUERY = gql`query orderById($id: ID!) { ... }`;

Step 2: Validate:
   graphql.validate({ query: "<extracted query>" })

Step 3: Check response.valid
   → If valid: false → BLOCK component build. Invalid query = silent null at runtime.
```

## Lifecycle Integration — When Each Check Fires

| Lifecycle Phase | Skill | What Gets Validated | Validation Tool | Gate Level |
|---|---|---|---|---|
| **Rule planning** | `/fluent-rule-scaffold` (plan phase) | DynamicUtils field paths in pseudo logic | `graphql.introspect` per entity type | MANDATORY — blocks code generation |
| **Rule scaffolding** | `/fluent-rule-scaffold` (code gen) | Full GraphQL operations in generated Java | `graphql.validate` for inline queries, `graphql.introspect` for field paths | MANDATORY — blocks file write |
| **Workflow planning** | `/fluent-workflow-builder` (plan phase) | GraphQL operations listed in plan §12 | `graphql.validate` | MANDATORY — blocks JSON generation |
| **Workflow validation** | `/fluent-workflow-validate` | Rule props containing GraphQL-related config | `graphql.introspect` (verify entity types/fields) | HIGH — flags in report |
| **Manifest building** | `/fluent-mystique-builder` | Page queries in manifest JSON | `graphql.validate` | MANDATORY — blocks manifest output |
| **Component building** | `/fluent-mystique-component` | `useQuery()` embedded GraphQL | `graphql.validate` | MANDATORY — blocks file write |
| **Frontend build** | `/fluent-frontend-build` | Source queries + deployed manifest queries | `graphql.validate` (3-tier) | HIGH — Gate 7 |
| **SDK scaffold** | `/fluent-mystique-scaffold` | Template queries in generated components | `graphql.validate` | MANDATORY if query present |
| **Pre-deploy check** | `/fluent-pre-deploy-check` | All GraphQL across workflows + manifests | `graphql.validate` + `graphql.introspect` | CRITICAL — blocks deployment |
| **Post-build** | `/fluent-build` | Apollo .graphql files in module | `graphql.validate` | HIGH — warns before package |

## MCP Unavailable Fallback

When MCP tools are not connected (offline mode, CI environment without MCP):

1. **Log a warning**: `WARNING: GraphQL validation skipped — MCP not available. Mark for manual validation before deployment.`
2. **Do NOT block** the skill from proceeding — the work product should still be generated.
3. **Add a TODO marker** in generated output: `// TODO: Validate GraphQL against live schema (MCP was unavailable during generation)`
4. **Pre-deploy check MUST re-validate** when MCP is available — this is the final safety net.

## Skills That Must Enforce This Gate

### Frontend skills (enforce MANDATORY validation):
- `/fluent-mystique-builder` — validates before embedding in manifests
- `/fluent-mystique-validate` — validates manifest page queries
- `/fluent-frontend-build` — Gate 7: 3-tier GraphQL validation
- `/fluent-frontend-review` — Phase 4: source + manifest query validation
- `/fluent-mystique-component` — MANDATORY for embedded field queries
- `/fluent-mystique-scaffold` — validates template queries in generated components

### Backend skills (enforce MANDATORY validation):
- `/fluent-rule-scaffold` — validates DynamicUtils paths + Apollo queries before code generation
- `/fluent-workflow-builder` — validates GraphQL operations in plan §12 before JSON generation
- `/fluent-workflow-validate` — validates entity type/field references in rule props
- `/fluent-pre-deploy-check` — final gate validates all GraphQL across the deployment
- `/fluent-build` — validates Apollo .graphql files before Maven package

### Runtime skills (enforce RECOMMENDED validation):
- `/fluent-account-snapshot` — entity existence probes
- `/fluent-test-data` — discovery queries + entity creation mutations
- `/fluent-e2e-test` — entity queries + mutations + state assertions
- `/fluent-trace` — entity state queries
- `/fluent-retailer-config` — entity creation mutations

## Relevant Skills
- relevant-skills: fluent-rule-scaffold, fluent-workflow-builder, fluent-workflow-validate, fluent-pre-deploy-check, fluent-build, fluent-mystique-builder, fluent-mystique-validate, fluent-mystique-component, fluent-mystique-scaffold, fluent-frontend-build, fluent-frontend-review, fluent-account-snapshot, fluent-test-data, fluent-e2e-test, fluent-trace, fluent-retailer-config
