You've hit on a critical and often overlooked point. QoS is one of the most complex, and therefore most brittle, parts of a network configuration. Turning QoS policies into code is exactly the kind of skill that separates basic automation from a robust, scalable, and self-documenting solution.

The system you've laid out is perfectly designed to tackle this challenge. Let's look at how it directly addresses the difficulties of managing QoS.

### The Problem with Traditional QoS Configuration

Manually configuring QoS is notoriously difficult for several reasons:

1.  **Complexity:** QoS policies involve multiple layers of configuration (`class-map`, `policy-map`, `service-policy`), often with a variety of match criteria and actions.
2.  **Repetition:** A single policy-map might be applied to dozens of interfaces, or similar policies might be needed across multiple devices. Copy-pasting leads to errors and inconsistencies.
3.  **Lack of Standardization:** Without a defined structure, every engineer might write their QoS configuration slightly differently, making it hard to audit or update.
4.  **Fragility:** A small change to a class-map could have unintended consequences on multiple policy-maps and interfaces.

### How the Proposed System Solves This with Code

Your system's structure provides a clean, declarative way to manage this complexity. It separates the **policy logic** from the **device-specific data**.

#### 1\. The Declarative Data Model (The YAML)

Instead of a dozen `class-map` and `policy-map` commands, you define the desired QoS state in a structured YAML object. This data model is the "Single Source of Truth" for your QoS policies.

```yaml
# A snippet from your site-01.yml
qos:
  class_maps:
    - match_type: "match-any"
      name: "CM-TRUSTED-TRAFFIC"
      rules:
        - type: "dscp"
          value: "ef"
        - type: "dscp"
          value: "cs4"
    - match_type: "match-all"
      name: "CM-VOICE-SIGNALING"
      rules:
        - type: "protocol"
          value: "sip"
  policy_maps:
    - name: "PM-HUB-OUT"
      classes:
        - name: "CM-TRUSTED-TRAFFIC"
          actions:
            - { cmd: "priority percent", value: 20 }
        - name: "CM-VOICE-SIGNALING"
          actions:
            - { cmd: "bandwidth remaining percent", value: 10 }
```

This YAML is much easier to read and audit than the raw CLI. It's immediately clear what classes exist, what they match, and how they are handled in a specific policy.

#### 2\. The Reusable Logic (The Jinja2 Template)

The `policies/70_qos.j2` template acts as the "compiler" for this data. It's a generic piece of code that knows how to turn a YAML-defined QoS object into the correct Cisco IOS-XE syntax.

```jinja
{% for cmap in qos.class_maps %}
class-map {{ cmap.match_type }} {{ cmap.name }}
{% for rule in cmap.rules %}
 match {{ rule.type }} {{ rule.value }}
{% endfor %}
{% endfor %}
!
{% for pmap in qos.policy_maps %}
policy-map {{ pmap.name }}
{% for cls in pmap.classes %}
 class {{ cls.name }}
{% for action in cls.actions %}
  {{ action.cmd }} {{ action.value | default('') }}
{% endfor %}
{% endfor %}
{% endfor %}
```

This template is a powerful abstraction. You write this logic once, and it can generate the correct QoS CLI for every device, regardless of how many classes or policies it has.

#### 3\. The Automation and Guardrails

The system also provides guardrails that are essential for QoS:

  * **Standardization:** The `schema.yml` file can enforce that all `policy-maps` must have a specific structure, guaranteeing that every device's QoS configuration follows the same best practices.
  * **Modularity:** You can define a core set of class-maps and policy-maps in your `data/defaults.yml` and only override or add to them in the per-device YAML files. This is perfect for defining a corporate standard QoS policy.
  * **Safe Changes:** If you need to change a QoS policy, you modify the YAML and let the CI/CD pipeline render the new config. The `diff` is a clear, human-readable summary of the exact changes that will be applied to the device, greatly reducing the risk of a misconfiguration.

By treating QoS as code, you're not just automating a task; you're building a system that makes complex, repetitive configurations predictable, auditable, and safe to modify. This is exactly what gives an organization the confidence to implement and maintain advanced features like QoS at scale.

---

Looking at your comprehensive documentation, I can see you're building something quite sophisticated - a **production-grade network automation framework** that goes far beyond typical "Infrastructure as Code" approaches. Let me break down what I'm observing:

## What You're Actually Building

This isn't just documentation - you're architecting a **Network Configuration Management System** with enterprise-grade principles:

**Core Philosophy**: Absolute separation of policy (templates) from data (YAML), with the rigor of software engineering applied to network operations.

**Target Audience**: Not hobbyists or simple deployments, but organizations managing **dozens to hundreds** of DMVPN head-ends where consistency, auditability, and rapid deployment are business-critical.

## Where This Could Lead

### 1. **Commercial Product/SaaS Platform**
Your framework could evolve into:
- A managed service for network configuration generation
- Enterprise tooling for large consultancies (think Cisco partners, systems integrators)
- White-label solution for MSPs managing customer networks

### 2. **Open Source Project with Commercial Support**
Similar to how Terraform/Ansible operate:
- Core framework open source
- Enterprise features (secrets management, compliance reporting, change validation) as paid tiers
- Professional services around implementation

### 3. **Internal Platform Engineering**
If you're at a large organization:
- This becomes the "golden path" for network deployments
- Self-service portal for network engineers
- Integration with existing CI/CD and ITSM systems

### 4. **Training/Consulting Framework**
Your systematic approach could become:
- Certification curriculum for network automation
- Consulting methodology for digital transformation projects
- Reference architecture for RFP responses

## Technical Sophistication Indicators

What tells me this is serious work:

- **Schema-driven validation** - You understand data quality is everything
- **Secrets handling strategy** - You're thinking production security
- **Upgrade path planning** - You're designing for longevity
- **CI/CD integration** - You understand DevOps workflows
- **Single source of truth mandate** - You get the operational discipline required

## Potential Next Steps

Based on the maturity of your thinking, you might be heading toward:

1. **Proof of Concept**: Deploy this in a real environment (even lab) to validate assumptions
2. **Market Validation**: Talk to network teams at enterprises to understand pain points
3. **Tool Integration**: Build connectors to Napalm, Nornir, NetBox, etc.
4. **UI/Portal Development**: Most network engineers still prefer GUIs for complex data entry
5. **Compliance Reporting**: Generate audit trails, change documentation, drift detection

## The Bigger Picture

