# CPN Schema Reference

## Overview

This document is a complete reference for the CPN (Component Part Number) Schema YAML specification. The schema defines the structure and constraints for CPN generation schemes in the Duro PLM system.

## Schema Information

* **Schema Version**: 1.0
* **JSON Schema**: `https://json-schema.org/draft-07/schema#`
* **Type**: Object

## Root Level Properties

| Property      | Type   | Required | Description                                                                                               |
| ------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------- |
| `version`     | string | Yes      | Version of the CPN schema. Must match pattern `^\d+\.\d+$`                                                |
| `schema_type` | string | Yes      | Must be `"cpn_generation_scheme"` (or `"cpn_generation_scheme_custom"` for custom template-group schemes) |
| `elements`    | array  | Yes      | Array of element definitions (declared first — this is the structural backbone)                           |
| `settings`    | object | Yes      | Global settings, including `element_mappings` that bind elements to system inputs                         |
| `examples`    | array  | Yes      | At least one example CPN that follows the scheme                                                          |

### Recommended Authoring Order

The default schemes shipped with Duro (and the order that is easiest for a human to read) declare keys in this order:

```yaml
version: '1.0'
schema_type: cpn_generation_scheme
elements:
  # ... structural definition of the CPN
settings:
  element_mappings:
    category: category
  allow_override: false
  allow_freeform: false
examples:
  - '410-00001'
```

You define the `elements` first because they describe *what* the CPN looks like. You then describe *how it behaves* under `settings`, including which element maps to the system's category input.

## Settings Object

The `settings` object contains global configuration for CPN generation.

```yaml
settings:
  element_mappings:            # Required when an element resolves from system data (e.g. categories)
    category: <element_name>
    variant: <element_name>    # Optional, only used when a variant element is present
  allow_override: boolean      # Default: false
  allow_freeform: boolean      # Default: false
  override_elements: [string]  # Default: null (all elements)
  freeform_validation:         # Optional, only used when allow_freeform is true
    pattern: string
    max_length: integer
    description: string
```

| Property              | Type    | Required | Default | Description                                                                                                                                                                    |
| --------------------- | ------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `element_mappings`    | object  | Cond.    | —       | Binds scheme element names to system inputs. Required when any element resolves from a system value (for example, a `list` element using `${{duro.categories}}`).              |
| `allow_override`      | boolean | No       | `false` | Whether manual CPN override is allowed. If `false`, the user must accept the system-generated CPN. If `true`, the user may enter another CPN (see `allow_freeform` for rules). |
| `allow_freeform`      | boolean | No       | `false` | Defines how overrides behave. If `false`, overrides must conform to the scheme. If `true`, overrides may be any unique valid string.                                           |
| `override_elements`   | array   | No       | `null`  | Element names that can be overridden individually. Only applies when `allow_override` is `true`. See "Element-Level Override Control" below.                                   |
| `freeform_validation` | object  | No       | —       | Custom validation for freeform overrides. Only applies when `allow_freeform` is `true`. Defaults to alphanumeric/hyphen/underscore with 50-character limit.                    |

### Element Mappings

`element_mappings` is the bridge between your scheme and the values Duro provides at generation time. When the generator receives a `categoryId` (or a variant identifier), it looks up which scheme element should receive that value.

```yaml
settings:
  element_mappings:
    category: category   # Bind the "category" input to the element named "category"
    variant: variant     # Optional — bind variant inputs to a variant element
```

| Key        | Required | Description                                                                                                                                                                 |
| ---------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `category` | Cond.    | The `name` of the `list` element that represents the part category. Required when the element draws values from `${{duro.categories}}` or any other category-driven source. |
| `variant`  | No       | The `name` of the element that represents the variant. Only set when your scheme has a variant element.                                                                     |

The key (`category` / `variant`) is the **system input**; the value is the **element `name`** declared in the `elements` array. They do not need to match — for example, if your category list element is named `prefix`, you would write `category: prefix`.

{% hint style="info" %}
Without `element_mappings.category`, a category-driven list element has no way to know which value to render and CPN generation will fail. This is the single most common reason a custom scheme rejects valid input.
{% endhint %}

### Freeform Validation Object

When `allow_freeform` is `true`, you can optionally specify custom validation rules:

| Property      | Type    | Required | Description                                                                                     |
| ------------- | ------- | -------- | ----------------------------------------------------------------------------------------------- |
| `pattern`     | string  | No       | Regex pattern that freeform CPNs must match. If not specified, uses default `^[a-zA-Z0-9\-_]+$` |
| `max_length`  | integer | No       | Maximum length for freeform CPNs. If not specified or ≤ 0, defaults to 50 characters            |
| `description` | string  | No       | Human-readable description of the format requirements shown to users                            |

#### Freeform Validation Examples

**Basic freeform with default validation:**

