# Feature Plan Template

> **Source of truth** for all implementation plans generated by planning-gated skills.
> Referenced from individual skill Planning Gate sections.
> Enhance this file to improve all future plans across all skills.

---

## Universal Source Classification

Every table row in every section of a plan carries a **Source** column. This makes plans self-contained — a reviewer can scan any section and immediately see what's new (risky, needs review), what's reused (lower risk, proven pattern), and what's unchanged (context only).

| Label | Meaning | Required Detail |
|-------|---------|----------------|
| **NEW** | Doesn't exist yet, must be created | Full pseudo logic / value shape / trigger spec |
| **EXISTING** | Already deployed, no modification needed | State current value, confirm no change |
| **MODIFIED** | Already exists but needs changes | Show before → after diff |
| **REUSED** | Pattern exists in codebase, copy the approach | Reference source file/rule |
| **OOTB** | Platform rule, use with configuration only | State which props/values to configure |

---

## Plan File Conventions

**Feature plan path:** `accounts/<PROFILE>/features/<slug>/plan.md`

**Task plan path:** `accounts/<PROFILE>/tasks/<YYYY-MM-DD>-<skill-short-name>-<brief-slug>.md`

**Repo-scoped path:** `.fluent-ai-skills/plans/<YYYY-MM-DD>-<skill-short-name>-<brief-slug>.md`

Use feature plan path for plans tied to a business feature (creates `status.json` alongside). Use task plan path for operational plans (module-scaffold, bootstrap, version-manage). Use repo-scoped for package-internal work.

**Naming rules:**
- `<PROFILE>` — active Fluent CLI profile (e.g., `MY_PROFILE`, `ACME_PROD`)
- `<YYYY-MM-DD>` — creation date
- `<skill-short-name>` — generating skill without `fluent-` prefix (e.g., `feature-plan`, `rule-scaffold`, `workflow-builder`)
- `<brief-slug>` — 2-4 word kebab-case description (e.g., `curbside-pickup`, `order-hd-returns`)

**Status lifecycle:** `PENDING` → `APPROVED` / `REJECTED` → implementation → session summary references plan file.

---

## Template

Every plan file MUST follow this structure. Omit sections marked _optional_ when they don't apply. Sections marked **required** must always be present.

````markdown
# Plan: <Feature Title>

| Field | Value |
|-------|-------|
| Status | `PENDING` |
| Created | <YYYY-MM-DD> |
| Skill | `/fluent-<skill-name>` |
| Profile | <PROFILE> |
| Retailer(s) | <RETAILER_REF> (ID: <N>) |
| Module(s) | <module names if applicable> |
| Business Spec | `accounts/<PROFILE>/features/<slug>/spec.md` |
| Approved by | --- |
| Revision | 1 |

## Revision Log

| Rev | Date | Change Type | Summary | Status Impact |
|-----|------|-------------|---------|---------------|
| v1 | YYYY-MM-DD | Initial | Initial plan | -- |

## Table of Contents