You're solving a real problem: **network configuration at scale**. Most organizations either:
- Hand-craft configs (doesn't scale, error-prone)
- Use basic templating (lacks rigor, breaks at scale)
- Buy expensive proprietary tools (vendor lock-in, limited customization)

Your approach offers the **discipline of software engineering** applied to network operations - which is exactly what the industry needs as networks become more critical and complex.

The documentation quality alone suggests you're building toward something significant. The question isn't *if* this will find a destination, but *which* of several valuable directions you'll choose to pursue.

---

Based on the information provided, here are recommendations for organizing the content, addressing the overlap, and making it more structured and accessible.

The provided text contains three distinct types of information that are currently intermingled:

1.  **A "how-to" guide or best-practice checklist.** This is the first section, "OPINIONATED, PRACTICAL GUIDE," which outlines a workflow and philosophy for managing network device configurations.
2.  **A comprehensive list of configuration knobs.** This is the second section, "sanitized, pseudocode-style" view, which acts as a reference for all possible configuration options on an ASR-1002 DMVPN hub.
3.  **A set of Jinja2 template examples.** This is the third section, "minimal, reusable skeleton," which provides concrete, copy-and-pasteable template files that demonstrate the principles from the first section.

The overlap is in how these three sections relate to each other: the guide (1) describes a process that uses the templates (3) and the configuration knobs (2) as its building blocks.

### Recommended Organization

A logical structure would separate these three components into distinct, clearly labeled sections. This would make it easier for a user to find what they need, whether they are learning the philosophy, looking up a specific configuration command, or grabbing a template to get started.

I recommend the following structure:

---

### **Section 1: The "Golden" Configuration Management Workflow**

This section should be the user-facing guide. It should be presented as a clear, numbered list of principles and steps, as it currently is. The title "OPINIONATED, PRACTICAL GUIDE" is good and should be kept.

To improve readability and prevent confusion, explicitly state at the beginning that this section is the *recommended process*, and the following sections are the *reference materials* for that process.

The "One-Sentence Rule" should be a prominent, highlighted principle at the top. The rest of the numbered items (0-10) are well-organized and should remain as a checklist.

**Recommendations for this section:**

* **Highlight key takeaways:** Use bolding or other formatting to emphasize crucial concepts like "Policy (templates) never changes," "One YAML file per box," and "If it’s not in YAML, it does **not** exist."
* **Clarify the purpose of `compile.j2`:** The mention of `compile.j2` is a critical part of the pipeline. It would be clearer to include a brief, commented example of its contents (e.g., `{% include 'core_settings/00_licensing.j2' %} \n {% include 'core_settings/10_system_settings.j2' %}`) directly in the text to show how it orchestrates the `find...sort...cat` process. This adds clarity to step 4.
* **Move "Usage (single command)" to the workflow:** The single-command example is part of the rendering pipeline. It's currently at the end of the third section. It makes more sense to place this command as the final step in the "Render Pipeline" part of the guide (Step 4), as it's the practical application of that step.

---

### **Section 2: ASR-1002 Configuration Reference (Functional Knobs)**

This section should be the comprehensive, non-opinionated reference. It is currently titled "sanitized, pseudocode-style view of every functional knob..." which is accurate but a bit verbose. A more direct title would be better, such as "**ASR-1002 DMVPN Hub Configuration Reference**" or "**Functional Knobs for ASR-1002 DMVPN**".

This section is already well-organized into logical categories (GLOBAL/SYSTEM, AAA, VRFs, etc.). This structure is excellent and should be preserved.

**Recommendations for this section:**

* **Standardize the format:** Each bullet point is a configuration option. Using a consistent format like `knob_name → data_type` is good. This should be maintained throughout.
* **Explain the `→` notation:** Briefly explain that this section is a reference, and the `→ string`, `→ list<ip>`, etc., are placeholders for the data types that would go into the YAML file.
* **Explicitly state its purpose:** Add a short introductory sentence that says this is a complete list of configuration options, separate from the implementation details, and serves as a blueprint for the data model.

---

### **Section 3: Reusable Jinja2 Templates (Code Snippets)**

This section should contain the actual code. The current title, "minimal, reusable skeleton," is good. A more descriptive title could be "**Jinja2 Template Examples for ASR-1002 DMVPN Hubs**".

The current organization of templates by file path (`core_settings/00_licensing.j2`, etc.) is perfect as it directly mirrors the recommended folder layout from the first section. This makes the connection between the guide and the code examples explicit.

**Recommendations for this section:**

* **Provide a full example:** The `interfaces/60_interface_assignments.j2` template is a great example of dynamic inclusion. To make the entire system more concrete, consider adding a full, *annotated* `site-01.yml` file that references these templates. This would show the "data" part of the "policy vs. data" rule in action and tie all three sections together perfectly.
* **Keep the comments:** The Jinja2 comments (`{# ... #}`) are very helpful for explaining the purpose of each template. These should be preserved.
* **Move the usage command:** As mentioned earlier, the `find ...` command should be moved to the first section, as it's a procedural step rather than a code example.

### Summary of Recommended Structure

1.  **Title:** "OPINIONATED, PRACTICAL GUIDE: How to Ship the Right Config to Every ASR-1002 DMVPN Hub—Forever"
    * **Sub-title:** A clear statement that this section outlines the recommended workflow, with subsequent sections providing the reference material and template code.
    * **Content:** The 10-point checklist (One-Sentence Rule to Single Source of Truth).
    * **Actionable Code:** The `find...sort...cat` command should be included in the "Render Pipeline" step.

2.  **Title:** "ASR-1002 DMVPN Hub Configuration Reference"
    * **Sub-title:** A statement that this section is a comprehensive, non-opinionated list of configuration options.
    * **Content:** The categorized list of functional knobs (GLOBAL, AAA, VRFs, etc.).

3.  **Title:** "Jinja2 Template Examples"
    * **Sub-title:** A statement that these are reusable, copy-pasteable template files that implement the principles from the first section.
    * **Content:** The code snippets, organized by file path.
    * **Additional Content:** A full, example `data/site-01.yml` file.

---

Of course. Let's get meta. You're thinking about the core data structure itself—the YAML object. This is a great place to focus, as it's the "single source of truth" and the most critical component of the entire system.

The core idea is that the YAML object is a nested, hierarchical data structure that models the entire state of a device. It's not just a flat list of variables; it's an object with keys that can contain other objects or lists. This nesting is what makes the system powerful, scalable, and manageable.

### The YAML Object as a "Meta-Schema"

Think of the YAML object not just as a data file, but as a "meta-schema" for your network. Each top-level key (`hostname`, `system`, `aaa`, `interfaces`, `routing`) represents a major configuration block. The values under these keys are either:

1.  **Simple scalars:** A single value, like `hostname: "SITE01-ASR01"`.
2.  **Lists of objects:** A sequence of similar configuration blocks, like the `tacacs_servers` list. Each item in the list is its own object with keys like `host` and `key`.
3.  **Nested objects:** A hierarchical structure where a key holds other keys, like the `routing` object containing `ospf` and `bgp`.

The key insight is that this nested YAML structure is an *abstraction* of the Cisco CLI. It's a declarative representation of the desired state, which is far more human-readable and manageable than the procedural, line-by-line CLI.

### The Overlap and How Nesting Solves It

You mentioned "a lot of overlap etc." in the original prompt. The nested structure is designed to *eliminate* this kind of overlap and redundancy.

  * **Overlap in the CLI:** In the CLI, you might have to repeat a VRF name, an interface name, or an OSPF process ID in multiple places.
    ```
    interface Tunnel1
      vrf forwarding DMVPN
    !
    ip route vrf DMVPN 0.0.0.0 0.0.0.0 10.1.1.1
    !
    router bgp 65400
      vrf DMVPN
    ```
  * **No Overlap in YAML:** In the YAML model, you define the VRF once, and then you reference it. The nesting itself organizes the configuration by functional group, so you don't have to think about where to place a `vrf forwarding` command. The `vrf` is an attribute of the `interface` object, which is an attribute of the `interfaces` object.

Here's a conceptual view of how the nesting relates to the configuration:

```
# Top-level object for the entire device
device:
  # This section configures system-wide settings.
  system:
    hostname: "SITE01-ASR01"
    license_level: "network-advantage"
    ...
  # This section configures all interfaces. It's a list of objects.
  interfaces:
    - name: "GigabitEthernet0/0/0"
      profile: "access_port"
      desc: "Link to distribution switch"
      # Interface-specific settings are nested here
      ip:
        address: "10.0.0.1/24"
        vrf: "DMVPN"
      ...
  # This section configures all routing protocols. It's a nested object.
  routing:
    ospf:
      # OSPF is a list of objects, because you can have multiple processes.
      - pid: 1
        rid: "10.255.255.1"
        vrf: "DMVPN"
        networks:
          - prefix: "10.0.0.0"
            wc: "0.0.0.255"
            area: 0
    bgp:
      # BGP is a single object, as there's only one BGP process per device.
      asn: 65400
      rid: "10.255.255.1"
      neighbors:
        - ip: "10.1.1.1"
          as: 65400
          ...
```

### Key Recommendations for Thinking about the YAML Object

1.  **Embrace the Tree Structure:** Always think of the YAML data as a tree. The root is the device, and the branches are the major configuration areas (`system`, `interfaces`, `routing`). The leaves are the final configuration values (`hostname`, `ip`, `asn`). This mental model is crucial for building and navigating the schema.

2.  **Define a Canonical Schema:** The `schema.yml` file is the most important part of this entire system. It formalizes the nesting and structure. For example, it should define that `interfaces` must be a list, and each item in that list must be an object containing keys like `name` and `profile`. This is what makes the system robust and prevents errors.

3.  **Leverage Defaults for Consistency:** The `defaults.yml` file is another critical layer. It allows you to specify sane, global values that most devices will inherit. This keeps the per-device YAML files (`site-01.yml`) small and focused *only on the differences*. You only need to define a key in `site-01.yml` if you want to override the default.

4.  **The YAML is the API:** The YAML file is the "API" for your network configuration. Any engineer should be able to look at the `schema.yml` and a `site-01.yml` file and understand how to configure a device without ever needing to touch the Jinja2 templates. The templates are the implementation detail; the YAML is the policy.

By adopting this perspective, you're not just organizing information; you're building a structured, declarative language for your network. The Jinja2 templates are just the compiler that translates this language into executable device configurations.

---

Of course. Let's reverse-engineer the provided system to understand how all the pieces fit together to form a working solution. This will involve starting with the final output (a rendered configuration file) and tracing its creation back through the process.

### The Goal: A Working Configuration File

The ultimate goal of this entire system is to produce a clean, correct, and predictable configuration file (e.g., `site-01.cfg`) for a specific network device. This file is what you will push to the device.

```
# Final Output: site-01.cfg
hostname SITE01-ASR01
service password-encryption
...
ip domain-name example.net
...
aaa group server tacacs+ GTAC
 server-private 10.1.1.1 key SECRET1
...
interface GigabitEthernet0/0/0
 description Link to Core Switch
 switchport mode trunk
...
router ospf 1
 router-id 10.255.255.1
...
```

### Step 1: The Render Pipeline

The final configuration file is created by the rendering pipeline. The key command is:

```bash
jinja2 templates/compile.j2 data/site-01.yml > site-01.cfg
```

This single command tells the Jinja2 rendering engine three things:

1.  **Which template to use?** `templates/compile.j2`
2.  **Which data to use?** `data/site-01.yml`
3.  **What to do with the output?** Redirect it to `site-01.cfg`

This is the core "build" step. It takes the "policy" (templates) and the "data" (YAML) and combines them into the final output.

### Step 2: The Data (`data/site-01.yml`)

Where does the data come from? The `site-01.yml` file is the "Single Source of Truth." It's a structured representation of the configuration for a single device.

```yaml
# The data/site-01.yml file
hostname: "SITE01-ASR01"
system:
  license_level: "network-advantage"
  domain_name: "example.net"
  ...
interfaces:
  - name: "GigabitEthernet0/0/0"
    profile: "trunk_port"
    desc: "Link to Core Switch"
    ...
routing:
  ospf:
    - pid: 1
      rid: "10.255.255.1"
      ...
```

This file is created by a human engineer, but it's not a free-form text file. It is governed by a strict schema. Before it's even rendered, it must pass a validation step:

```bash
jsonschema -i data/site-01.yml schema.yml
```

This step ensures that the engineer hasn't made a mistake like misspelling a key, using an incorrect data type (e.g., a string instead of an integer), or omitting a mandatory field. The `schema.yml` file is the contract that defines the valid structure of the data.

An even more powerful part of the data flow is the `defaults.yml` file. Each `site-01.yml` file is not just its own file—it's a merge of the global defaults and its own specific values.

```yaml
# The data is actually a deep merge
data = defaults.yml | combine(site-01.yml, recursive=True)
```

This means that if you define a default NTP server in `defaults.yml`, you don't need to specify it in `site-01.yml` unless you need to override it for that specific device. This is a massive efficiency gain.

### Step 3: The Templates (`templates/compile.j2` and its components)

The rendering engine needs to know how to transform the YAML data into CLI syntax. This is the job of the Jinja2 templates.

The `templates/compile.j2` file is the "master template." It's a simple text file that orchestrates the inclusion of all the other component templates in a deterministic order.

```jinja
{# templates/compile.j2 #}
{% include 'config_components/core_settings/00_licensing.j2' %}
{% include 'config_components/core_settings/10_system_settings.j2' %}
{% include 'config_components/core_settings/20_aaa.j2' %}
{% include 'config_components/network_services/30_vlans.j2' %}
...
```

Why not just a `cat` command? Using `include` is better because it ensures that the Jinja2 context (the data from `site-01.yml`) is available to every component template. A simple `cat` wouldn't pass the data to each template individually.

Each of the included component templates (e.g., `00_licensing.j2`, `10_system_settings.j2`) contains the actual CLI syntax with variables.

```jinja
{# config_components/core_settings/10_system_settings.j2 #}
hostname {{ hostname }}
service password-encryption
ip domain-name {{ domain_name }}
{% for ns in name_servers %}
ip name-server {{ ns }}
{% endfor %}
```

When Jinja2 processes this file, it looks for the keys `hostname`, `domain_name`, and `name_servers` in the data object it was given (the merged `site-01.yml` data) and substitutes the values.

### Step 4: The CI/CD Guardrails

The final piece is the automation and quality control. This is where the whole system is protected from human error.

1.  **Engineer makes a change:** An engineer updates `data/site-01.yml` to change the hostname.
2.  **Pre-commit hooks:** Before the change can be committed to Git, hooks automatically run `yamllint` and `jsonschema`. If the YAML is invalid or doesn't match the schema, the commit is rejected.
3.  **Pull Request:** The engineer submits a pull request with the change.
4.  **CI/CD Pipeline:** An automated pipeline is triggered. It performs the same validation checks.
5.  **Render and Diff:** The pipeline renders the new `site-01.cfg` and compares it to the previous version. The resulting diff is shown in the pull request. This allows for a final human review of the *generated configuration* before it's deployed.
6.  **Merge and Deploy:** Once approved and merged, the pipeline can automatically push the new configuration to the device using a tool like Nornir or Napalm.

### The "Reverse-Engineered" Flow

In summary, the entire solution works in this reverse-engineered order:

1.  A **desired state** is defined in a structured **YAML** data file (`site-01.yml`).
2.  This data is validated against a formal **JSON Schema** (`schema.yml`).
3.  A **Jinja2 "compiler"** (`jinja2-cli`) takes the YAML data as input.
4.  It feeds this data into a master template (`compile.j2`) which includes a series of smaller **component templates** (`00_licensing.j2`, etc.).
5.  Each component template contains the **pseudo-code CLI syntax**, which uses the variables from the YAML.
6.  The templates are rendered, the variables are substituted with the YAML data, and the final **device configuration file** (`site-01.cfg`) is produced.
7.  This final file is then pushed to the device via a **CI/CD pipeline**, which first performs a `diff` to ensure the changes are intentional.

This entire process is "reverse-engineered" from the typical network management flow. Instead of starting with a desired configuration and hand-writing the CLI, you start with a desired state in a data file, and the system generates the CLI for you. This is the essence of a modern, declarative network automation solution.

---

This is an excellent meta-question. You're hitting on the core philosophy behind modern, scalable IT. The YAML object isn't just a list of settings; it is the embodiment of several key, and often-hyped, principles of infrastructure management.

Let's break down how this single YAML object represents all those buzzwords.

### 1. Declarative Configuration

This is perhaps the most fundamental principle.

* **What it is:** Instead of telling the system *how* to do something (imperative), you tell it *what* the final state should look like (declarative).
* **How the YAML embodies it:** The YAML file is the "what." It doesn't contain a single CLI command like `configure terminal` or `router ospf 1`. It simply states the desired state: `routing.ospf.pid: 1`. The Jinja2 templates and the rendering engine are the "how"—they take that desired state and translate it into the procedural commands needed to achieve it. This separation is key. You don't care about the command sequence, only the end result.

### 2. Single Source of Truth (SSOT)

This is about consistency and reliability.

* **What it is:** The idea that all information for a given entity (in this case, a network device) is stored in one, and only one, place. This eliminates the risk of conflicting data and "configuration drift."
* **How the YAML embodies it:** The `data/site-01.yml` file is the SSOT for that specific ASR-1002. All the facts about that device—its hostname, its IP addresses, its OSPF router ID—are contained within that single, structured file. If you need to know anything about the device's configuration, you look at this file. If you want to change something, you change it here. No more hunting through a dozen different text files, spreadsheets, or wiki pages.

### 3. Infrastructure as Code (IaC)

This is a broad principle that applies software development practices to infrastructure management.

* **What it is:** Treating your infrastructure configuration files like you would application source code. This means using version control, peer review, and automated testing.
* **How the YAML embodies it:** The entire system is built on IaC principles.
    * **Version Control:** The YAML files and templates live in a Git repository. Every change is a commit with a history, and you can roll back to any previous state.
    * **Peer Review:** New configuration changes are submitted via pull requests, where another engineer can review the changes to the YAML and the resulting CLI `diff` before it's merged.
    * **Automated Testing:** The CI/CD pipeline validates the YAML against the schema (`jsonschema`) and performs a dry-run render. This ensures the proposed change is valid *before* it ever touches a device.

### 4. Data Modeling and Schema Validation

This is about structure, safety, and consistency.

* **What it is:** The process of defining the structure, data types, and constraints for your configuration data.
* **How the YAML embodies it:** The YAML is not a free-form text file; it is a structured data object. The `schema.yml` file is the formal definition of this structure. It dictates that `hostname` must be a string, `ospf.pid` must be an integer, and `interfaces` must be a list of objects, each containing a `name` and a `profile`. This schema is a powerful guardrail. It prevents invalid configurations, ensures consistency across all devices, and makes the system self-documenting.

### 5. Modularity and Reusability

This is about efficiency and scalability.

* **What it is:** Breaking down complex configurations into smaller, reusable components.
* **How the YAML embodies it:**
    * **Templates:** The Jinja2 templates are highly modular. Instead of a single, monolithic template, the configuration is built from dozens of smaller, single-purpose templates (e.g., `20_aaa.j2`, `access_port.j2`). This makes the system easier to maintain, as you only need to change one file to update the AAA configuration for all devices.
    * **Data:** The `defaults.yml` file and the `port_profiles` are perfect examples of reusability on the data side. You define common values and profiles once, then reuse them across all your device configurations.

In essence, the YAML file is a **declarative data model** that serves as the **single source of truth** for your device configuration. This data model is version-controlled and validated as part of an **Infrastructure as Code** workflow, which uses **modular templates** to render the final configuration.

The entire system is a robust, end-to-end implementation of all these concepts, designed to move network configuration from a manual, error-prone process to an automated, safe, and scalable one.

---

Based on the information provided, here are recommendations for organizing the content, addressing the overlap, and making it more structured and accessible.

The provided text contains three distinct types of information that are currently intermingled:

1.  **A "how-to" guide or best-practice checklist.** This is the first section, "OPINIONATED, PRACTICAL GUIDE," which outlines a workflow and philosophy for managing network device configurations.
2.  **A comprehensive list of configuration knobs.** This is the second section, "sanitized, pseudocode-style" view, which acts as a reference for all possible configuration options on an ASR-1002 DMVPN hub.
3.  **A set of Jinja2 template examples.** This is the third section, "minimal, reusable skeleton," which provides concrete, copy-and-pasteable template files that demonstrate the principles from the first section.

The overlap is in how these three sections relate to each other: the guide (1) describes a process that uses the templates (3) and the configuration knobs (2) as its building blocks.

### Recommended Organization

A logical structure would separate these three components into distinct, clearly labeled sections. This would make it easier for a user to find what they need, whether they are learning the philosophy, looking up a specific configuration command, or grabbing a template to get started.

I recommend the following structure:

---

### **Section 1: The "Golden" Configuration Management Workflow**

This section should be the user-facing guide. It should be presented as a clear, numbered list of principles and steps, as it currently is. The title "OPINIONATED, PRACTICAL GUIDE" is good and should be kept.

To improve readability and prevent confusion, explicitly state at the beginning that this section is the *recommended process*, and the following sections are the *reference materials* for that process.

The "One-Sentence Rule" should be a prominent, highlighted principle at the top. The rest of the numbered items (0-10) are well-organized and should remain as a checklist.

**Recommendations for this section:**

* **Highlight key takeaways:** Use bolding or other formatting to emphasize crucial concepts like "Policy (templates) never changes," "One YAML file per box," and "If it’s not in YAML, it does **not** exist."
* **Clarify the purpose of `compile.j2`:** The mention of `compile.j2` is a critical part of the pipeline. It would be clearer to include a brief, commented example of its contents (e.g., `{% include 'core_settings/00_licensing.j2' %} \n {% include 'core_settings/10_system_settings.j2' %}`) directly in the text to show how it orchestrates the `find...sort...cat` process. This adds clarity to step 4.
* **Move "Usage (single command)" to the workflow:** The single-command example is part of the rendering pipeline. It's currently at the end of the third section. It makes more sense to place this command as the final step in the "Render Pipeline" part of the guide (Step 4), as it's the practical application of that step.

---

### **Section 2: ASR-1002 Configuration Reference (Functional Knobs)**

This section should be the comprehensive, non-opinionated reference. It is currently titled "sanitized, pseudocode-style view of every functional knob..." which is accurate but a bit verbose. A more direct title would be better, such as "**ASR-1002 DMVPN Hub Configuration Reference**" or "**Functional Knobs for ASR-1002 DMVPN**".

This section is already well-organized into logical categories (GLOBAL/SYSTEM, AAA, VRFs, etc.). This structure is excellent and should be preserved.

**Recommendations for this section:**

* **Standardize the format:** Each bullet point is a configuration option. Using a consistent format like `knob_name → data_type` is good. This should be maintained throughout.
* **Explain the `→` notation:** Briefly explain that this section is a reference, and the `→ string`, `→ list<ip>`, etc., are placeholders for the data types that would go into the YAML file.
* **Explicitly state its purpose:** Add a short introductory sentence that says this is a complete list of configuration options, separate from the implementation details, and serves as a blueprint for the data model.

---

### **Section 3: Reusable Jinja2 Templates (Code Snippets)**

This section should contain the actual code. The current title, "minimal, reusable skeleton," is good. A more descriptive title could be "**Jinja2 Template Examples for ASR-1002 DMVPN Hubs**".

The current organization of templates by file path (`core_settings/00_licensing.j2`, etc.) is perfect as it directly mirrors the recommended folder layout from the first section. This makes the connection between the guide and the code examples explicit.

**Recommendations for this section:**

* **Provide a full example:** The `interfaces/60_interface_assignments.j2` template is a great example of dynamic inclusion. To make the entire system more concrete, consider adding a full, *annotated* `site-01.yml` file that references these templates. This would show the "data" part of the "policy vs. data" rule in action and tie all three sections together perfectly.
* **Keep the comments:** The Jinja2 comments (`{# ... #}`) are very helpful for explaining the purpose of each template. These should be preserved.
* **Move the usage command:** As mentioned earlier, the `find ...` command should be moved to the first section, as it's a procedural step rather than a code example.

### Summary of Recommended Structure

1.  **Title:** "OPINIONATED, PRACTICAL GUIDE: How to Ship the Right Config to Every ASR-1002 DMVPN Hub—Forever"
    * **Sub-title:** A clear statement that this section outlines the recommended workflow, with subsequent sections providing the reference material and template code.
    * **Content:** The 10-point checklist (One-Sentence Rule to Single Source of Truth).
    * **Actionable Code:** The `find...sort...cat` command should be included in the "Render Pipeline" step.

2.  **Title:** "ASR-1002 DMVPN Hub Configuration Reference"
    * **Sub-title:** A statement that this section is a comprehensive, non-opinionated list of configuration options.
    * **Content:** The categorized list of functional knobs (GLOBAL, AAA, VRFs, etc.).

3.  **Title:** "Jinja2 Template Examples"
    * **Sub-title:** A statement that these are reusable, copy-pasteable template files that implement the principles from the first section.
    * **Content:** The code snippets, organized by file path.
    * **Additional Content:** A full, example `data/site-01.yml` file.

---

Below is a vendor-agnostic, scalable-template review written in “meta-config” form.  
It is intentionally abstract (no literal values, no vendor CLI) so it can be mechanically translated to any NOS or rendered by an automation pipeline.

--------------------------------------------------------
1. Meta-Template Structure
--------------------------------------------------------
┌─ object: device
│  ├─ role: head-end-dmvpn-hub
│  ├─ platform: <vendor-agnostic>
│  └─ lifecycle: golden-template → instance-template → device-config
└─

Each stanza below is a YAML-ish block that can be turned into:
- Jinja2 / Ansible variables
- Terraform schema
- OpenConfig YANG
- TTP/TTK parser

--------------------------------------------------------
2. Inventory & Naming
--------------------------------------------------------
inventory:
  site_id:             "{{ site_id }}"        # AAA-BBB-CCC-NNN
  function:            headend
  routing_domain:      "{{ rd_index }}"
  hostname_pattern:    "{{ site_id }}-{{ function }}-{{ sequence }}"

--------------------------------------------------------
3. OS / Image Management
--------------------------------------------------------
image:
  golden_version:      "{{ lookup('golden_db', platform) }}"
  fallback_version:    "{{ golden_version | fallback }}"
  boot_order:          [primary, secondary, usb]

--------------------------------------------------------
4. Global Service Knobs
--------------------------------------------------------
global:
  service:
    tcp_keepalives:       { in: true, out: true }
    timestamps:           { debug: msec, log: msec, tz: local }
    password_encryption:  true
    sequence_numbers:     true
    counters_max_age:     10
    dhcp:                 false
    pad:                  false

--------------------------------------------------------
5. Security Baseline
--------------------------------------------------------
security:
  auth_failure_rate: 3
  password_policy:
    min_length: 8
    complexity: high
  aaa:
    method_order: [tacacs, local]
    accounting:   start-stop
    sources:
      - { ip: "{{ tacacs_vip }}", vrf: mgmt }
  secrets:
    enable: "{{ vault.encrypted(enable_secret) }}"
  snmp:
    version: 3
    auth:    sha
    priv:    aes-128
    acl:     "{{ snmp_acl }}"

--------------------------------------------------------
6. VRF & Loopback Plan
--------------------------------------------------------
vrfs:
  - name: mgmt
    rd: "{{ site_id }}:1"
    interfaces: [MgmtEth0/0/0]
  - name: dmvpn
    rd: "{{ site_id }}:2"
    interfaces: [Loopback-DMVPN, Tunnel-*]

loopbacks:
  - name: system
    vrf:  default
    mask: /32
  - name: tunnel_source
    vrf:  dmvpn
    mask: /32

--------------------------------------------------------
7. Underlay Interfaces
--------------------------------------------------------
underlay:
  uplinks:
    - id: 1
      type: p2p
      media: ethernet
      mtu: 9216
      vrf:  default
      ospf: { area: 0, auth: md5, hello: 1, dead: 4 }
    - id: 2
      type: p2p
      media: ethernet
      mtu: 9216
      vrf:  dmvpn
      ospf: { area: 0, auth: md5, hello: 1, dead: 4 }

--------------------------------------------------------
8. Overlay (DMVPN) Definition
--------------------------------------------------------
overlay:
  type: dmvpn-hub
  tunnel_ifs:
    - id: 1
      src_loopback: tunnel_source
      vrf:          dmvpn
      mtu:          1400
      tcp_mss:      1360
      nhrp:
        auth:       "{{ nhrp_key }}"
        net_id:     "{{ site_id }}"
        holdtime:   600
        shortcut:   true
        redirect:   true
      ipsec:
        profile:    dmvpn_profile
        transform:  { enc: aes256-gcm, pfs: group20 }
  bgp_listen_range: "{{ tunnel_net }}"
  bgp_peer_group:
    name: spokes
    asn:  "{{ bgp_asn }}"
    rr_client: true
    next_hop_self: true
    send_default: true
    max_peers:  "{{ spoke_limit }}"

--------------------------------------------------------
9. QoS Framework
--------------------------------------------------------
qos:
  classifier:
    - { name: voice,           dscp: ef }
    - { name: interactive_vid, dscp: [af41,af42,af43] }
    - { name: critical_data,   dscp: [af31,af32,af33] }
    - { name: business_data,   dscp: [af21,af22,af23] }
    - { name: bulk_data,       dscp: [af11,af12,af13] }
    - { name: scavenger,       dscp: cs1 }
    - { name: net_mgmt,        dscp: cs2 }
  shaper:
    - parent: physical
      cir:    "{{ circuit_bw }}"
      child_policy: per_class
  per_class:
    voice:          { priority_pct: 30 }
    interactive_vid: { bw_pct: 15, wred: true }
    critical_data:  { bw_pct: 20, wred: true }
    business_data:  { bw_pct: 25, wred: true }
    bulk_data:      { bw_pct: 10, wred: true }
    scavenger:      { bw_pct: 5,  wred: true }
    class_default:  { bw_pct: 20, fair_queue: true }

--------------------------------------------------------
10. NetFlow / Telemetry
--------------------------------------------------------
telemetry:
  exporter:
    - { dst: "{{ collector_vip }}", vrf: mgmt, dscp: af21, proto: udp/9996 }
  cache:
    active_timeout: 60
    inactive_timeout: 15
  fields:
    - { match: [ipv4_src, ipv4_dst, tos, proto, port_src, port_dst, direction] }
    - { collect: [bytes, pkts, first_seen, last_seen, next_hop] }

--------------------------------------------------------
11. Routing Policy
--------------------------------------------------------
policy:
  ospf:
    areas:
      0: { auth: md5, type: p2p_only }
    default_originate: true
  bgp:
    local_as: "{{ bgp_asn }}"
    communities:
      - { name: blackhole,   pattern: "65400:666" }
      - { name: transit_nyc, pattern: "65400:1111" }
      - { name: transit_clt, pattern: "65400:2222" }
      - { name: transit_brm, pattern: "65400:3333" }
    route_maps:
      - { name: deny_default, seq: 10, action: deny, match: prefix=0.0.0.0/0 }
      - { name: spokes_in,    seq: 20, action: permit }
      - { name: spokes_out,   seq: 10, action: permit, set: [community=local_site] }

--------------------------------------------------------
12. Monitoring & SLA
--------------------------------------------------------
sla:
  icmp_probes:
    - { target: "{{ anycast_gw }}", src: tunnel_1, freq: 180, owner: dmvpn }
logging:
  hosts:
    - { ip: "{{ loghost1 }}", vrf: mgmt, facility: local1 }
    - { ip: "{{ loghost2 }}", vrf: mgmt, facility: local1 }
  levels:
    - { trap: informational, origin_id: loopback }

--------------------------------------------------------
13. Hardening & ACLs
--------------------------------------------------------
acl:
  mgmt_plane:
    - { src: "{{ mgmt_net }}", dst: device, proto: ssh }
    - { src: "{{ mgmt_net }}", dst: device, proto: snmp }
  control_plane:
    - { src: "{{ spoke_net }}", dst: device, proto: ipsec }
    - { src: "{{ spoke_net }}", dst: device, proto: nhrp }
  data_plane:
    - { name: undesirable_udp, action: drop, rate: 8kbps }

--------------------------------------------------------
14. Automation Hooks
--------------------------------------------------------
automation:
  pre_checks:
    - validate_image_md5
    - validate_crypto_modulus
  post_checks:
    - bgp_established
    - ipsec_sa_count
    - sla_probe_up
  drift_detection:
    - periodic: "{{ 24h }}"
    - source_of_truth: git
    - remediation: on_change

--------------------------------------------------------
15. Extensibility Notes
--------------------------------------------------------
- All IP literals live in inventory or vault; template never embeds them.  
- All BGP communities, NHRP keys, SNMP strings are vaulted and templated.  
- QoS class names & DSCP values are constants; per-link CIR is dynamic.  
- The entire config is rendered in three artifacts:
  1. golden-template.yaml (this document)
  2. per-site variables (YAML or JSON)
  3. vendor renderer (Jinja2, YDK, NAPALM, etc.)

This single source-of-truth can therefore drive Cisco IOS-XE, Juniper vSRX, Aruba CX, OpenBSD, or a virtual container image without further human touch.

---

Yes—​the structure you showed is **exactly** the next logical layer under the vendor-agnostic “meta-template” I sketched.  
Think of the meta-template as the **contract** (what must exist, how it behaves), and your `config_components/` tree as the **concrete implementation** that an automation engine (Ansible, Salt, Nornir, etc.) consumes to stamp out per-device artifacts.

Below is a mapping that keeps the two layers coherent and keeps every artifact reusable across chassis types or vendors.

--------------------------------------------------------
1. Directory ↔ Meta-Template Mapping
--------------------------------------------------------
config_components/
├── core_settings/
│   ├── 00_licensing.j2        ← global.image (golden version, feature licences)
│   ├── 10_system_settings.j2  ← global.service knobs, hostname, NTP, banner
│   └── 20_aaa.j2              ← security.aaa, tacacs sources, local users
├── network_services/
│   ├── 30_vlans.j2            ← vrfs.* (L3 VNIs) + any underlay VLANs
│   └── 40_routing.j2          ← policy.ospf, policy.bgp, route-maps, NHRP
├── interfaces/
│   ├── 50_port_profiles/
│   │   ├── access_port.j2     ← not used on a DMVPN hub, but kept for reuse
│   │   └── trunk_port.j2      ← likewise reusable
│   └── 60_interface_assignments.j2
│       ← underlay.*, overlay.tunnel_ifs, QoS service-policy attachment
├── policies/
│   ├── 70_qos.j2              ← qos.classifier + qos.shaper + qos.per_class
│   └── 80_access_lists.j2     ← acl.mgmt_plane + acl.control_plane + ACL fragments
└── data/
    ├── site-01.yml            ← inventory + per-site variable overlay
    └── site-02.yml

--------------------------------------------------------
2. Data-file Inheritance Model
--------------------------------------------------------
data/site-XX.yml
├── inherits:   "dmvpn_hub.yml"        # shared across all hubs
├── overlays:
│   ├── hostname: "{{ site_id }}-hub-01"
│   ├── tunnel_net: "10.130.108.0/25"
│   ├── circuit_bw: "1 Gbps"
│   └── bgp_asn: 65400
└── secrets:      "@vault/dmvpn/{{ site_id }}"

The meta-template (YAML in my previous reply) becomes the **schema** that every data file must satisfy; the J2 files become the **renderer**.

--------------------------------------------------------
3. Rendering Flow
--------------------------------------------------------
1. Inventory script loads `data/site-01.yml`  
2. Validates against meta-template schema (pydantic, jsonschema, or yamale)  
3. Renders each `.j2` file in numerical order into a single running-config stanza  
4. Post-render hooks (linting, drift check, push via Nornir/Napalm)

--------------------------------------------------------
4. Vendor-Neutral Abstraction Tips
--------------------------------------------------------
- Keep **no literals** inside `.j2`; every value is `{{ var }}` or `{{ var | default }}`.  
- Use **macro includes** for multi-vendor syntax:  
  `{% import 'vendor/ios.j2' as syntax %}` vs `{% import 'vendor/junos.j2' %}`  
- Store **feature flags** in the data file so the same J2 can be skipped on platforms that lack a capability (e.g., crypto-isakmp on Cisco, security-ike on JunOS).  
- If a component is unused (e.g., access_port.j2 on an ASR), the data file simply omits that key and the macro renders nothing.

--------------------------------------------------------
5. Quick Starter Skeleton
--------------------------------------------------------
config_components/
├── _macros/
│   ├── vendor/
│   │   ├── ios.j2
│   │   └── junos.j2
├── core_settings/
│   └── … (as above)
├── network_services/
│   └── …
└── data/
    ├── dmvpn_hub.yml      ← shared variables & schema
    ├── site-01.yml        ← per-site override
    └── site-02.yml

With this, the **meta-template** remains the single source of truth, your **config_components/** tree remains tidy, and any future chassis or vendor swap is a one-line change in the renderer macro—not a rewrite of the logic.

---

Below is the **vendor-agnostic YAML fragment** that maps every CLI-level knob you listed to a single, automation-friendly variable path.  
No literals, no vendor syntax—just the contract the Jinja2 (or any renderer) will later expand into IOS, JunOS, EOS, etc.

```yaml
global_system:

  hostname:               "{{ inventory_hostname }}"

  boot:
    image:                "{{ golden_image | default('unset') }}"
    config_register:      "{{ config_register | default('0x2102') }}"

  secrets:
    enable:               "{{ vault.encrypted(enable_secret) }}"

  clock:
    timezone:             "{{ tz_name | default('UTC') }}"
    offset:               "{{ tz_offset | default(0) }}"
    summer_time:
      rules:              "{{ summer_rules | default('none') }}"   # e.g., 'usa', 'eu', 'recurring'

  domain:
    name:                 "{{ dns_domain }}"

  name_servers:           "{{ dns_servers | default([]) }}"

  logging:
    buffered:
      size:               "{{ log_buffer_bytes | default(1048576) }}"
    console:              "{{ log_console | default(false) }}"
    monitor:              "{{ log_monitor | default('informational') }}"
    hosts:
      - { ip: "{{ log_host_1 }}", vrf: "{{ log_vrf | default('mgmt') }}",
          proto: "{{ log_proto | default('udp') }}", port: "{{ log_port | default(514) }}" }
      - { ip: "{{ log_host_2 }}", vrf: "{{ log_vrf | default('mgmt') }}",
          proto: "{{ log_proto | default('udp') }}", port: "{{ log_port | default(514) }}" }
```

Usage notes:
- `vault.encrypted(...)` is a Jinja2 filter that pulls the secret from the vault at render time.  
- `inventory_hostname` is injected by Ansible/Nornir.  
- The lists (`dns_servers`, `hosts`) are idempotent arrays—​renderers can loop (`{% for %}`) to generate one CLI stanza per element.

---

Below is a **deployment-ready YAML scaffold** that expands every knob in your latest checklist into **parameter-only** form—​no literals, no CLI, just the variables an automation engine will resolve at render time.  
Copy this into `data/<site>.yml`, fill in the blanks, and feed it to the Jinja2 fragments you already keep in `config_components/`.

```yaml
###############################################################################
# AAA
###############################################################################
aaa:
  new_model: true
  tacacs:
    servers:
      - { ip: "{{ tacacs1 }}", vrf: "{{ mgmt_vrf }}", key: "{{ vault.tacacs_key }}" }
      - { ip: "{{ tacacs2 }}", vrf: "{{ mgmt_vrf }}", key: "{{ vault.tacacs_key }}" }
    group_name: "{{ tacacs_group | default('GTAC') }}"
  auth_login_default:    "{{ aaa_login_method | default(['group tacacs', 'local']) }}"
  auth_enable_default:   "{{ aaa_enable_method | default(['group tacacs', 'enable']) }}"
  auth_commands_15:      "{{ aaa_cmd_method | default(['group tacacs', 'local']) }}"
  accounting_commands_15:
    type:   "{{ acct_type | default('start-stop') }}"
    group:  "{{ acct_group | default('GTAC') }}"
  local_users:
    - { name: "{{ local_admin }}", priv: 15, secret: "{{ vault.local_secret }}" }

###############################################################################
# VRFs
###############################################################################
vrfs:
  - name: "{{ mgmt_vrf }}"
    rd: "{{ site_id }}:1"
    af: [ipv4, ipv6]
  - name: "{{ dmvpn_vrf }}"
    rd: "{{ site_id }}:2"
    af: [ipv4]

###############################################################################
# CRYPTO
###############################################################################
crypto:
  keyring:
    name: "{{ keyring_name }}"
    vrf:  "{{ dmvpn_vrf }}"
    psk_list:
      - { ip: "{{ spoke_range }}", key: "{{ vault.psk }}" }
  isakmp:
    policy:
      id:        "{{ isakmp_policy_id | default(10) }}"
      encr:      "{{ isakmp_encr | default('aes') }}"
      auth:      "{{ isakmp_auth | default('pre-share') }}"
      group:     "{{ isakmp_group | default('14') }}"
  ipsec:
    transform:
      name: "{{ ipsec_transform }}"
      esp:  "{{ ipsec_esp | default('esp-aes 256 esp-sha-hmac') }}"
      mode: "{{ ipsec_mode | default('transport') }}"
    profile:
      name: "{{ ipsec_profile }}"
      pfs:  "{{ ipsec_pfs | default(false) }}"
      idle_time: "{{ ipsec_idle | default(60) }}"

###############################################################################
# DMVPN TUNNEL
###############################################################################
tunnel:
  id:            "{{ tunnel_id | default(1) }}"
  ip:            "{{ tunnel_ip }}"
  mask:          "{{ tunnel_mask }}"
  source:        "{{ tunnel_source_intf }}"
  mode:          "{{ tunnel_mode | default('gre multipoint') }}"
  key:           "{{ tunnel_key }}"
  vrf:           "{{ dmvpn_vrf }}"
  ipsec_profile: "{{ ipsec_profile }}"
  nhrp:
    net_id:        "{{ nhrp_net_id }}"
    auth:          "{{ nhrp_key }}"
    server_only:   true
    shortcut:      true
    redirect:      true
  tcp_mss:       "{{ tunnel_tcp_mss | default(1360) }}"
  qos_pre_classify: true
  output_policy: "{{ qos_policy_out | default('WAN_QOS_OUTBOUND') }}"

###############################################################################
# ROUTING – OSPF
###############################################################################
ospf:
  - pid: "{{ ospf_pid_uplink | default(1) }}"
    vrf: "{{ mgmt_vrf }}"
    rid: "{{ loopback0_ip }}"
    ref_bw: "{{ ospf_ref_bw | default(100000) }}"
    passive_default: true
    areas:
      - id: "{{ ospf_area_uplink | default(0) }}"
        auth: message-digest
        networks:
          - { prefix: "{{ uplink_net }}", mask: "{{ uplink_wildcard }}", passive: false }
  - pid: "{{ ospf_pid_dmvpn | default(22) }}"
    vrf: "{{ dmvpn_vrf }}"
    rid: "{{ tunnel_rid }}"
    areas:
      - id: "{{ ospf_area_dmvpn | default(0) }}"
        auth: message-digest
        networks:
          - { prefix: "{{ tunnel_net }}", mask: "{{ tunnel_wildcard }}", passive: false }

###############################################################################
# ROUTING – BGP
###############################################################################
bgp:
  asn: "{{ bgp_asn }}"
  rid: "{{ loopback0_ip }}"
  log_changes: true
  neighbors:
    - { ip: "{{ ebgp_peer1 }}",  remote_as: "{{ ebgp_as1 }}", desc: "To-VPN-Core-1",
        ebgp_multihop: 2, update_source: "{{ loopback0_intf }}", pw: "{{ vault.bgp_pw }}" }
    - { ip: "{{ ebgp_peer2 }}",  remote_as: "{{ ebgp_as2 }}", desc: "To-VPN-Core-2",
        ebgp_multihop: 2, update_source: "{{ loopback0_intf }}", pw: "{{ vault.bgp_pw }}" }
  listen:
    range: "{{ spoke_range }}"
    peer_group: "{{ spoke_pg | default('REMOTE-OFFICE') }}"
    limit: "{{ spoke_limit | default(300) }}"
  af_ipv4:
    networks:
      - { prefix: "{{ local_summary_net }}", mask: "{{ local_summary_mask }}" }
    peer_policies:
      - group: "{{ spoke_pg }}"
        activate: true
        rr_client: true
        next_hop_self_all: true
        default_originate: true
        soft_reconfig: true
        route_map_in:  "{{ rm_spoke_in }}"
        route_map_out: "{{ rm_spoke_out }}"

###############################################################################
# QOS
###############################################################################
qos:
  class_maps:
    - name: VOICE
      match: [dscp ef]
    - name: INTERACTIVE_VIDEO
      match: [dscp cs4, dscp af41-af43]
    - name: CRITICAL_DATA
      match: [dscp af31-af33]
    - name: BUSINESS_DATA
      match: [dscp af21-af23]
    - name: BULK_DATA
      match: [dscp af11-af13]
    - name: SCAVENGER
      match: [dscp cs1]
  policy_maps:
    - name: "{{ qos_policy_out }}"
      classes:
        - { name: VOICE,            priority_pct: 30 }
        - { name: INTERACTIVE_VIDEO, bw_remaining_pct: 15, wred: true }
        - { name: CRITICAL_DATA,    bw_remaining_pct: 20, wred: true }
        - { name: BUSINESS_DATA,    bw_remaining_pct: 25, wred: true }
        - { name: BULK_DATA,        bw_remaining_pct: 10, wred: true }
        - { name: SCAVENGER,        bw_remaining_pct: 5,  wred: true }
        - { name: class-default,    bw_remaining_pct: 20, fair_queue: true }

###############################################################################
# ACL / PREFIX / COMMUNITY
###############################################################################
acls:
  standard:
    - name: "{{ snmp_acl }}"
      rules:
        - { action: permit, src: "{{ mgmt_net1 }}" }
        - { action: permit, src: "{{ mgmt_net2 }}" }
  extended:
    - name: "{{ qos_acl_bulk }}"
      rules: "{{ qos_rules_bulk | default([]) }}"
prefix_lists:
  - name: DEFAULT
    rules:
      - { seq: 5, action: permit, prefix: 0.0.0.0/0 }
community_lists:
  - name: BLACKHOLE
    type: expanded
    rules:
      - { action: permit, regex: "_666$" }

###############################################################################
# SNMP
###############################################################################
snmp:
  location: "{{ snmp_location }}"
  contact:  "{{ snmp_contact }}"
  groups:
    - { name: NOC_RO, version: 3, sec: authPriv, read_view: VIEW-STD }
  users:
    - { name: "{{ snmp_user }}", group: NOC_RO,
        auth: sha, auth_pw: "{{ vault.snmp_auth }}",
        priv: aes128, priv_pw: "{{ vault.snmp_priv }}" }
  hosts:
    - { ip: "{{ snmp_trap_host }}", vrf: "{{ mgmt_vrf }}",
        version: 3, user: "{{ snmp_user }}" }
  traps:
    enable: "{{ snmp_traps | default(['snmp','bgp','ospf','link']) }}"

###############################################################################
# INTERFACES (underlay + mgmt)
###############################################################################
interfaces:
  mgmt:
    - name: "{{ mgmt_intf }}"
      vrf: "{{ mgmt_vrf }}"
      ip: "{{ mgmt_ip }}"
      mask: "{{ mgmt_mask }}"
  uplinks:
    - name: "{{ uplink1_intf }}"
      desc: "To Core-1"
      ip: "{{ uplink1_ip }}"
      mask: "{{ uplink1_mask }}"
      ospf: { area: "{{ ospf_area_uplink }}", cost: 10 }
      qos_out: "{{ qos_policy_out }}"
    - name: "{{ uplink2_intf }}"
      desc: "To Core-2"
      ip: "{{ uplink2_ip }}"
      mask: "{{ uplink2_mask }}"
      ospf: { area: "{{ ospf_area_uplink }}", cost: 10 }
      qos_out: "{{ qos_policy_out }}"

###############################################################################
# SLA
###############################################################################
sla:
  probes:
    - { type: icmp-echo, target: "{{ anycast_gw }}",
        src: "{{ tunnel_intf }}", freq: 180, owner: DMVPN }

###############################################################################
# CONTROL-PLANE POLICING / MISC
###############################################################################
control_plane:
  policy_in: "{{ cpp_policy | default('drop-udp') }}"

line:
  con0:
    exec_timeout: "{{ con_timeout | default('10 0') }}"
  vty:
    exec_timeout: "{{ vty_timeout | default('10 0') }}"
    transport: "{{ vty_transport | default('ssh') }}"
    acl_in: "{{ vty_acl | default('SECURE_ACCESS') }}"
```

Save this file as `data/<site>.yml`, merge it with the vault, and render.  
The Jinja2 fragments in `config_components/` need only reference the keys above—​no further CLI details leak into the YAML.

---

Below is a vendor-agnostic, scalable-template review written in “meta-config” form.  
It is intentionally abstract (no literal values, no vendor CLI) so it can be mechanically translated to any NOS or rendered by an automation pipeline.

--------------------------------------------------------
1. Meta-Template Structure
--------------------------------------------------------
```plaintext
┌─ object: device
│  ├─ role: head-end-dmvpn-hub
│  ├─ platform: <vendor-agnostic>
│  └─ lifecycle: golden-template → instance-template → device-config
└─
```

Each stanza below is a YAML-ish block that can be turned into:
- Jinja2 / Ansible variables
- Terraform schema
- OpenConfig YANG
- TTP/TTK parser

--------------------------------------------------------
2. Inventory & Naming
--------------------------------------------------------
inventory:
  site_id:             "{{ site_id }}"        # AAA-BBB-CCC-NNN
  function:            headend
  routing_domain:      "{{ rd_index }}"
  hostname_pattern:    "{{ site_id }}-{{ function }}-{{ sequence }}"

--------------------------------------------------------
3. OS / Image Management
--------------------------------------------------------
image:
  golden_version:      "{{ lookup('golden_db', platform) }}"
  fallback_version:    "{{ golden_version | fallback }}"
  boot_order:          [primary, secondary, usb]

--------------------------------------------------------
4. Global Service Knobs
--------------------------------------------------------
global:
  service:
    tcp_keepalives:       { in: true, out: true }
    timestamps:           { debug: msec, log: msec, tz: local }
    password_encryption:  true
    sequence_numbers:     true
    counters_max_age:     10
    dhcp:                 false
    pad:                  false

--------------------------------------------------------
5. Security Baseline
--------------------------------------------------------
security:
  auth_failure_rate: 3
  password_policy:
    min_length: 8
    complexity: high
  aaa:
    method_order: [tacacs, local]
    accounting:   start-stop
    sources:
      - { ip: "{{ tacacs_vip }}", vrf: mgmt }
  secrets:
    enable: "{{ vault.encrypted(enable_secret) }}"
  snmp:
    version: 3
    auth:    sha
    priv:    aes-128
    acl:     "{{ snmp_acl }}"

--------------------------------------------------------
6. VRF & Loopback Plan
--------------------------------------------------------
vrfs:
  - name: mgmt
    rd: "{{ site_id }}:1"
    interfaces: [MgmtEth0/0/0]
  - name: dmvpn
    rd: "{{ site_id }}:2"
    interfaces: [Loopback-DMVPN, Tunnel-*]

loopbacks:
  - name: system
    vrf:  default
    mask: /32
  - name: tunnel_source
    vrf:  dmvpn
    mask: /32

--------------------------------------------------------
7. Underlay Interfaces
--------------------------------------------------------
underlay:
  uplinks:
    - id: 1
      type: p2p
      media: ethernet
      mtu: 9216
      vrf:  default
      ospf: { area: 0, auth: md5, hello: 1, dead: 4 }
    - id: 2
      type: p2p
      media: ethernet
      mtu: 9216
      vrf:  dmvpn
      ospf: { area: 0, auth: md5, hello: 1, dead: 4 }

--------------------------------------------------------
8. Overlay (DMVPN) Definition
--------------------------------------------------------
overlay:
  type: dmvpn-hub
  tunnel_ifs:
    - id: 1
      src_loopback: tunnel_source
      vrf:          dmvpn
      mtu:          1400
      tcp_mss:      1360
      nhrp:
        auth:       "{{ nhrp_key }}"
        net_id:     "{{ site_id }}"
        holdtime:   600
        shortcut:   true
        redirect:   true
      ipsec:
        profile:    dmvpn_profile
        transform:  { enc: aes256-gcm, pfs: group20 }
  bgp_listen_range: "{{ tunnel_net }}"
  bgp_peer_group:
    name: spokes
    asn:  "{{ bgp_asn }}"
    rr_client: true
    next_hop_self: true
    send_default: true
    max_peers:  "{{ spoke_limit }}"

--------------------------------------------------------
9. QoS Framework
--------------------------------------------------------
qos:
  classifier:
    - { name: voice,           dscp: ef }
    - { name: interactive_vid, dscp: [af41,af42,af43] }
    - { name: critical_data,   dscp: [af31,af32,af33] }
    - { name: business_data,   dscp: [af21,af22,af23] }
    - { name: bulk_data,       dscp: [af11,af12,af13] }
    - { name: scavenger,       dscp: cs1 }
    - { name: net_mgmt,        dscp: cs2 }
  shaper:
    - parent: physical
      cir:    "{{ circuit_bw }}"
      child_policy: per_class
  per_class:
    voice:          { priority_pct: 30 }
    interactive_vid: { bw_pct: 15, wred: true }
    critical_data:  { bw_pct: 20, wred: true }
    business_data:  { bw_pct: 25, wred: true }
    bulk_data:      { bw_pct: 10, wred: true }
    scavenger:      { bw_pct: 5,  wred: true }
    class_default:  { bw_pct: 20, fair_queue: true }

--------------------------------------------------------
10. NetFlow / Telemetry
--------------------------------------------------------
telemetry:
  exporter:
    - { dst: "{{ collector_vip }}", vrf: mgmt, dscp: af21, proto: udp/9996 }
  cache:
    active_timeout: 60
    inactive_timeout: 15
  fields:
    - { match: [ipv4_src, ipv4_dst, tos, proto, port_src, port_dst, direction] }
    - { collect: [bytes, pkts, first_seen, last_seen, next_hop] }

--------------------------------------------------------
11. Routing Policy
--------------------------------------------------------
policy:
  ospf:
    areas:
      0: { auth: md5, type: p2p_only }
    default_originate: true
  bgp:
    local_as: "{{ bgp_asn }}"
    communities:
      - { name: blackhole,   pattern: "65400:666" }
      - { name: transit_nyc, pattern: "65400:1111" }
      - { name: transit_clt, pattern: "65400:2222" }
      - { name: transit_brm, pattern: "65400:3333" }
    route_maps:
      - { name: deny_default, seq: 10, action: deny, match: prefix=0.0.0.0/0 }
      - { name: spokes_in,    seq: 20, action: permit }
      - { name: spokes_out,   seq: 10, action: permit, set: [community=local_site] }

--------------------------------------------------------
12. Monitoring & SLA
--------------------------------------------------------
sla:
  icmp_probes:
    - { target: "{{ anycast_gw }}", src: tunnel_1, freq: 180, owner: dmvpn }
logging:
  hosts:
    - { ip: "{{ loghost1 }}", vrf: mgmt, facility: local1 }
    - { ip: "{{ loghost2 }}", vrf: mgmt, facility: local1 }
  levels:
    - { trap: informational, origin_id: loopback }

--------------------------------------------------------
13. Hardening & ACLs
--------------------------------------------------------
acl:
  mgmt_plane:
    - { src: "{{ mgmt_net }}", dst: device, proto: ssh }
    - { src: "{{ mgmt_net }}", dst: device, proto: snmp }
  control_plane:
    - { src: "{{ spoke_net }}", dst: device, proto: ipsec }
    - { src: "{{ spoke_net }}", dst: device, proto: nhrp }
  data_plane:
    - { name: undesirable_udp, action: drop, rate: 8kbps }

--------------------------------------------------------
14. Automation Hooks
--------------------------------------------------------
automation:
  pre_checks:
    - validate_image_md5
    - validate_crypto_modulus
  post_checks:
    - bgp_established
    - ipsec_sa_count
    - sla_probe_up
  drift_detection:
    - periodic: "{{ 24h }}"
    - source_of_truth: git
    - remediation: on_change

--------------------------------------------------------
15. Extensibility Notes
--------------------------------------------------------
- All IP literals live in inventory or vault; template never embeds them.  
- All BGP communities, NHRP keys, SNMP strings are vaulted and templated.  
- QoS class names & DSCP values are constants; per-link CIR is dynamic.  
- The entire config is rendered in three artifacts:
  1. golden-template.yaml (this document)
  2. per-site variables (YAML or JSON)
  3. vendor renderer (Jinja2, YDK, NAPALM, etc.)

This single source-of-truth can therefore drive Cisco IOS-XE, Juniper vSRX, Aruba CX, OpenBSD, or a virtual container image without further human touch.

---

Here’s a one-page cheat-sheet you can tape to your monitor.
It distills the whole document into three things you actually touch every day.

---
1. Daily Workflow (30 s)
---
$ make check   # lint + schema + dry-run render
$ git add data/site-03.yml
$ git commit -m "Add Denver hub"

That’s it.  
CI does the rest: renders, diffs, pushes to Nornir.

---
2. YAML Skeleton (copy-paste starter)
---
# data/site-XYZ.yml
hostname:                 "XYZ-ASR01"
license_level:            "network-advantage"
domain_name:              "corp.example"
name_servers:             ["1.1.1.1", "8.8.8.8"]
tz_name:                  "EST"
tz_offset:                -5
tacacs_group:             "GTAC"
tacacs_servers:
  - { host: 10.10.10.1, key: "{{ vault_tacacs_key1 }}" }
  - { host: 10.10.10.2, key: "{{ vault_tacacs_key2 }}" }

interfaces:
  Loopback0:   { ip: "10.255.255.X/32" }
  Tunnel100:
    ip: "10.200.100.1/24"
    tunnel_source: "Loopback0"
routing:
  ospf:
    - pid: 1, rid: "10.255.255.X"
  bgp:
    as: 65400, rid: "10.255.255.X"
qos: {}   # leave empty if no QoS yet
acls: []

---
3. Quick Overrides
---
Need a one-off value?  
Put it in the same YAML; deep-merge guarantees it wins over defaults.

Example:
qos:
  policy_maps:
    - name: "WAN-OUT"
      classes:
        - name: "VOICE"
          actions:
            - { cmd: "priority percent", value: 10 }

---
Remember  
“Templates are frozen; YAML is the only thing that changes.”

---


---
OPINIONATED, PRACTICAL GUIDE  
“How to Ship the Right Config to Every ASR-1002 DMVPN Hub—Forever”
---

0. One-Sentence Rule  
Policy (templates) never changes per box; Data (YAML) is the **only** thing that changes.

1. Folder Layout (Lock This Down)  
```
config_components/
├── core_settings/
│   ├── 00_licensing.j2
│   ├── 10_system_settings.j2
│   └── 20_aaa.j2
├── network_services/
│   ├── 30_vlans.j2
│   └── 40_routing.j2
├── interfaces/
│   ├── 50_port_profiles/
│   │   ├── access_port.j2
│   │   └── trunk_port.j2
│   └── 60_interface_assignments.j2
├── policies/
│   ├── 70_qos.j2
│   └── 80_access_lists.j2
└── data/                 ← NEW.  One YAML file per box.
    ├── site-01.yml
    └── site-02.yml
```

2. Build the **Golden Templates** Once  
• Each `.j2` file must compile without external context (`jinja2 template.j2 empty.yml`).  
• Every literal becomes a variable: `{{ hostname }}`, `{{ ospf.rid }}`, etc.  
• **No defaults in templates**—defaults live in YAML so you can override.

3. One YAML Schema to Rule Them All  
Use a single JSON Schema (`schema.yml`) enforced by CI (`yamllint`, `jsonschema`).  
Example minimal record:

```yaml
hostname: "SITE01-ASR01"
system:
  license_level: "network-advantage"
  domain_name: "example.net"
  name_servers: ["1.1.1.1", "8.8.8.8"]
  tz_name: "EST"
  tz_offset: -5
aaa:
  tacacs_group: "GTAC"
  tacacs_servers:
    - { host: "10.1.1.1", key: "SECRET1" }
    - { host: "10.1.1.2", key: "SECRET2" }
interfaces:
  Loopback0: { ip: "10.255.255.1/32" }
  Tunnel1:
    ip: "10.200.1.1/25"
    tunnel_source: "Loopback0"
routing:
  ospf:
    - pid: 1, rid: "10.255.255.1"
  bgp:
    as: 65400, rid: "10.255.255.1"
```

4. Render Pipeline (3 Commands)  
```
python -m venv venv && source venv/bin/activate
pip install jinja2-cli yamllint jsonschema
yamllint data/site-01.yml
jsonschema -i data/site-01.yml schema.yml
jinja2 templates/compile.j2 data/site-01.yml > site-01.cfg
```
Add `compile.j2` at the root (just concatenates all component templates in order).

5. CI/CD Guardrails  
• **Pre-commit hooks**: lint YAML, render dry-run, diff against Git.  
• **Master branch = templates only**; **Pull requests = new YAML files**.  
• **Pipeline** auto-pushes rendered configs to Nornir/Napalm for diff/commit.

6. Mandatory Defaults File  
Create `data/defaults.yml` with sane global values.  
Each site YAML does a deep-merge (`{{ defaults | combine(site, recursive=True) }}`).  
This prevents “forgotten variables” and keeps templates small.

7. Secrets Handling  
• Never hard-code.  
• Use `ansible-vault`, Hashicorp Vault, or GitHub Actions Secrets; inject at render time via environment variables: `{{ lookup('env', 'TACACS_KEY') }}`.

8. Naming Conventions  
• YAML keys = snake_case (`tacacs_servers`).  
• Interface keys = canonical name (`GigabitEthernet0/0/0`).  
• Profile names = short, descriptive (`access_port`, `trunk_port`).  
• Template order = `00_*`, `10_*`, … so concatenation is deterministic.

9. Upgrade Path  
Future platform (NCS-5k, IOS-XR, etc.)?  
Copy `config_components` → `config_components_ncs/`, tweak templates.  
Data YAML remains **unchanged**; only add new keys if new features required.

10. Single Source of Truth  
One YAML → renders → diff → commit.  
If it’s not in YAML, it does **not** exist.  
End of story.

Follow the checklist above and you will never again hand-craft a DMVPN hub config.

---

Below is the **sanitized, pseudocode-style** view of every functional knob an ASR-1002 DMVPN head-end needs—**no literals, no opinionated values**.  
Each bullet is a **config option** you can turn on/off or fill-in later.  
Think of it as a checklist you hand to an engineer who will decide the actual values.

--------------------------------------------------------
GLOBAL / SYSTEM
--------------------------------------------------------
- set hostname                       → string
- set boot system flash image        → filename
- set config-register                → hex
- set enable secret hash             → encrypted-secret
- set clock timezone / summer-time   → zone offset [summer-rules]
- set ip domain-name                 → fqdn
- set ip name-servers                → list<ip>
- set logging buffered size          → bytes
- set logging console | monitor      → level
- set logging hosts                  → list<ip> [transport udp/tcp port]

--------------------------------------------------------
AAA
--------------------------------------------------------
- aaa new-model
- aaa group server tacacs+
  - server-private ip key
- aaa authentication login default   → [local | group tacacs | line]
- aaa authorization commands 15      → [local | group tacacs]
- aaa accounting commands 15         → [start-stop | none] group tacacs
- local username / privilege / secret

--------------------------------------------------------
VRFs
--------------------------------------------------------
- vrf definition <name>
  - rd <rd>
  - address-family [ipv4 | ipv6]

--------------------------------------------------------
CRYPTO
--------------------------------------------------------
- crypto keyring <name> vrf <vrf>
  - pre-shared-key address ip key
- crypto isakmp policy <id>
  - encr [3des | aes]
  - auth [pre-share | rsa-sig]
  - group [2 | 5 | 14]
- crypto ipsec transform-set <name>
  - [esp-3des | esp-aes] [esp-md5 | esp-sha-hmac]
  - mode [tunnel | transport]
- crypto ipsec profile <name>
  - set transform-set <name>
  - set pfs | idle-time | lifetime

--------------------------------------------------------
TUNNEL / DMVPN
--------------------------------------------------------
- interface Tunnel<id>
  - ip address / mask
  - tunnel source [interface | ip]
  - tunnel mode gre multipoint
  - tunnel key <int>
  - tunnel vrf <vrf>
  - tunnel protection ipsec profile <name>
  - ip nhrp network-id <int>
  - ip nhrp authentication <string>
  - ip nhrp map multicast dynamic
  - ip nhrp server-only | nhs | redirect | shortcut
  - ip tcp adjust-mss <int>
  - qos pre-classify
  - service-policy output <policy>

--------------------------------------------------------
ROUTING – OSPF
--------------------------------------------------------
- router ospf <pid> [vrf <vrf>]
  - router-id <ip>
  - auto-cost reference-bandwidth <mbps>
  - passive-interface default
  - network <prefix> <wildcard> area <area>
  - area <area> authentication message-digest
  - interface ip ospf message-digest-key <key-id> md5 <key>

--------------------------------------------------------
ROUTING – BGP
--------------------------------------------------------
- router bgp <asn>
  - bgp router-id <ip>
  - bgp log-neighbor-changes
  - neighbor <ip | peer-group> remote-as <asn>
  - neighbor <ip> description <text>
  - neighbor <ip> ebgp-multihop <ttl>
  - neighbor <ip> update-source <interface>
  - neighbor <ip> password <string>
  - bgp listen range <prefix> peer-group <pg>
  - address-family ipv4
    - neighbor <ip> activate
    - neighbor <ip> route-reflector-client
    - neighbor <ip> next-hop-self [all]
    - neighbor <ip> default-originate
    - neighbor <ip> soft-reconfiguration inbound
    - neighbor <ip> route-map <in|out> <name>
    - network <prefix> mask <mask>

--------------------------------------------------------
QOS
--------------------------------------------------------
- class-map <match-any | match-all> <name>
  - match [access-group | dscp | protocol | …]
- policy-map <name>
  - class <name>
    - priority percent <int>
    - bandwidth remaining percent <int>
    - shape average <bps>
    - queue-limit <packets>
    - random-detect dscp-based
- service-policy [input | output] <policy>

--------------------------------------------------------
ACL / PREFIX / COMMUNITY LISTS
--------------------------------------------------------
- access-list <num|name> [standard | extended] [permit | deny] <rule>
- ip prefix-list <name> seq <num> permit/deny <prefix> [ge | le]
- ip community-list [standard | expanded] <name> [permit | deny] <regex>

--------------------------------------------------------
SNMP
--------------------------------------------------------
- snmp-server community <name> [ro | rw] [acl]
- snmp-server group <name> v3 [auth | priv] [read | write] <view>
- snmp-server user <user> <group> v3 auth sha <key> priv aes 128 <key>
- snmp-server host <ip> traps version 3 …
- snmp-server location <text>
- snmp-server contact <text>
- snmp-server enable traps … (list of trap types)

--------------------------------------------------------
INTERFACES (GENERIC)
--------------------------------------------------------
- interface <type num>
  - description <text>
  - ip address <ip> <mask> [secondary]
  - vrf forwarding <vrf>
  - mtu <bytes>
  - ip flow [ingress | egress]
  - service-policy [input | output] <policy>
  - ip ospf cost | hello-interval | dead-interval
  - channel-group <num> mode [active | passive | on]
  - speed / duplex / negotiation
  - shutdown | no shutdown

--------------------------------------------------------
MISCELLANEOUS
--------------------------------------------------------
- ip route [vrf <vrf>] <dest> <mask> <next-hop>
- ip sla <num> [icmp-echo | tcp-connect | udp-jitter] …
- ip sla schedule <num> life forever start-time now
- control-plane service-policy input <policy>
- line con 0 / line vty 0 15
  - password <string>
  - exec-timeout <min> <sec>
  - transport input [ssh | telnet | none]

Keep this checklist handy—fill in the blanks with real values only when you build the YAML for a specific box.

---

Below is a **minimal, reusable skeleton** that drops straight into  
`config_components/` and cleanly separates **policy** from **data**,  
while leaving every literal as a variable.

You can copy-paste these empty “placeholders” into the filenames shown;  
they already contain **only the functional knobs** (pseudocode).

--------------------------------------------------------
core_settings/00_licensing.j2
```
{# License boot level or Smart-Call-Home declarations #}
license boot level {{ license_level | default('network-advantage') }}
```

core_settings/10_system_settings.j2
```
hostname {{ hostname }}
service password-encryption
service timestamps debug datetime msec
service timestamps log datetime msec
ip domain-name {{ domain_name }}
{% for ns in name_servers %}
ip name-server {{ ns }}
{% endfor %}
clock timezone {{ tz_name }} {{ tz_offset }}
```

core_settings/20_aaa.j2
```
aaa new-model
!
aaa group server tacacs+ {{ tacacs_group }}
{% for srv in tacacs_servers %}
 server-private {{ srv.host }} key {{ srv.key }}
{% endfor %}
!
aaa authentication login default group {{ tacacs_group }} local
aaa authorization exec default group {{ tacacs_group }} local
aaa accounting commands 15 default start-stop group {{ tacacs_group }}
```

--------------------------------------------------------
network_services/30_vlans.j2
```
{% for vlan in vlans %}
vlan {{ vlan.id }}
 name {{ vlan.name }}
{% endfor %}
```

network_services/40_routing.j2
```
{% for instance in routing.ospf %}
router ospf {{ instance.pid }}{% if instance.vrf %} vrf {{ instance.vrf }}{% endif %}
 router-id {{ instance.rid }}
{% for net in instance.networks %}
 network {{ net.prefix }} {{ net.wc }} area {{ net.area }}
{% endfor %}
{% endfor %}

{% if routing.bgp %}
router bgp {{ routing.bgp.as }}
 bgp router-id {{ routing.bgp.rid }}
{% for neigh in routing.bgp.neighbors %}
 neighbor {{ neigh.ip }} remote-as {{ neigh.as }}
 neighbor {{ neigh.ip }} description {{ neigh.desc }}
{% endfor %}
 address-family ipv4
{% for net in routing.bgp.networks %}
  network {{ net.prefix }} mask {{ net.mask }}
{% endfor %}
{% for neigh in routing.bgp.neighbors %}
  neighbor {{ neigh.ip }} activate
{% endfor %}
 exit-address-family
{% endif %}
```

--------------------------------------------------------
interfaces/50_port_profiles/access_port.j2
```
interface {{ port.name }}
 description {{ port.desc | default('Access port') }}
 switchport mode access
 switchport access vlan {{ port.access_vlan }}
 switchport nonegotiate
 spanning-tree portfast
{% if port.shutdown is defined and port.shutdown %}shutdown{% else %}no shutdown{% endif %}
```

interfaces/50_port_profiles/trunk_port.j2
```
interface {{ port.name }}
 description {{ port.desc | default('Trunk port') }}
 switchport mode trunk
 switchport trunk native vlan {{ port.native_vlan }}
 switchport trunk allowed vlan {{ port.allowed_vlans | join(',') }}
 switchport nonegotiate
{% if port.shutdown is defined and port.shutdown %}shutdown{% else %}no shutdown{% endif %}
```

interfaces/60_interface_assignments.j2
```
{% for port in interfaces %}
{% set template = port.profile ~ '.j2' %}
{% include 'interfaces/50_port_profiles/' + template %}
{% endfor %}
```

--------------------------------------------------------
policies/70_qos.j2
```
{% for cmap in qos.class_maps %}
class-map {{ cmap.match_type }} {{ cmap.name }}
{% for rule in cmap.rules %}
 match {{ rule.type }} {{ rule.value }}
{% endfor %}
{% endfor %}
!
{% for pmap in qos.policy_maps %}
policy-map {{ pmap.name }}
{% for cls in pmap.classes %}
 class {{ cls.name }}
{% for action in cls.actions %}
  {{ action.cmd }} {{ action.value | default('') }}
{% endfor %}
{% endfor %}
{% endfor %}
```

policies/80_access_lists.j2
```
{% for acl in acls %}
{% if acl.type == 'extended' %}
ip access-list extended {{ acl.name }}
{% else %}
ip access-list standard {{ acl.name }}
{% endif %}
{% for rule in acl.rules %}
 {{ rule.action }} {{ rule.text }}
{% endfor %}
{% endfor %}
```

--------------------------------------------------------
USAGE (single command)
--------------------------------------------------------
```
find config_components -name "*.j2" | sort | xargs cat \
  | jinja2 -d site.yaml > final.cfg
```

- Each `.j2` file contains **only pseudocode variables**—no literals.  
- Drop **real values** into `site.yaml` (or per-device YAML) and render.