```yaml
settings:
  allow_override: true
  allow_freeform: true
  # Uses default pattern ^[a-zA-Z0-9\-_]+$ with 50 character limit
```

**Custom pattern for company naming convention:**

```yaml
settings:
  allow_override: true
  allow_freeform: true
  freeform_validation:
    pattern: "^[A-Z]{2,4}-\\d{4,6}$"
    max_length: 20
    description: "Format: 2-4 letters, hyphen, 4-6 digits"
```

### Element-Level Override Control

The `override_elements` setting controls which specific elements a user can override when `allow_override` is `true`.

| `override_elements`        | Behavior                                                       |
| -------------------------- | -------------------------------------------------------------- |
| `null` (default)           | All elements are overrideable when `allow_override` is `true`. |
| `["element1", "element2"]` | Only the listed elements can be overridden.                    |

```yaml
settings:
  allow_override: true
  allow_freeform: false
  override_elements: ["variant"]   # Only "variant" can be overridden
```

### Behavior Matrix

| `allow_override` | `allow_freeform` | `override_elements` | Behavior                                                                                                                |
| ---------------- | ---------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `false`          | `false`          | *ignored*           | User must accept the system-generated CPN. No manual input allowed.                                                     |
| `false`          | `true`           | *ignored*           | Same as above — `allow_freeform` has no effect when `allow_override` is `false`.                                        |
| `true`           | `false`          | `null` (default)    | User may override entire CPN or individual elements, but values must conform to the scheme.                             |
| `true`           | `false`          | `["element1"]`      | User may override only the listed elements. Values must conform to each element's validation rules.                     |
| `true`           | `true`           | `null` (default)    | User may override with freeform text or override individual elements. Freeform validates against `freeform_validation`. |
| `true`           | `true`           | `["element1"]`      | User may override with freeform text or override the listed elements.                                                   |

## Elements

The `elements` array contains definitions for each component of the CPN. Each element must be one of:

* `list` — selects a value from a predefined list or system reference
* `constant` — inserts a fixed value such as a delimiter or prefix
* `numeric_counter` — generates sequential numeric values within a range
* `hex_counter` — generates sequential hexadecimal values within a range
* `alpha_counter` — generates sequential uppercase alphabetic values within a range
* `free` — allows user-entered text validated by a regex and max length (only allowed inside a `group`)
* `group` — bundles multiple elements into a single logical unit

### Common Element Properties

All elements include these base properties:

| Property     | Type    | Required | Description                                                                                                |
| ------------ | ------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| `type`       | string  | Yes      | Element type identifier.                                                                                   |
| `name`       | string  | Yes      | Unique name for the element. Used for cross-references such as `attachedTo` and `element_mappings`.        |
| `required`   | boolean | No       | Whether the element must appear in every generated CPN. Defaults to `false`.                               |
| `attachedTo` | array   | No       | Names of one or more elements this element is **scoped to**. Ensures uniqueness within the attached scope. |

#### About `attachedTo`

`attachedTo` is commonly used with counters and variants to ensure values are unique within the context of one or more parent elements. For example, a sequence counter attached to `category` will track an independent sequence per category. Multiple attachments are supported (e.g. `['prefix', 'sequence']`).

```yaml
- type: numeric_counter
  name: sequence
  required: true
  attachedTo: ['category']
  format:
    min_value: 1
    max_value: 99999
```

### List Element

A `list` element selects from a predefined set of values, an object array with metadata, or a system-provided template reference.

```yaml
- type: list
  name: string
  required: boolean
  use: string                 # Optional — pick a field when values are objects (e.g. "id")
  values: (string[] | object[] | template_string)
  validation:
    pattern: string           # Optional regex pattern
```

| Property             | Type            | Required | Description                                    |
| -------------------- | --------------- | -------- | ---------------------------------------------- |
| `values`             | array \| string | Yes      | Array of values or a template reference        |
| `use`                | string          | No       | When values are objects, which field to render |
| `validation.pattern` | string          | No       | Regex pattern to additionally validate values  |

Values must be one of:

1. Array of strings
2. Array of objects with `id`, `name`, and optional `description`
3. Template reference in the form `${{namespace.field}}`

#### List Element Examples

```yaml
# Pull values from the system's categories
- type: list
  name: category
  required: true
  values: ${{ duro.categories }}

# Inline string values with a validation pattern
- type: list
  name: prefix
  required: true
  values:
    - "410"
    - "591"
    - "423"
  validation:
    pattern: "^\\d{3}$"

# Object values with metadata, rendered by id
- type: list
  name: family
  required: true
  use: id
  values:
    - id: "410"
      name: Screws
      description: Mechanical fasteners
    - id: "591"
      name: Resistors
      description: Electronic components
```