1. [Business Context](#1-business-context)
2. [Scope](#2-scope)
3. [Architecture & Diagrams](#3-architecture--diagrams)
4. [Workflows](#4-workflows)
5. [Statuses](#5-statuses)
6. [Rulesets — Change Summary](#6-rulesets--change-summary)
   - 6.1 [Complete Workflow Inventory](#61-complete-workflow-inventory) (full rules + props)
7. [Rules Inventory](#7-rules-inventory)
8. [Detailed Design (New Rules)](#8-detailed-design-new-rules)
9. [Settings](#9-settings)
10. [UI / Manifest Artifacts](#10-ui--manifest-artifacts) _(REQUIRED when feature includes manifest/UI work; skip for backend-only features)_
    - 10.1 [Spec §9a Coverage](#101-spec-9a-coverage)
    - 10.2 [Route Definitions](#102-route-definitions)
    - 10.3 [Component Hierarchy](#103-component-hierarchy)
    - 10.4 [Data Query Specifications](#104-data-query-specifications)
    - 10.5 [Manifest Fragment Changes](#105-manifest-fragment-changes)
    - 10.6 [Build vs Configure Decision](#106-build-vs-configure-decision)
    - 10.7 [i18n Keys](#107-i18n-keys)
    - 10.8 [Visual Mock / Layout Sketch](#108-visual-mock--layout-sketch)
11. [Webhooks](#11-webhooks)
12. [Scheduled Events](#12-scheduled-events)
13. [GraphQL Operations](#13-graphql-operations)
14. [Cross-Entity Impact](#14-cross-entity-impact)
    - 14a. [Roles & Permissions](#14a-roles--permissions) _(REQUIRED when feature has role-differentiated behavior)_
15. [Files](#15-files)
16. [Risks & Mitigations](#16-risks--mitigations)
17. [Test Plan](#17-test-plan)
18. [Deployment Sequence](#18-deployment-sequence)
19. [Rollback Plan](#19-rollback-plan)

---

## 1. Business Context **[required]**

> **Business objective:** <What business problem does this solve?>
> **Trigger:** <What triggered this — user story, bug, go-live requirement?>
> **Success criteria:** <How do we know this is done? Measurable outcome.>

## 2. Scope **[required]**

<One paragraph: what this plan achieves technically. Reference specific workflow names, entity types, and module names.>

**In scope:**
- <bulleted list>

**Out of scope:**
- <bulleted list>

## 3. Architecture & Diagrams **[required — at least one diagram]**

> **Refine from spec:** If the approved spec has §5a User Journey Diagrams, use them as the starting point. The spec diagrams show business-level actor flows; this section translates those into technical implementation diagrams (state machines, cross-entity events, data flows). Every actor and use case from spec §5a should be traceable here at the implementation level.

#### Spec §5a → Plan §3 Mapping

| Spec Diagram (§5a) | Plan Diagram (§3) | Transformation |
|---|---|---|
| **Actor Map** (table) | §3.2 Cross-Entity Event Flow | Actors become participants; add event names, GraphQL ops, webhook calls |
| **User Journey Flow** (flowchart TD) | §3.1 State Machine + §3.6 Workflow Processing Flow | Business steps become workflow statuses; branches become rulesets; add event triggers |
| **Actor Interaction Sequence** (sequenceDiagram) | §3.2 Cross-Entity Event Flow | Replace business labels with technical names (rule names, event names, API calls) |
| **Decision Flow** (flowchart TD) | §3.3 Data Flow or §3.6 Workflow Processing Flow | Business conditions become rule logic; BR-XX references become rule class names |

**Traceability checklist** (verify before finalizing §3):
- [ ] Every actor from spec §4 appears as a participant in at least one plan diagram
- [ ] Every use case from spec §5 maps to at least one state transition or event sequence
- [ ] Every business rule decision point from spec §7 appears in a plan diagram (state machine branch, flowchart decision, or ruleset routing)
- [ ] No plan diagram introduces actors or flows that aren't in the spec (if they do, the spec needs updating first)

### 3.1 State Machine

> Mermaid `stateDiagram-v2` for workflow creation or modification.
> Show ALL statuses. Annotate: `:::added`, `:::removed`, `:::modified`.

```mermaid
stateDiagram-v2
    [*] --> CREATED
    CREATED --> BOOKED : ConfirmOrder
    BOOKED --> AWAITING_PICKUP : CurbsideRequested:::added
    AWAITING_PICKUP --> COMPLETE : ConfirmPickup:::added
    AWAITING_PICKUP --> CANCELLED : CurbsideTimeout:::added
    BOOKED --> FULFILLED : FulfilmentComplete
```

### 3.2 Cross-Entity Event Flow

> Mermaid `sequenceDiagram` for cross-entity events, webhooks, or multi-step processing.

```mermaid
sequenceDiagram
    participant WF as Workflow Engine
    participant R1 as CreateCurbsidePickup [NEW]
    participant R2 as ValidatePickupWindow [NEW]
    participant GQL as GraphQL API
    participant EXT as External Webhook

    WF->>R2: run(context) [ORDER]
    R2->>GQL: SettingUtils.getSettingByRef(curbside.pickup.config)
    GQL-->>R2: {maxPickupHours: 48}
    R2->>R2: Validate window
    WF->>R1: run(context) [ORDER]
    R1->>GQL: query order items
    GQL-->>R1: OrderItem[]
    R1->>GQL: createFulfilment(type=CURBSIDE)
    GQL-->>R1: Fulfilment created
    WF->>EXT: POST /curbside/notify (via SendWebhook OOTB)
```

### 3.3 Data Flow _(optional)_

> Mermaid `flowchart` for complex processing, decision trees, or event propagation chains.

### 3.4 Before → After Diff _(optional)_

> For modifications to existing workflows. Use `workflow.diff` output or describe changes.

### 3.5 Entity Relationships _(optional)_

> For module scaffolding or retailer config. Show entity types and their edges.

### 3.6 Workflow Processing Flow **[required when modifying an existing workflow]**

> Mermaid `flowchart TD` showing the complete ruleset→status processing chain for each modified workflow.
> Highlights NEW and MODIFIED rulesets with green styling. Shows how new rulesets fit into the existing flow.
> This complements the state machine (§3.1) which shows statuses only — this shows the rulesets that drive transitions.

```mermaid
flowchart TD
    subgraph "ORDER (order lifecycle)"
        O1[CREATED] -->|CREATE| O2[ON_VALIDATION]
        O2 -->|ValidateOrder| O2
        O2 -->|"ConfirmValidation + ProcessOrder"| O3[IN_PROGRESS]
        O3 -->|NotifyFulfilmentShipped| O4[SHIPPED]
        O4 -->|"NotifyFulfilmentDelivered + TransitionToCompleted"| O5[COMPLETED]
    end
    subgraph "FULFILMENT_CHOICE (FC routing)"
        FC1[FC CREATED] -->|RouteFulfilmentChoice| FC2{Route by type}
        FC2 -->|HD| FC3[ProcessHDFC]
        FC2 -->|CC| FC4[ProcessCCFC]
        FC2 -->|CURBSIDE| FC5[ProcessCurbsideFC]:::added
    end
    subgraph "FULFILMENT (lifecycle by subtype)"
        F1[FUL CREATED] -->|AssignFulfilment| F2[READY]
        F2 -->|ConfirmAllocation| F3[RECEIVED]
        F3 -->|CreateInvoice| F4[INVOICED]
        F4 -->|ConfirmPick| F5[PICKPACK]
        F5 -->|"ReadyForPickup (CURBSIDE)"| F6[READY_FOR_PICKUP]:::added
        F6 -->|"ConfirmCollection (CURBSIDE)"| F7[COLLECTED]:::added
    end

    classDef added fill:#90EE90,stroke:#333
```

> **Format guidance:**
> - One `subgraph` per entity type in the workflow
> - Node labels show status names; edge labels show ruleset/event names
> - Use `:::added` class for NEW rulesets/statuses, bold edge labels for MODIFIED
> - Keep it high-level — show the routing and status flow, not individual rules within rulesets
> - This diagram pairs with §6.1 Complete Workflow Inventory which shows the detail per ruleset

### 3.7 Delta View (for features extending existing functionality) _(optional)_

> When this plan modifies existing workflows or rulesets, show the before→after for each change:

| Artifact | Before | After | Change Type |
|----------|--------|-------|-------------|
| ORDER::HD Ruleset "CreateFulfilment" | OOTB `CreateFulfilment` rule | Custom `CreateFulfilmentWithBulkySplit` | REPLACED |
| ORDER::HD Status "PICK_PACK" | Transitions: AssignFulfilment | Transitions: AssignFulfilment, AssignBulkyFulfilment | MODIFIED |
| Setting `BULKY_ATTRIBUTE_NAME` | (not present) | `isBulky` | NEW |

> For **MODIFIED** artifacts: specify exactly what changes (added rule props, new transitions, changed parameters).
> For **REPLACED** artifacts: name the old and new, explain why replacement is needed vs modification.
> For **NEW** artifacts: mark as NEW, no "before" needed.

> **Source classification reminder:** Every row in every table carries a Source column: NEW / EXISTING / MODIFIED / REUSED / OOTB. The Delta View supplements this by showing the actual before→after diff for MODIFIED and REPLACED items.

## 4. Workflows **[required]**

| Workflow | Source | Current Ver | Action | Details |
|----------|--------|-------------|--------|---------|
| ORDER::HD | MODIFIED | v12 | Modify | Add CurbsidePickupRequested ruleset, add 2 statuses |
| FULFILMENT::CURBSIDE | NEW | — | Create | New workflow for curbside fulfilments |
| FULFILMENT::HD | EXISTING | v8 | — | No change — listed for context |

## 5. Statuses **[required when touching workflows]**

| Status | Source | Entity Type | Workflow | Added By |
|--------|--------|-------------|----------|----------|
| CREATED | EXISTING | ORDER | ORDER::HD | Already in workflow |
| BOOKED | EXISTING | ORDER | ORDER::HD | Already in workflow |
| AWAITING_PICKUP | NEW | ORDER | ORDER::HD | SetState in CurbsidePickupRequested ruleset |
| PICKED_UP | NEW | ORDER | ORDER::HD | SetState in ConfirmCurbsidePickup ruleset |
| READY_FOR_PICKUP | NEW | FULFILMENT | FULFILMENT::CURBSIDE | SetState in PrepareCurbside ruleset |
| COLLECTED | NEW | FULFILMENT | FULFILMENT::CURBSIDE | SetState in ConfirmCollection ruleset |

## 6. Rulesets — Change Summary **[required when touching workflows]**

> Quick-glance table of what rulesets are being added, modified, or referenced. Full detail (every rule, every prop) lives in §6.1.

| Ruleset | Source | Workflow | From Status | To Status | Change Summary |
|---------|--------|----------|-------------|-----------|----------------|
| CurbsidePickupRequested | NEW | ORDER::HD | BOOKED | AWAITING_PICKUP | 3 rules: ValidatePickupWindow (NEW), CreateCurbsidePickupRule (NEW), SendEvent (OOTB) |
| ConfirmCurbsidePickup | NEW | ORDER::HD | AWAITING_PICKUP | PICKED_UP | 2 rules: UpdateFulfilmentStatus (EXISTING), SetOrderStatus (OOTB) |
| BookOrder | EXISTING | ORDER::HD | CREATED | BOOKED | No change — listed for context |

### 6.1 Complete Workflow Inventory **[required when modifying an existing workflow]**

> The **single source of truth** for the entire workflow. Lists ALL rulesets — not just new/modified — with every rule and every prop value.
> This replaces the need to read the raw workflow JSON. Reviewers can see exactly where new rulesets fit, what's already there, and what each rule does.

**Format:** One block per ruleset, grouped by entity type. Each block shows:
- **Heading:** `# | RulesetName | Source marker` — bold for NEW/MODIFIED
- **Metadata:** Entity type, subtype (if filtered), trigger status, event type
- **Description:** What the ruleset does
- **Rules table:** Every rule in execution order with full prop values and source annotations
- **User actions:** If the ruleset exposes UI buttons

**Source markers:** `EXISTING` = no change, `MODIFIED` = changed by this plan (show what changed), `NEW` = added by this plan

---

#### ORDER Rulesets (N existing, N new, N modified)

##### 1. CREATE | EXISTING
**Entity:** ORDER | **Trigger:** CREATED | **EventType:** NORMAL
> Order created. Create missing products, send webhook, branch on fraud.

| # | Rule | Props | Source |
|---|------|-------|--------|
| 1 | CreateMissingVariantProducts | — | EXISTING |
| 2 | SendWebhookWithDynamicAttributes | `setting: webhook.order.created` | EXISTING |
| 3 | SendEventOnVerifyingAttributeValue | `attributeName: fraudCheckRequired, onMatchEventName: InitFraudCheck, noMatchEventName: SkipFraudCheck` | EXISTING |

---

##### 2. BookOrder | EXISTING
**Entity:** ORDER | **Trigger:** BOOKED | **EventType:** NORMAL
> Standard booking, no change.

| # | Rule | Props | Source |
|---|------|-------|--------|
| 1 | ... | ... | EXISTING |

---

##### 3. **CurbsidePickupRequested** | **NEW**
**Entity:** ORDER | **Trigger:** BOOKED | **EventType:** NORMAL
> Validates curbside request (time window, vehicle desc) and creates CURBSIDE fulfilment.

| # | Rule | Props | Source |
|---|------|-------|--------|
| 1 | **ValidatePickupWindow** | `configSettingName: curbside.pickup.config, pickupTimePath: event.requestedPickupTime, vehicleDescPath: event.vehicleDescription` | **NEW** |
| 2 | **CreateCurbsidePickupRule** | `fulfilmentType: CURBSIDE, pickupLocationPath: event.pickupLocationRef` | **NEW** |
| 3 | SendEvent | `eventName: FulfilmentCreated` | OOTB |

**User Actions:**
```json
[{"eventName": "CurbsidePickupRequested", "context": [{"label": "REQUEST CURBSIDE PICKUP", "type": "PRIMARY"}]}]
```

---

> _Repeat for every ruleset in the workflow. Use `...` only for large EXISTING blocks where props are unchanged and well-known. Never abbreviate NEW or MODIFIED rulesets._

#### Summary

| Entity | Existing | New | Modified | Total |
|--------|----------|-----|----------|-------|
| ORDER | N | N | N | N |
| FULFILMENT_CHOICE | N | N | N | N |
| FULFILMENT | N | N | N | N |
| **Total** | **N** | **N** | **N** | **N** |

> **Key rules for this section:**
> - **Every ruleset gets a block** — no exceptions. This is the complete workflow.
> - **Every rule in every ruleset gets a row** with its full props.
> - **Props must show actual values**, not placeholders. For EXISTING rulesets, read from the workflow JSON.
> - **NEW and MODIFIED blocks are bolded** in the heading and in rule rows.
> - **MODIFIED rulesets** must show what changed — add a note like "Added rule #3 (NEW)" or "Changed prop `type` from `CC` to `CURBSIDE`".
> - **Group by entity type** with entity-level count headers.
> - **User actions** are shown as JSON blocks below the rules table (only if the ruleset has them).
> - This section replaces the need to cross-reference §6 and §6.1 — it is the single comprehensive reference.

## 7. Rules Inventory **[required]**

> ALL rules involved in the feature. Every rule classified by source.
> A feature typically involves a mix of OOTB, existing custom, and new custom rules.

| ID | Rule Name | Source | Module / Plugin | Entity | Inline/Scheduled | Purpose |
|----|-----------|--------|----------------|--------|-----------------|---------|
| R1 | CreateCurbsidePickupRule | **NEW** | my-extensions | ORDER | Inline | Creates CURBSIDE fulfilment with pickup location |
| R2 | ValidatePickupWindow | **NEW** | my-extensions | ORDER | Inline | Validates pickup time is within allowed window |
| R3 | SendEvent | **OOTB** | fc-core | ORDER | Inline | Fires CurbsidePickupCreated → FULFILMENT. Config: `eventName=CurbsidePickupCreated` |
| R4 | SetAttributes | **OOTB** | fc-core | FULFILMENT | Inline | Sets estimatedPickupTime from event attribute. Config: `attributeMap={...}` |
| R5 | SendWebhook | **OOTB** | fc-plugin-webhook | ORDER | Inline | HTTP POST to curbside notification endpoint. Config: `settingName=webhook.curbside.notify` |
| R6 | ScheduleExpiry | **OOTB** | fc-plugin-order | ORDER | Inline | Schedules CurbsideTimeout event. Config: `delay=30m, eventName=CurbsideTimeout` |
| R7 | UpdateFulfilmentStatus | **EXISTING** | my-extensions | FULFILMENT | Inline | Reused from HD flow — no code changes needed |
| R8 | SetOrderStatus | **OOTB** | fc-core | ORDER | Inline | Sets ORDER to target status. Config: `status=AWAITING_PICKUP` |

> **For OOTB rules:** state the prop/config values to set in the ruleset.
> **For EXISTING rules:** confirm no code changes needed, or if MODIFIED, note what changes.
> **For NEW rules:** full pseudo logic in §8. Distinguish rule props (`@ParamString` — static workflow config) from event attributes (`@EventAttribute` — dynamic event payload data).
> **Input classification:** For every NEW rule, identify which inputs are rule props (static, from workflow JSON) vs event attributes (dynamic, from event payload). Missing `@EventAttribute` annotations cause silent null reads at runtime.

## 8. Detailed Design (New Rules) **[required if any NEW rules exist]**

> For each **NEW** rule in §7, provide: class metadata, parameters table, pseudo logic, and GraphQL operations.
> Skip this section entirely if all rules are OOTB or EXISTING.

---

### R1: CreateCurbsidePickupRule

**Class:** `com.fluentcommerce.rule.order.CreateCurbsidePickupRule`
**Extends:** `BaseRule`
**Entity:** ORDER

#### Parameters (Rule Props — `@ParamString`, static workflow config)

| Param | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| pickupLocationPath | String | Yes | — | JSON path to pickup location ref |
| estimatedPickupTimePath | String | No | — | JSON path to estimated pickup time |
| fulfilmentType | String | No | `CURBSIDE` | Fulfilment subtype |
| vehicleDescriptionPath | String | No | — | JSON path to vehicle description |

#### Event Attributes (`@EventAttribute`, dynamic from event payload)

> List ALL event attributes this rule reads. Each MUST be declared with `@EventAttribute` on the class — missing declarations cause silent null reads at runtime. Read via `event.getAttributes().get("name")`, NEVER via `DynamicUtils.getJsonPath(context, "event.X")`.

| Attribute | Type | Required | Source | Description |
|-----------|------|----------|--------|-------------|
| pickupLocationRef | String | Yes | User action payload | Location ref for curbside pickup |
| requestedPickupTime | String | No | User action payload | ISO-8601 requested pickup time |
| vehicleDescription | String | No | User action payload | Customer vehicle description |

#### Pseudo Logic

```
FUNCTION run(context):
    order = context.getEntity()
    event = context.getEvent()

    // 1. Extract required fields
    pickupLocationRef = extractFromPath(event, props.pickupLocationPath)
    IF pickupLocationRef IS NULL:
        THROW "Pickup location ref is required but was null at path: {pickupLocationPath}"

    // 2. Extract optional fields
    estimatedPickupTime = extractFromPath(event, props.estimatedPickupTimePath)  // nullable
    vehicleDescription = extractFromPath(event, props.vehicleDescriptionPath)    // nullable

    // 3. Fetch order items
    items = DynamicUtils.queryList(context, "items", OrderItem.class, null)
    IF items IS EMPTY:
        THROW "Order has no items — cannot create fulfilment"

    // 4. Build fulfilment
    fulfilmentRef = "CURB-{order.ref}-{pickupLocationRef}"
    fulfilmentInput = CreateFulfilmentInput {
        ref: fulfilmentRef,
        type: props.fulfilmentType OR "CURBSIDE",
        deliveryType: "CURBSIDE",
        order: { id: order.id },
        fromLocation: { ref: pickupLocationRef },
        items: items.map(i => { ref: i.ref, quantity: i.quantity }),
        attributes: [
            { name: "pickupLocationRef", value: pickupLocationRef },
            { name: "estimatedPickupTime", value: estimatedPickupTime },
            { name: "vehicleDescription", value: vehicleDescription },
            { name: "deliveryType", value: "CURBSIDE" },
            { name: "createdByRule", value: "CreateCurbsidePickupRule" }
        ]
    }

    // 5. Execute mutation
    DynamicUtils.create(context, fulfilmentInput)

    // 6. Log
    context.addLog("Created curbside fulfilment {fulfilmentRef} for order {order.ref}")
```

#### GraphQL Operations

**Query — Fetch order items:** _(REUSED pattern from CreateFulfilmentFromSourcingLocation)_
```graphql
query {
  orderById(id: $orderId) {
    items { edges { node { id ref quantity productRef } } }
  }
}
```

**Mutation — Create fulfilment:** _(REUSED pattern)_
```graphql
mutation($input: CreateFulfilmentInput!) {
  createFulfilment(input: $input) { id ref status }
}
```

---

### R2: ValidatePickupWindow

**Class:** `com.fluentcommerce.rule.order.ValidatePickupWindowRule`
**Extends:** `BaseRule`
**Entity:** ORDER

#### Parameters (Rule Props — `@ParamString`)

| Param | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| configSettingName | String | Yes | — | Setting key for pickup config JSON |
| pickupTimePath | String | Yes | — | JSON path to requested pickup time in event |

#### Event Attributes (`@EventAttribute`)

| Attribute | Type | Required | Source | Description |
|-----------|------|----------|--------|-------------|
| requestedPickupTime | String | Yes | User action payload | ISO-8601 requested pickup time |

#### Pseudo Logic

```
FUNCTION run(context):
    order = context.getEntity()
    event = context.getEvent()

    // 1. Read config from setting
    config = SettingUtils.getSettingByRef(context, props.configSettingName)
    maxPickupHours = config.lobValue.maxPickupHours

    // 2. Extract requested pickup time from event
    requestedPickupTime = extractFromPath(event, props.pickupTimePath)
    IF requestedPickupTime IS NULL:
        THROW "Requested pickup time is required at path: {pickupTimePath}"

    // 3. Validate window
    orderCreatedOn = order.createdOn
    deadline = orderCreatedOn + maxPickupHours hours
    IF requestedPickupTime > deadline:
        THROW "Pickup time {requestedPickupTime} exceeds allowed window of {maxPickupHours}h from order creation"

    // 4. Pass — validation gate, no mutation
    context.addLog("Order {order.ref} passed pickup window validation")
```

#### GraphQL Operations

**Query — Read setting:** _(REUSED pattern — SettingUtils utility)_
```graphql
query {
  settings(first: 1, filter: { name: { eq: $settingName }, context: { eq: "RETAILER" } }) {
    edges { node { lobValue } }
  }
}
```

---

> _Repeat this pattern for each NEW rule._

## 9. Settings **[required when creating/modifying settings]**

| Setting Key | Source | Context | Context ID | Value Type | Shape / Example | Used By (Rule) |
|------------|--------|---------|-----------|-----------|-----------------|---------------|
| curbside.pickup.config | **NEW** | RETAILER | 5 | LOB | `{"maxPickupHours": 48, "zones": ["ZONE_A", "ZONE_B"], "requireVehicleDesc": true}` | R2: ValidatePickupWindow |
| webhook.curbside.notify | **NEW** | RETAILER | 5 | LOB | `{"url": "https://store-api.example.com/curbside/notify", "headers": {"X-Api-Key": "..."}}` | R5: SendWebhook |
| ORDER_HD_FULFILMENT_TYPE | **EXISTING** | RETAILER | 5 | STRING | Already exists, value `"HD"` — no change | — |

> **For NEW settings:** full JSON example showing all fields.
> **For EXISTING settings:** confirm current value and whether it needs modification.
> **For MODIFIED settings:** show before → after diff.

## 10. UI / Manifest Artifacts _(REQUIRED when feature includes manifest/UI work; skip for backend-only features)_

> **Gate:** If any manifest or UI work is planned, this section MUST be completed before `/fluent-mystique-builder` will proceed. The builder checks for this section in the approved plan.

> Required for cross-domain features that include UI changes (Mystique manifest overrides, custom SDK components). Maps directly from Spec §9a (UI / Frontend Requirements).

### 10.1 Spec §9a Coverage

> Every page/view listed in the approved Spec §9a must have a corresponding plan artifact. Missing entries indicate gaps between spec and plan.

| Spec §9a Page / View | Plan Artifact(s) | Status |
|-----------------------|-------------------|--------|
| [e.g., Appeasement list] | Route `/appeasements`, fragment `ordermanagement` | Covered in §10.2, §10.3 |
| [e.g., Appeasement detail] | Route `/appeasements/:id`, fragment `ordermanagement` | Covered in §10.2, §10.3 |
| [e.g., Order detail — appeasements tab] | Modified route `/orders/:id`, tab addition | Covered in §10.2, §10.3 |

### 10.2 Route Definitions

| Path | Section | Page Title | Type | Roles | Guard / Condition | Source |
|------|---------|-----------|------|-------|-------------------|--------|
| `/appeasements` | Orders | Appeasement List | fc.list | ADMIN, OPERATOR | — | NEW |
| `/appeasements/:id` | Orders | Appeasement Detail | fc.page | ADMIN, OPERATOR | — | NEW |
| `/orders/:id` | Orders | Order Detail | fc.page | (existing) | — | MODIFIED — add appeasements tab |

> Path follows Mystique route `path` field convention. Guards use template-string conditions (e.g., `{{hasRole 'ADMIN'}}`). Source uses NEW/EXISTING/MODIFIED classification.

### 10.3 Component Hierarchy

> Nested tree showing the page layout structure per new/modified page. Use indentation to show parent-child component nesting. Use `fc.*` aliases from the Mystique component registry. Mark custom SDK components with `[CUSTOM]`.

**Appeasement List Page** (`/appeasements`):
```
fc.page
  └── fc.list (dataSource: creditMemos)
        columns: Ref, Order Ref, Amount, Status, Created
        filters: status, createdOn (date range)
        rowLink: /appeasements/{{node.id}}
        sort: createdOn DESC
```

**Appeasement Detail Page** (`/appeasements/:id`):
```
fc.page (actions: true, backButtons: [Appeasements])
  ├── fc.card.attribute (title: "Summary", width: half)
  │     attributes: Ref, Status (styled), Amount, Currency, Reason, Created
  ├── fc.card.attribute (title: "Linked Order", width: half)
  │     attributes: Order Ref (link), Order Status, Customer Name
  ├── fc.tabs.card
  │     ├── Tab "Line Items" → fc.list (dataSource: items)
  │     └── Tab "Activity" → fc.activity.entity
  └── fc.conditional (value: status, matches: DRAFT)
        └── [user action: Submit Appeasement]
```

> This hierarchy feeds directly into `/fluent-mystique-builder` Phase 3 (component tree composition).

### 10.4 Data Query Specifications

| Page | Query Type | Root Field | Variables | Key Fields | Pagination | Source |
|------|-----------|-----------|-----------|------------|------------|--------|
| Appeasement list | List | `creditMemos(first, status, type, retailerId)` | `type: "APPEASEMENT"`, `retailerId: {{activeRetailer.id}}` | id, ref, status, totalAmount, currency, createdOn, order.ref | Yes (cursor) | NEW |
| Appeasement detail | Detail | `creditMemoById(id)` | `id: {{id}}` | id, ref, status, totalAmount, currency, createdOn, order { id ref status }, items, attributes | No | NEW |
| Order detail (appeasements tab) | Nested | `creditMemos(first, orderId)` via `fc.provider.graphql` | `orderId: {{id}}` | id, ref, totalAmount, status | Yes | NEW |

> For each page from §10.2, specify the exact GraphQL query. Use the Entity Type Query Map from `/fluent-mystique-builder` for root field names. Variables use Mystique template syntax `{{...}}`. This feeds directly into manifest `data` and `dataSources` blocks.

### 10.5 Manifest Fragment Changes

| Fragment Setting | App | Source | Change Description |
|-----------------|-----|--------|-------------------|
| `fc.mystique.manifest.oms.fragment.ordermanagement` | OMS | MODIFIED | Add appeasement list page, appeasement detail page, appeasement tab on order detail |

> Identify which fragment(s) to override. Prefer modifying existing fragments loaded from settings over creating new ones. See Mystique builder SKILL.md for fragment deployment protocol.

**OOTB Components Used:**
- `fc.page`, `fc.list`, `fc.card.attribute`, `fc.tabs.card`, `fc.conditional`, `fc.activity.entity`, `fc.provider.graphql`

**Custom SDK Components** (if any):

| Component | Registry Alias | Category | Source | Description |
|-----------|---------------|----------|--------|-------------|
| [e.g., TrackingMap] | `acme.content.tracking-map` | content | NEW | Map visualization for pickup location |

> If no custom SDK components are needed, state: "All UI requirements can be met with OOTB Mystique components."

**Custom Template Helpers** (if any):

| Helper | Registry Alias | Source | Description | Used In |
|--------|---------------|--------|-------------|---------|
| [e.g., formatWeight] | `acme.helpers.format-weight` | NEW | Format weight with unit suffix | Product detail card, list column |

> Template helpers are registered via `TemplateRegistry.register()` in an SDK project's `src/index.tsx`. They extend the Handlebars expression engine used in manifest template strings (e.g., `{{acme.helpers.format-weight node.weight 'kg'}}`). Use `/fluent-mystique-component --type template` to scaffold. Convention: `<namespace>.helpers.<kebab-name>`. If no custom template helpers are needed, omit this table — built-in helpers (`currency`, `dateFormat`, `i18n`, `eq`, etc.) require no SDK work.

### 10.6 Build vs Configure Decision

- [ ] Can this be achieved with manifest configuration alone? (preferred)
- [ ] Does this require a custom SDK component? If yes, scaffold via `/fluent-mystique-scaffold`
- [ ] SDK source available? Ask user first for an existing SDK repo/folder (preferred). If missing, download from `https://fluent.apps.fluentcommerce.com/_sdk/latest/omx-component-sdk.zip`, extract, and place project under `accounts/<PROFILE>/SOURCE/frontend/`
- [ ] Local-first loop defined? (`/fluent-frontend-build dev` + manifest `plugins[]` -> `http://localhost:3001/` for rapid iteration)
- [ ] Local server reuse gate defined? (check `http://localhost:3001/version.json` first; reuse existing server instead of starting duplicates)
- [ ] Release gate defined? (`/fluent-frontend-build full` + `/fluent-frontend-review` + `/fluent-ui-test` before sign-off)
- [ ] Safety noted? Ensure deployed manifests do not retain localhost plugin URLs

### 10.7 i18n Keys

| Key | Default (en) | Context |
|-----|-------------|---------|
| `i18n:fc.om.appeasements.nav` | Appeasements | Sidebar navigation label |
| `i18n:fc.om.appeasements.list.title` | Appeasements | List page title |
| `i18n:fc.om.appeasements.detail.title` | Appeasement {{ref}} | Detail page title |
| `i18n:fc.om.appeasements.column.ref` | Reference | List column header |
| `i18n:fc.om.appeasements.column.amount` | Amount | List column header |

> List all translatable strings. Follow the `i18n:fc.<app>.<section>.<element>` naming convention. These go into the manifest `i18n:` prefixed values.

### 10.8 Visual Mock / Layout Sketch _(optional but recommended)_

> Show how the UI looks before implementation. Three tiers, use the highest fidelity available:

**Tier 1: Live UI Injection (best — when environment is accessible)**

Navigate to the real Fluent OMS/Store app via Chrome DevTools MCP, take a "Before" screenshot, inject DOM changes + annotation CSS, take an "After" screenshot. The theme, data, and layout are 100% real.

```
features/<slug>/assets/<page>-before.png     ← Real app before changes
features/<slug>/assets/<page>-after.png      ← Real app with injected changes + annotations
```

```markdown
**Before (current OMS order list):**
![Before](assets/order-list-before.png)

**After (with Channel column injected + annotations):**
![After](assets/order-list-after.png)
```

**Tier 2: MUI CDN Mock (fallback — offline or custom SDK components)**

Build a self-contained HTML file loading React 17 + Material-UI 4.12 from CDN (same versions as Fluent OMS SDK). Best for custom components that don't exist in the app yet.

```
features/<slug>/assets/<component>-mock.html     ← Self-contained HTML with React+MUI from CDN
features/<slug>/assets/<component>-fullpage.png   ← Full-page screenshot
```

**Tier 3: ASCII wireframe (quick layout sketch)**

```
┌─────────────────────────────────────────┐
│ Order Detail                            │
├────────────────┬────────────────────────┤
│ Summary Card   │ Status Timeline [NEW]  │
│ ref, type, $   │ ● CREATED  → 10:15    │
│                │ ● BOOKED   → 10:22    │
├────────────────┴────────────────────────┤
│ Fulfilments Table                       │
└─────────────────────────────────────────┘
```

**Annotation conventions** (shared across all tiers):
- **Blue highlight** (`rgba(26,115,232,0.06)` + `border-left: 2px solid #1a73e8`) — marks NEW elements
- **"NEW" badge** (`background: #1a73e8; color: white; font-size: 9px; padding: 2px 6px; border-radius: 8px`) — labels new columns/cards/sections
- **Pulsing glow** (`@keyframes pulseGlow`) — draws attention to changed headers
- **Yellow callout** (`#fff8e1` background, `#ffb300` border) — explanation annotation

> **Cross-reference:** Mock review is Phase 4b in the feature planning workflow (SKILL.md). For manifest building, use `/fluent-mystique-builder`. For manifest validation, use `/fluent-mystique-validate`. For SDK scaffolding, see `/fluent-mystique-scaffold`.

## 11. Webhooks _(optional)_

| Setting Name | Source | Method | Endpoint Pattern | Trigger Rule | Payload Shape |
|-------------|--------|--------|-----------------|-------------|---------------|
| webhook.curbside.notify | **NEW** | POST | `${url}` from setting | R5: SendWebhook (OOTB) | `{"orderRef": "...", "pickupTime": "...", "locationRef": "...", "vehicleDesc": "..."}` |

> Omit this section if no webhooks are involved.

## 12. Scheduled Events _(optional)_

| Event Name | Source | Delay | Entity Type | Triggered By (Rule) | Purpose |
|-----------|--------|-------|-------------|-------------------|---------|
| CurbsideTimeout | **NEW** | 30min | ORDER | R6: ScheduleExpiry (OOTB) | Cancel pickup if not collected |

> Omit this section if no scheduled events are involved.

## 13. GraphQL Operations **[required when rules make GraphQL calls]**

| Operation | Source | Type | Root Field | Selected Fields | Used By (Rule) | Purpose |
|-----------|--------|------|-----------|----------------|---------------|---------|
| Fetch order items | **REUSED** pattern from CreateFulfilmentFromSourcingLocation | Query | orderById.items | id, ref, quantity | R1: CreateCurbsidePickupRule | Get items for fulfilment creation |
| Create fulfilment | **REUSED** pattern | Mutation | createFulfilment | ref, type, order, items, attributes | R1: CreateCurbsidePickupRule | Create CURBSIDE fulfilment |
| Read setting | **REUSED** — SettingUtils | Query | settings | lobValue | R2: ValidatePickupWindow | Read pickup config |

> **"REUSED pattern"** means existing code in the codebase already does this — copy the approach.
> **"NEW"** would mean a novel GraphQL operation not seen in the codebase.

## 14. Cross-Entity Impact **[required for multi-entity features]**

| Source Entity | Target Entity | Mechanism | Source | Action |
|--------------|--------------|-----------|--------|--------|
| ORDER | FULFILMENT | createFulfilment mutation | **NEW** (in R1: CreateCurbsidePickupRule) | Creates CURBSIDE fulfilment |
| ORDER | FULFILMENT | SendEvent (OOTB R3) | **REUSED** | Fires CurbsidePickupCreated |
| FULFILMENT | ORDER | SendEvent (OOTB) | **REUSED** | Status update event back to ORDER |

> Omit this section for single-entity features.

## 14a. Roles & Permissions **[required when feature has role-differentiated behavior]**

> CONDITIONAL: Include when the feature restricts actions by role, introduces new roles, or needs custom role-checking in rules. Skip for features with no role considerations. Cross-reference with spec §4a.

| Role | Existing/New | Enforcement Layer | Capabilities in this Feature | Source |
|------|-------------|-------------------|------------------------------|--------|
| [e.g., RETURNS_MANAGER] | EXISTING | Platform (Transition API) | Can trigger ApproveReturn, RejectReturn user actions | EXISTING |
| [e.g., RETURNS_APPROVER] | NEW | Custom (rule checks `me.roles`) | Can approve returns over $500 | NEW |
| [e.g., ADMIN] | EXISTING | Manifest (`hasRole`) | Can see "Admin Settings" page | EXISTING |

**Context scoping:**
- [e.g., Store app users (AGENT context) see only their location's returns]
- [e.g., OMS users (RETAILER context) see all returns for their retailer]

**`actionedBy` wiring:** [Yes/No — if yes, specify which user actions pass `{{me.username}}` and which rules consume it via `@EventAttribute`]

**New roles to create:**
| Role Name | Permissions | Notes |
|-----------|-------------|-------|
| [e.g., RETURNS_APPROVER] | None (label-only) | Checked in ApproveReturnRule via `me.roles` query |

> If no new roles needed, state: "All roles already exist — no new roles required."

## 15. Files **[required]**

| # | Action | Path | Source | Description |
|---|--------|------|--------|-------------|
| 1 | Create | `.../src/main/java/.../CreateCurbsidePickupRule.java` | NEW | Rule R1 |
| 2 | Create | `.../src/test/java/.../CreateCurbsidePickupRuleTest.java` | NEW | Tests for R1 (4 tests) |
| 3 | Create | `.../src/main/java/.../ValidatePickupWindowRule.java` | NEW | Rule R2 |
| 4 | Create | `.../src/test/java/.../ValidatePickupWindowRuleTest.java` | NEW | Tests for R2 (3 tests) |
| 5 | Modify | `.../resources/module.json` | MODIFIED | Wire R1, R2 registrations |
| 6 | Modify | `accounts/.../workflows/.../ORDER__HD.json` | MODIFIED | Add CurbsidePickupRequested + ConfirmCurbsidePickup rulesets |
| 7 | Create | `accounts/.../workflows/.../FULFILMENT__CURBSIDE.json` | NEW | New curbside fulfilment workflow |

> Backend paths relative to `accounts/<PROFILE>/SOURCE/backend/<repo>/`. Frontend paths relative to `accounts/<PROFILE>/SOURCE/frontend/<project>/`.

## 16. Risks & Mitigations **[required]**

| Risk | Severity | Impact | Mitigation |
|------|----------|--------|-----------|
| Ruleset trigger overlaps with existing OnCreate | HIGH | Events route to wrong ruleset | Verify trigger uniqueness via workflow-analyzer |
| New rule throws on null pickup location | MEDIUM | Order stuck in current status | Unit test null/empty inputs; validation guard |
| Webhook endpoint not provisioned at go-live | LOW | Notification silently fails | Setting updated later; log warning |

## 17. Test Plan **[required]**

| # | Test | Type | Event | Expected Status | Assertion |
|---|------|------|-------|----------------|-----------|
| 1 | Happy path: curbside pickup | E2E | CurbsidePickupRequested | ORDER → AWAITING_PICKUP | status + fulfilment created |
| 2 | Pickup collected | E2E | ConfirmCurbsidePickup | ORDER → PICKED_UP | status + fulfilment COLLECTED |
| 3 | Timeout cancellation | E2E | (wait 30min) | ORDER → CANCELLED | scheduled event fired |
| 4 | Regression: normal HD | E2E | CreateOrder | ORDER → BOOKED | Existing flow unaffected |
| 5 | R1: creates fulfilment | Unit | — | — | Mutation called with correct args |
| 6 | R1: throws on missing location | Unit | — | — | RuntimeException |
| 7 | R2: rejects expired window | Unit | — | — | RuntimeException |
| 8 | R2: passes valid pickup time | Unit | — | — | No exception, log written |

## 18. Deployment Sequence **[required for environment changes]**

| # | Step | Depends On | Skill | Rollback |
|---|------|-----------|-------|----------|
| 1 | Validate module structure | — | `/fluent-module-validate` | N/A |
| 2 | Build module v1.2.4 | 1 | `/fluent-build` | N/A (local) |
| 3 | Pre-deploy check | 2 | `/fluent-pre-deploy-check` | N/A |
| 4 | Deploy module | 3 (READY) | `/fluent-module-deploy` | Re-deploy v1.2.3 |
| 5 | Upload ORDER::HD v13 | 4 | `/fluent-workflow-deploy` | Re-upload v12 |
| 6 | Upload FULFILMENT::CURBSIDE v1 | 4 | `/fluent-workflow-deploy` | Delete workflow |
| 7 | Create settings (2) | 5, 6 | `/fluent-settings` | Delete settings |
| 8 | E2E test | 7 | `/fluent-e2e-test` | — |

> **Ordering constraint:** Module deploy (step 4) must complete before workflow deploy (steps 5-6). Workflows reference rules that are registered during module deployment. Deploying workflows before the module causes NO_MATCH events.

## 19. Rollback Plan _(optional — recommended for production changes)_

> How to reverse this change:
> 1. Re-upload ORDER::HD v12 via `/fluent-workflow-deploy`
> 2. Delete FULFILMENT::CURBSIDE workflow (or leave dormant -- no triggers will route to it)
> 3. Delete/disable webhook and config settings
> 4. Re-deploy module v1.2.3 via `/fluent-module-deploy`
> 5. Note: New statuses (AWAITING_PICKUP, PICKED_UP) become dormant (no triggers route to them)

## Deliberation Log

> Auto-generated by the self-critique protocol (Phase 4a). Shows what was reviewed, what was changed, and why.
> See `CRITIQUE_PROTOCOL.md` for the full protocol, rubric, and guardrails.

### Round 1

| # | Severity | Finding | Evidence | Verdict | Action |
|---|----------|---------|----------|---------|--------|
| C1 | [CRITICAL/HIGH/MEDIUM/LOW] | [Finding text] | [Evidence] | [ACCEPTED/REJECTED/DEFERRED] | [Action taken or rejection reason] |

<!-- Additional rounds if needed -->

### Summary
- **Rounds:** N (exit: [clean pass / converged / max reached])
- **Findings:** X total — Y accepted, Z rejected, W deferred
- **Plan changes:** [list of accepted changes applied]
````

---

## Section Applicability Guide

Not every plan needs every section. Use this guide:

| Section | When Required |
|---------|-------------|
| 1. Business Context | **Always** |
| 2. Scope | **Always** |
| 3. Architecture & Diagrams | **Always** (at least one diagram) |
| 4. Workflows | **Always** (even "No workflow changes" is valid) |
| 5. Statuses | When adding or modifying workflow statuses |
| 6. Rulesets — Change Summary | **Always** when touching workflows (quick-glance table of what's changing) |
| 6.1 Complete Workflow Inventory | When modifying an existing workflow. **Full rules + props for every ruleset** — the single source of truth |
| 7. Rules Inventory | **Always** when the feature involves rules |
| 8. Detailed Design (New Rules) | When any rule is **NEW** |
| 9. Settings | When creating/modifying settings |
| 10. UI / Manifest Artifacts | **REQUIRED** when the feature includes any manifest/UI work (Mystique manifest overrides or custom SDK components). `/fluent-mystique-builder` checks for this section before proceeding |
| 11. Webhooks | When adding webhook-based rules |
| 12. Scheduled Events | When adding time-delayed events |
| 13. GraphQL Operations | When rules make GraphQL calls |
| 14. Cross-Entity Impact | When feature spans multiple entity types |
| 15. Files | **Always** |
| 16. Risks | **Always** |
| 17. Test Plan | **Always** |
| 18. Deployment Sequence | When deploying to an environment |
| 19. Rollback Plan | Recommended for production; optional for dev/sandbox |

## Rules Classification Decision Flow

Before classifying any rule, follow this decision tree:

1. **Search `plugin.list`** for an OOTB match by name or description
2. **If OOTB exists and fits** → label as **OOTB**, document the prop values to configure
3. **If OOTB exists but doesn't quite fit** → document why. Consider wrapping (OOTB + custom pre/post rule) vs writing from scratch
4. **Search existing custom source** (`accounts/<PROFILE>/SOURCE/backend/` for Java rules, `SOURCE/frontend/` for SDK components) for a similar rule
5. **If existing custom fits** → label as **EXISTING**, confirm no changes needed
6. **If existing custom needs tweaks** → label as **MODIFIED**, show before → after
7. **If pattern exists** but no reusable rule → label the approach as **REUSED** in GraphQL Operations (§13)
8. **Only when no viable alternative exists** → label as **NEW**, provide full pseudo logic in §8

## Diagram Requirements

Plans MUST include visual Mermaid diagrams. Include ALL that apply:

| Diagram | When Required | Mermaid Type |
|---------|-------------|-------------|
| State machine | Any workflow creation or modification | `stateDiagram-v2` |
| Cross-entity event flow | Cross-entity events, webhooks, multi-step processing | `sequenceDiagram` |
| Data flow | Complex processing, decision trees, event propagation | `flowchart` |
| Before → After diff | Modifications to existing workflows | Text or `workflow.diff` |
| Entity relationships | Module scaffolding, retailer config | `erDiagram` or `flowchart` |
| Workflow processing flow | Modifying an existing workflow with multiple rulesets | `flowchart TD` |

For workflow modifications, the state diagram MUST show ALL statuses (not just new ones) and annotate changes with `:::added`, `:::removed`, `:::modified`. The workflow processing flow (§3.6) MUST show all rulesets in execution context with NEW/MODIFIED highlighted.

**Syntax validation:** Before writing any plan file, validate all Mermaid blocks against `knowledge/mermaid-syntax-rules.md` and the Mermaid Syntax Rules below.

## Mermaid Syntax Rules

**MANDATORY: Validate every Mermaid block against these rules before outputting.**

### All Diagrams
- First line after the mermaid fence MUST be the diagram type (`stateDiagram-v2`, `sequenceDiagram`, `flowchart TD`, etc.) — no blank line between fence and type
- Node/state IDs: alphanumeric and underscores only. NO spaces, NO hyphens, NO special chars. Use `as` aliasing for display names
- Every `subgraph`, `alt`, `opt`, `loop`, `par`, `state { }` block MUST have a matching `end`
- No HTML tags in labels (use `<br/>` only in flowchart node labels, nowhere else)
- No trailing semicolons

### stateDiagram-v2
- MUST use `stateDiagram-v2` (NOT `stateDiagram` — v1 has different syntax)
- Start/end: `[*]` (brackets required)
- Transitions: `StateA --> StateB : EventName` (double dash + arrow)
- WRONG: `->` (single arrow), `=>`, `-->` without space before `:`
- Composite: `state "Display Name" as alias`
- Notes: `note right of StateName : text`
- Style annotations: `StateName:::className` (three colons)

### sequenceDiagram
- Declare participants: `participant A as "Display Name"` — MUST appear before first use
- Messages: `A->>B: text` (solid request), `A-->>B: text` (dashed response), `A-)B: text` (async)
- WRONG: `A->B` (renders as different arrow type, usually not what you want)
- WRONG: `A-->B` (this is a dotted line without arrowhead)
- Activations: `activate A` / `deactivate A` on separate lines, OR `A->>+B:` / `B-->>-A:`
- Blocks: `alt condition` / `else other` / `end` — every block needs `end`
- Notes: `Note over A,B: text` or `Note right of A: text` (capital N)

### flowchart
- MUST declare direction: `flowchart TD` (top-down), `flowchart LR` (left-right), `flowchart RL`, `flowchart BT`
- WRONG: `flowchart` alone (no direction = parse error), `graph TD` (deprecated, use `flowchart`)
- Node shapes: `A[rect]`, `A(rounded)`, `A{diamond}`, `A((circle))`, `A>flag]`, `A([stadium])`, `A[[subroutine]]`
- Arrows: `-->` solid, `-.->` dotted, `==>` thick
- Labels on arrows: `A -->|label text| B` (pipes around label)
- WRONG: `A --> label --> B`, `A --label--> B`
- Subgraphs: `subgraph Title` ... `end` — can be nested
- Node IDs MUST NOT start with a digit or contain spaces: `node1` OK, `1node` BAD, `my node` BAD

### classDiagram
- Class: `class ClassName { +String field; +method() }` — visibility prefixes: `+` public, `-` private, `#` protected
- Relationships: `A *-- B` composition, `A o-- B` aggregation, `A --> B` dependency, `A --|> B` inheritance

### erDiagram
- Entities: plain names `ORDER`, `FULFILMENT` (no brackets)
- Relationships: `ORDER ||--o{ FULFILMENT : "has"` — cardinality markers: `||` exactly one, `o|` zero-or-one, `}|` one-or-more, `}o` zero-or-more
- WRONG: using flowchart syntax, missing relationship label string

### Pre-Output Checklist
Before including any mermaid block in output:
1. Diagram type on first line? (no blank line after fence)
2. All IDs alphanumeric/underscore only?
3. All blocks closed with `end`?
4. Arrow syntax correct for this diagram type?
5. Participant/node declarations before first use?
6. No mixing of syntax from different diagram types?

## Plan Lifecycle

1. **Create** — Agent writes plan file with `Status: PENDING`, `Revision: 1`. Creates the plans directory if needed.
2. **Present** — Agent shows the full plan content to the user and waits for approval.
3. **Approve/Reject** — User responds:
   - **Approved** ("yes", "go ahead", "approved", "do it") → update `Status: APPROVED`, `Approved by: user`, proceed
   - **Rejected with feedback** → increment `Revision`, revise content, reset `Status: PENDING`, re-present
   - **Questions** → answer without changing the file. If answers lead to changes, revise and increment revision
   - **"Just do it" / "skip planning"** → write `Status: APPROVED (auto-skip)`, `Approved by: user (gate skipped)`, proceed
4. **Reference** — During implementation, the plan file is the single source of truth for what was agreed.
5. **Post-implementation** — Session summary references the plan file path. Future sessions can read approved plans.