{% hint style="info" %}
When a list element uses `${{duro.categories}}` (or any other category-driven source), you must declare `settings.element_mappings.category` so the generator can resolve the inbound `categoryId`.
{% endhint %}

### Constant Element

Constants are fixed strings injected into every generated CPN. Use them for delimiters (`-`, `.`) or static prefixes/suffixes.

```yaml
- type: constant
  name: string
  required: boolean
  value: string
```

| Property | Type   | Required | Description        |
| -------- | ------ | -------- | ------------------ |
| `value`  | string | Yes      | The constant value |

```yaml
- type: constant
  name: separator
  required: true
  value: "-"
```

### Numeric Counter

A `numeric_counter` generates sequential integers within a range. It is always **fixed length**, determined by the number of digits in `format.max_value`. Leading zeros are prepended automatically.

```yaml
- type: numeric_counter
  name: string
  required: boolean
  attachedTo: string[]
  format:
    min_value: integer       # Must be ≥ 0
    max_value: integer
```

```yaml
- type: numeric_counter
  name: sequence
  required: true
  attachedTo: ['category']
  format:
    min_value: 1
    max_value: 99999
```

The example above generates a 5-digit sequence (`00001` through `99999`), tracked independently per category:

* `410-00001`, `410-00002`, `410-00003`
* `591-00001` (independent sequence for category `591`)
* `410-00004` (continues category `410`)

### Hex Counter

A `hex_counter` generates sequential hexadecimal values within a range. Fixed length, zero-padded.

```yaml
- type: hex_counter
  name: string
  required: boolean
  attachedTo: string[]
  format:
    min_value: string        # Pattern: ^[0-9A-F]+$
    max_value: string        # Pattern: ^[0-9A-F]+$
```

```yaml
- type: hex_counter
  name: sequence
  required: true
  attachedTo: ['category']
  format:
    min_value: "0"
    max_value: "FF"
```

Generates `00`, `01`, … `FE`, `FF`.

### Alpha Counter

An `alpha_counter` generates sequential uppercase alphabetic values within a range. Like other counters, it is fixed length, determined by the length of `format.max_value`.

```yaml
- type: alpha_counter
  name: string
  required: boolean
  attachedTo: string[]
  format:
    min_value: string        # Pattern: ^[A-Z]+$
    max_value: string        # Pattern: ^[A-Z]+$
```

```yaml
- type: alpha_counter
  name: revision
  required: true
  attachedTo: ['base_cpn']
  format:
    min_value: "A"
    max_value: "Z"
```

Generates `A`, `B`, `C`, …, `Z`. With `min_value: "AA"` / `max_value: "ZZ"`, generates two-letter sequences (`AA`, `AB`, …, `ZZ`).

### Group Element

Groups bundle multiple elements into a single logical unit. They are useful for scoping (a variant attaches to the group rather than to each member element) and for organizing complex schemes.

```yaml
- type: group
  name: string
  required: boolean
  reusable: boolean          # Optional, default false
  attachedTo: string[]       # Optional
  elements: array
```

| Property     | Type    | Required | Description                                                                                                                                       |
| ------------ | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `elements`   | array   | Yes      | Array of nested element definitions (at least one).                                                                                               |
| `reusable`   | boolean | No       | When `true`, group instances are tracked and reused. The inner counter's `attachedTo` defines what makes an instance unique. Defaults to `false`. |
| `attachedTo` | array   | No       | Names of elements this group is scoped to.                                                                                                        |

Group elements can contain: `list`, `constant`, `free`, `numeric_counter`, `hex_counter`, and `alpha_counter`. The `free` element type is only valid inside a group.

#### Group Example — Optional Variant Suffix

```yaml
- type: group
  name: variant_group
  required: false
  elements:
    - type: constant
      name: separator
      value: "."
    - type: free
      name: variant
      validation:
        pattern: "^\\w{1,10}$"
```

Produces CPNs such as `123-4567.A` or `123-4567.TEST1`.

#### Group Example — Base CPN with Variant

```yaml
- type: group
  name: base_cpn
  required: true
  elements:
    - type: list
      name: prefix
      required: true
      values:
        - id: "Category100"
          name: "100"
      validation:
        pattern: "^[1-9][0-9]{2}$"
    - type: constant
      name: delimiter_1
      value: "-"
    - type: numeric_counter
      name: sequence
      required: true
      attachedTo: ['prefix']
      format:
        min_value: 1
        max_value: 99999

- type: constant
  name: delimiter_2
  value: "-"

- type: list
  name: variant
  required: true
  attachedTo: ['base_cpn']
  values:
    - id: "A"
      name: "A"
    - id: "B"
      name: "B"
  validation:
    pattern: "^[A-Z]{1}$"
```

`variant` is attached to the `base_cpn` group, so variant values are scoped to the complete base number rather than to `prefix` and `sequence` separately.

### Free Text Element

The `free` element accepts user-entered text. It is only valid **inside a group**, and `allow_override` must be `true` for the user to be able to enter a value.

```yaml
- type: free
  name: string
  validation:
    pattern: string
    max_length: integer
```

| Property                | Type    | Required | Description                  |
| ----------------------- | ------- | -------- | ---------------------------- |
| `validation.pattern`    | string  | Yes      | Regex pattern for validation |
| `validation.max_length` | integer | Yes      | Maximum length of the text   |

## Examples Array

The `examples` array must contain at least one example CPN that follows the defined scheme.

```yaml
examples:
  - "410-00001"
  - "591-00042"
```

## Template References

Template references pull dynamic value lists from the system:

```yaml
values: "${{ namespace.field }}"
```

Template references must match the pattern `^\$\{\{\s*[\w\.]+\s*\}\}$`. Currently the only supported namespace is `duro`, and the only supported fields are `categories` and `families`. When a list element uses a category template reference, remember to set `settings.element_mappings.category`.

## Complete Schema Examples

### Example 1: Basic Semi-Intelligent Scheme

This is the default semi-intelligent scheme shipped with Duro. Categories come from the system; the sequence is tracked per category.

```yaml
version: '1.0'
schema_type: cpn_generation_scheme
elements:
  - name: category
    type: list
    values: ${{ duro.categories }}
    required: true
  - name: category_counter_separator
    type: constant
    required: true
    value: '-'
  - name: sequence
    type: numeric_counter
    required: true
    format:
      min_value: 1
      max_value: 99999
    attachedTo:
      - category
settings:
  element_mappings:
    category: category
  allow_freeform: false
  allow_override: false
examples:
  - '410-00001'
  - '591-00042'
  - '423-01234'
```

### Example 2: Non-Intelligent (Pure Sequence)

A flat numeric scheme with no categories and therefore no `element_mappings`.

```yaml
version: '1.0'
schema_type: cpn_generation_scheme
elements:
  - name: sequence
    type: numeric_counter
    required: true
    format:
      min_value: 100001
      max_value: 999999
settings:
  allow_freeform: false
  allow_override: false
examples:
  - '100001'
  - '100042'
  - '101234'
```

### Example 3: Category, Sequence, and Variant

A scheme with an overrideable variant element. Because the variant identifier is a system input, `element_mappings.variant` is declared.

```yaml
version: '1.0'
schema_type: cpn_generation_scheme

elements:
  - type: list
    name: category
    required: true
    use: id
    values:
      - id: "410"
        name: Screws
        description: Mechanical fasteners
    validation:
      pattern: "^\\d{3}$"

  - type: constant
    name: separator
    required: true
    value: "-"

  - type: numeric_counter
    name: sequence
    required: true
    attachedTo: [category]
    format:
      min_value: 1
      max_value: 9999

  - type: list
    name: variant
    required: true
    attachedTo: [category, sequence]
    values: ["A", "B", "C"]

settings:
  element_mappings:
    category: category
    variant: variant
  allow_override: true
  allow_freeform: true
  override_elements: ["variant"]
  freeform_validation:
    pattern: "^[A-Z]{2,4}-\\d{4,6}$"
    max_length: 20
    description: "Format: 2-4 letters, hyphen, 4-6 digits"

examples:
  - "410-0001-A"
  - "410-0001-B"
```

## Validation Rules

1. `version` must match `^\d+\.\d+$`.
2. `schema_type` must be `"cpn_generation_scheme"` (or `"cpn_generation_scheme_custom"` for custom template-group schemes).
3. All element `name` values must be unique within their scope.
4. Counter ranges must be valid (`min_value` ≤ `max_value`, and `min_value` ≥ 0 for numeric counters).
5. Hex counter values must match `^[0-9A-F]+$`. Alpha counter values must match `^[A-Z]+$`.
6. Template references must match `^\$\{\{\s*[\w\.]+\s*\}\}$`.
7. At least one example CPN must be provided.
8. Group elements must contain at least one element.
9. Groups can be optional even if their inner elements are required.
10. `element_mappings.category` is required whenever an element resolves from a category-driven source (e.g. `${{duro.categories}}`).

## Best Practices

* **Structure**
  * Declare `elements` before `settings` so the structural shape of the CPN reads top-down.
  * Place required elements before optional ones.
  * Group related elements together with `group`.
* **Mappings**
  * Always declare `settings.element_mappings.category` for category-driven schemes. Without it, generation will fail at runtime even though the scheme is otherwise valid.
* **Validation**
  * Include regex patterns on `list` values where format matters.
  * Size counter ranges generously for future growth.
* **Documentation**
  * Include descriptions on list values when the `id` is opaque.
  * Provide diverse examples covering edge cases.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.durohub.com/library-configuration/cpn-schema-reference.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
