Skip to content

feat: Add AI-discoverable documentation headers to all template files (#308)#311

Merged
josecelano merged 22 commits into
mainfrom
308-add-ai-discoverable-documentation-headers-to-templates
Jan 28, 2026
Merged

feat: Add AI-discoverable documentation headers to all template files (#308)#311
josecelano merged 22 commits into
mainfrom
308-add-ai-discoverable-documentation-headers-to-templates

Conversation

@josecelano

Copy link
Copy Markdown
Member

Overview

Adds AI-discoverable documentation headers to all 32 template files (11 dynamic .tera templates + 21 static templates) to improve documentation discoverability for both development-time and production-time scenarios.

Issue: Closes #308

Problem

AI agents helping with configuration troubleshooting only see rendered output files, without context about:

  • Original template source
  • Configuration options and valid values
  • Documentation location

Solution

Added standardized documentation headers to all templates containing:

  • Repository URL
  • Template source path
  • Rust wrapper path (dynamic templates only)
  • API documentation link
  • Description

Header Format

Dynamic templates (.tera files) - with timestamp:

# ============================================================================
# Torrust Tracker Deployer - Generated Configuration
# ============================================================================
#
# This file was generated by the Torrust Tracker Deployer.
# Generated: 2026-01-27T19:39:46Z
#
# DOCUMENTATION:
#   Repository:    https://github.com/torrust/torrust-tracker-deployer
#   Template:      templates/tracker/tracker.toml.tera
#   Rust Wrapper:  src/infrastructure/templating/.../template.rs
#   API Docs:      https://docs.rs/torrust-tracker-deployer/latest/
#
# DESCRIPTION: ...
# ============================================================================

Static templates - simplified (no timestamp, no Rust path):

# ============================================================================
# Torrust Tracker Deployer - Generated Configuration
# ============================================================================
#
# This file was generated by the Torrust Tracker Deployer.
#
# DOCUMENTATION:
#   Repository:    https://github.com/torrust/torrust-tracker-deployer
#   Template:      templates/ansible/install-docker.yml
#   API Docs:      https://docs.rs/torrust-tracker-deployer/latest/
#
# DESCRIPTION: ...
# ============================================================================

Implementation

Phase 1: Infrastructure Setup

  • Created TemplateMetadata struct with generated_at field (ISO 8601 format)
  • Injected SystemClock service into all project generators
  • Added metadata field (flattened) to all template contexts

Phase 2: Dynamic Templates (11 files)

Updated all .tera templates:

  • tracker.toml.tera, inventory.yml.tera, variables.yml.tera
  • docker-compose.yml.tera, .env.tera, Caddyfile.tera
  • prometheus.yml.tera (2 files), cloud-init.yml.tera
  • hetzner/variables.tfvars.tera, lxd/variables.tfvars.tera

Phase 3: Static Templates (21 files)

Updated with simplified headers:

  • 18 Ansible playbooks (install-docker.yml, deploy-*.yml, etc.)
  • 1 Grafana provisioning YAML
  • 2 OpenTofu main.tf files (LXD + Hetzner)

Phase 4: Documentation

  • Added "AI-Discoverable Headers" section to template-system-architecture.md
  • Created yml.md for YAML-specific conventions (header placement before ---)
  • Updated templates README with references

Phase 5: Full-Stack Manual E2E Test

Verified headers in real-world deployment with all services:

  • Provider: LXD (local VM)
  • Database: MySQL
  • Monitoring: Prometheus + Grafana
  • HTTPS: Caddy reverse proxy
  • Result: All services healthy, headers present and correct

Bonus: Bugfix

Fixed error message display (discovered during testing):

  • Before: "Configuration validation failed" (no context)
  • After: "Configuration validation failed: HTTPS section is defined but no service has TLS configured"
  • Impact: Users now see actionable error messages

Testing

Pre-commit checks: All passing (2089 unit tests + E2E + linting)

Manual E2E Test Results:

  • All 5 containers running and healthy (tracker, MySQL, Prometheus, Grafana, Caddy)
  • Headers verified in rendered output:
    • Dynamic templates show timestamps
    • Static templates use simplified format
    • YAML headers before --- marker
  • Deployment times: 26.7s provision + 45s configure + 18.4s release + 39.9s run

Documentation

  • Template system architecture updated with header pattern
  • YAML conventions documented (header placement)
  • All acceptance criteria verified and checked

Files Changed

  • Infrastructure: 1 new metadata struct, clock injection in 6 project generators
  • Templates: 32 files updated (11 dynamic + 21 static)
  • Documentation: 4 markdown files updated
  • Bugfix: 1 error message improvement

Breaking Changes

None. Headers are additive and don't change existing functionality.

Migration Guide

Not applicable - backward compatible change.

- Fix dynamic templates list with correct paths from tree output
- Remove JSON files from static templates (no comment support)
- Update static templates table with 21 files
- Add exclusion note for JSON format in Phase 3
- Fix metadata reference in domain module temporarily
- Add TemplateMetadata struct with DateTime<Utc> field
- Use generated_at_iso8601() for template rendering
- Remove from_clock() to avoid coupling with Clock trait
- Complete Phase 1 of implementation plan

docs: [#308] update spec for incremental template implementation

- Restructure phases: Phase 1 (infrastructure) is now complete
- Phase 2 now processes one template at a time
- For each template: update generator → add header → test → commit
- Remove bulk infrastructure updates from Phase 1
- Add TemplateMetadata struct with DateTime<Utc> and ISO 8601 serialization
- Update TrackerContext with metadata field (flattened for template compatibility)
- Integrate clock service through generator chain (SystemClock for production)
- Add 22-line AI-discoverable header to tracker.toml.tera template
- Use {%- and -%} delimiters to prevent extra blank lines from control flow
- Fix doctest in TrackerContext to include Clock trait import
- Update specification with detailed process for each Tera template:
  * Add AI-discoverable header
  * Review and clean up comments (keep config guidance, move implementation details to wrapper)
  * Fix Tera delimiters to avoid extra line breaks
  * Verify rendered output with pre-commit checks
- First dynamic template complete (9 dynamic + 21 static remaining)
Added template metadata infrastructure to InventoryContext:
- Added metadata field with timestamp to InventoryContext
- Updated InventoryContextBuilder with metadata support
- Integrated Clock service through command handlers and steps
- Added clock field to RegisterCommandHandler
- Updated all handler constructors to pass clock
- Fixed presentation controller to pass clock to handlers

Template changes:
- Added 20-line AI-discoverable header to inventory.yml.tera
- Includes Generated timestamp, Repository, Template path, Rust Wrapper, API Docs
- Maintains existing comprehensive comments for dual infrastructure support

All tests pass with metadata integration.
Added template metadata infrastructure to AnsibleVariablesContext:
- Added metadata field with timestamp to AnsibleVariablesContext
- Updated constructor signature to accept metadata as first parameter
- Reuses metadata from InventoryContext for consistent timestamps
- Added metadata() getter to InventoryContext for access
- Updated all test calls to include metadata parameter

Template changes:
- Added 20-line AI-discoverable header to variables.yml.tera
- Includes Generated timestamp, Repository, Template path, Rust Wrapper, API Docs
- Maintains existing comments about vars_files usage and consistency

All tests pass with metadata integration.
Added explicit '---' delimiter after AI-discoverable header for consistency
with variables.yml.tera and YAML best practices. This clearly separates
file-level metadata (generation info, documentation) from the YAML document
content.
…tera

- Added metadata field to DockerComposeContext with #[serde(flatten)]
- Added with_metadata() method to DockerComposeContextBuilder
- Integrated Clock service through RenderDockerComposeTemplatesStep
- Created default metadata fallback using SystemClock for backward compatibility
- Added 20-line standardized header matching tracker.toml.tera format
- Updated all tests to pass clock parameter to step constructor
- All 107 docker_compose tests pass
…limiters

- Add AI-discoverable header with metadata to .env.tera template
- Fix Tera delimiters in .env.tera and variables.yml.tera (use {%- to trim whitespace)
- Update EnvContext to accept TemplateMetadata parameter in constructors
- Fix doctest in env.rs renderer to include metadata parameter
- Update all test helpers to use create_test_metadata() function
- All 406 tests pass successfully
- Add TemplateMetadata field to CaddyContext struct with #[serde(flatten)]
- Update CaddyContext::new() to accept TemplateMetadata as first parameter
- Update RenderCaddyTemplatesStep to accept and use clock service
- Inject SystemClock in release command handler for timestamp generation
- Add custom Default implementation for CaddyContext (metadata with epoch timestamp)
- Add 20-line AI-discoverable header to Caddyfile.tera template
- Header includes: repository URL, template path, wrapper path, API docs, description
- Description: "Caddy reverse proxy configuration with automatic HTTPS. Provides TLS
  termination for tracker services using Let's Encrypt. Includes X-Forwarded-For
  header configuration critical for peer IP tracking when running behind a proxy."
- Update all tests to use create_test_metadata() helper function
- Fix doctest import to include Clock trait for SystemClock usage
- Delimiters already correct (using {%- format to prevent extra blank lines)

All 406 tests passing.
…urce template

- Add TemplateMetadata field to DatasourceContext struct with #[serde(flatten)]
- Update DatasourceContext::new() to accept TemplateMetadata as first parameter
- Update GrafanaProjectGenerator to accept and use clock service
- Update RenderGrafanaTemplatesStep to accept and use clock service
- Inject SystemClock in grafana release command handler for timestamp generation
- Add custom Default implementation for DatasourceContext (metadata with epoch timestamp)
- Add 20-line AI-discoverable header to prometheus.yml.tera template
- Header includes: repository URL, template path, wrapper path, API docs, description
- Description: "Grafana datasource configuration for Prometheus. Defines the connection
  settings and query parameters for Grafana to access Prometheus metrics data."
- Update all tests to use create_test_metadata() helper function
- Template already had no delimiters (no control flow statements)

All 406 tests passing.
This commit completes template 8 of 11 dynamic templates, adding
metadata infrastructure and AI-discoverable documentation headers.

Changes:
- Added TemplateMetadata field to PrometheusContext with flattened serialization
- Updated PrometheusContext constructor to accept metadata as first parameter
- Added custom Default implementation using epoch timestamp (1970-01-01)
- Updated PrometheusProjectGenerator to accept Clock service in constructor
- Made build_context() non-static to use self.clock for timestamp generation
- Updated RenderPrometheusTemplatesStep to accept and inject clock service
- Updated release command handler to inject SystemClock
- Added create_test_metadata() helper in all test files
- Updated all test call sites (18 total) to use metadata parameter
- Added 20-line AI-discoverable header to prometheus.yml.tera template
- Header includes: repository URL, template path, Rust wrapper path, docs.rs link, description

Verified:
- All 406 unit tests passing
- E2E deployment tests passing (1m 50s)
- Pre-commit checks passing (5m 6s)
- Rendered output includes timestamp in ISO 8601 format

Template count: 8 of 11 dynamic templates complete (73%)
- Add TemplateMetadata with timestamp to CloudInitContext
- Inject Clock service through architecture layers:
  * CloudInitRenderer accepts clock and generates metadata
  * TofuProjectGenerator passes clock to renderer
  * Provision handler injects SystemClock
  * All tests updated with MockClock
- Add AI-discoverable header following spec format:
  * Repository URL and template path
  * Rust wrapper and API docs links
  * Template description
- CRITICAL: #cloud-config must stay on line 1 for cloud-init
- Header placed after #cloud-config with warning comment
- All 2089 tests passing
- E2E tests passing (infrastructure + deployment workflows)
- Add TemplateMetadata with timestamp to HetznerVariablesContext
- Add metadata field to VariablesContext struct (flattened for JSON output)
- Add metadata field to VariablesContextBuilder with with_metadata() method
- Add MissingMetadata error variant for validation
- Update build() method to validate and include metadata
- Inject metadata generation in TofuProjectGenerator.render_hetzner_variables_template()
- Update all context tests to include metadata (create_test_metadata helper)
- Update template wrapper tests to include metadata in test context
- Update doc comment example to show with_metadata() usage
- Add AI-discoverable header to templates/tofu/hetzner/variables.tfvars.tera
- Header follows spec format: Torrust Tracker Deployer - Generated Configuration
- Includes: Repository URL, template path, Rust wrapper path, API docs, description
- Uses generated_at variable for timestamp in rendered output

All 2089 tests passing, E2E tests successful (54s + 1m 59s)
- Add TemplateMetadata with timestamp to LxdVariablesContext
- Add metadata field to VariablesContext struct (flattened for JSON output)
- Add metadata field to VariablesContextBuilder with with_metadata() method
- Add MissingMetadata error variant for validation
- Update build() method to validate and include metadata
- Inject metadata generation in TofuProjectGenerator.render_lxd_variables_template()
- Update all context tests to include metadata (create_test_metadata helper)
- Update template wrapper tests to include metadata in test context
- Update doc comment example to show with_metadata() usage
- Add AI-discoverable header to templates/tofu/lxd/variables.tfvars.tera
- Header follows spec format: Torrust Tracker Deployer - Generated Configuration
- Includes: Repository URL, template path, Rust wrapper path, API docs, description
- Uses generated_at variable for timestamp in rendered output

All 2089 tests passing, E2E tests successful (54s + 1m 59s)
All 11 dynamic Tera templates now have AI-discoverable headers
All 11 dynamic Tera templates have been successfully updated with:
- AI-discoverable documentation headers
- TemplateMetadata infrastructure with timestamps
- Rendered output verification
- All tests passing (2089 unit tests + E2E tests)

Templates completed:
- tracker.toml.tera
- inventory.yml.tera
- variables.yml.tera
- docker-compose.yml.tera
- .env.tera
- Caddyfile.tera
- prometheus.yml.tera (Grafana datasource)
- prometheus.yml.tera (Prometheus config)
- cloud-init.yml.tera
- hetzner/variables.tfvars.tera
- lxd/variables.tfvars.tera

Next: Phase 3 - Static template headers
Move AI-discoverable headers BEFORE the '---' YAML document marker to match
the convention used in dynamic templates (e.g., variables.yml.tera).

The '---' marker indicates the start of a YAML document and should come after
the header metadata, not before it. This ensures consistency across all
Ansible templates.

Files corrected (17 YAML playbooks):
- configure-firewall.yml
- configure-security-updates.yml
- create-grafana-storage.yml
- create-mysql-storage.yml
- create-prometheus-storage.yml
- create-tracker-storage.yml
- deploy-caddy-config.yml
- deploy-compose-files.yml
- deploy-grafana-provisioning.yml
- deploy-prometheus-config.yml
- deploy-tracker-config.yml
- init-tracker-database.yml
- install-docker.yml
- install-docker-compose.yml
- run-compose-services.yml
- update-apt-cache.yml
- wait-cloud-init.yml

All YAML linters passing
Created comprehensive documentation for YAML template conventions at
docs/contributing/templates/yml.md covering:

Key Conventions:
- Header placement BEFORE '---' YAML document marker (critical rule)
- Rationale: Header is metadata, '---' marks document start
- Consistency across dynamic (.yml.tera) and static (.yml) templates

YAML-Specific Tera Patterns:
- Whitespace control with {%- and -%} to prevent blank lines
- Conditional indentation patterns
- List generation with proper formatting
- Multi-line string handling

Common Pitfalls:
- Wrong document marker placement
- Extra blank lines from control flow
- Breaking YAML indentation
- Unquoted special characters

Best Practices:
- Validate rendered output in build/ directory
- Use comments for user guidance
- Group related configuration
- Test rendered output for blank lines

Also updated:
- docs/contributing/templates/README.md - Added yml.md to documentation index
- docs/issues/308-add-ai-discoverable-documentation-headers-to-templates.md -
  Added reference to YAML conventions

All markdown linters passing
Phase 4 Documentation Complete:
- Added comprehensive 'AI-Discoverable Documentation Headers' section to
  template-system-architecture.md covering purpose, format, metadata
  infrastructure, and YAML placement conventions
- Cross-referenced yml.md for YAML-specific patterns
- Documented both dynamic and static template header formats

Acceptance Criteria Updates:
- Marked all quality checks as complete (pre-commit passes)
- Verified all task-specific criteria:
  * TemplateMetadata struct implementation
  * Clock service injection in all project generators
  * Metadata fields in all template contexts
  * Headers in all .tera templates with correct URLs
  * ISO 8601 timestamps in rendered output
  * E2E tests passing with headers
  * Documentation complete
- Verified all architectural constraints met

Rendered Output Verification:
- Confirmed dynamic templates show timestamps (tracker.toml, variables.yml)
- Confirmed YAML headers placed before --- marker
- Confirmed static templates use simplified headers (install-docker.yml)
- All headers include correct repository URLs and API docs links

Issue #308 - All 4 phases complete
- Phase 1: Infrastructure Setup ✅
- Phase 2: Dynamic Templates (11 files) ✅
- Phase 3: Static Templates (21 files) ✅
- Phase 4: Documentation ✅

Ready for final review and issue closure
Problem:
The CreateCommandHandlerError::InvalidConfiguration error message was
"Configuration validation failed" without including the source error.
This made it impossible for users to see what actual validation failed.

Example of what users saw:
  "Create command execution failed: Configuration validation failed"

What they should see:
  "Configuration validation failed: Cross-service configuration validation
  failed: HTTPS section is defined but no service has TLS configured"

Solution:
Changed error format from:
  #[error("Configuration validation failed")]
To:
  #[error("Configuration validation failed: {0}")]

This includes the source CreateConfigError in the display, which contains
the specific validation failure (e.g., missing TLS config, invalid port,
etc.).

Impact:
Users now see actionable error messages that tell them exactly what's
wrong with their configuration, not just "validation failed".

Related to: Issue #308 Phase 5 manual E2E testing - discovered while
testing full-stack configuration with MySQL + Prometheus + Grafana + HTTPS
Phase 5: Full-Stack Manual E2E Test - Complete

Test Configuration:
- Provider: LXD (local)
- Database: MySQL (not default SQLite)
- Monitoring: Prometheus + Grafana enabled
- Reverse Proxy: Caddy with HTTPS support
- All optional services: MySQL, Prometheus, Grafana, Caddy

Deployment Performance:
- Infrastructure provisioned: 26.7s
- Software configured: 45s (Docker, security, firewall)
- Application released: 18.4s
- Services started: 39.9s

All Services Verified Healthy:
✓ Tracker (HTTP API): Responding with {status: Ok}
✓ MySQL: 4 tables created, database initialized
✓ Prometheus: Both jobs (metrics + stats) scraping successfully
✓ Grafana: v12.3.1 running, database ok
✓ Caddy: HTTPS configured, certificate obtained (staging)

AI-Discoverable Headers Verified:
✓ Dynamic templates show timestamps (tracker.toml, docker-compose.yml)
✓ Static templates use simplified headers (install-docker.yml)
✓ YAML headers correctly placed BEFORE --- marker
✓ All headers include repository URLs and API docs links

Bugfix Discovered:
- Error messages were hiding validation details
- Fixed InvalidConfiguration to show underlying error: {0}
- Users now see actionable messages like 'HTTPS section defined but
  no service has TLS configured'
- Commit: 9a3bab1

Conclusion:
All 32 template files (11 dynamic + 21 static) successfully render
with correct AI-discoverable headers in real-world full-stack
deployment. Headers verified in production-like environment with
all services enabled.

Issue #308 - All 5 phases complete:
- Phase 1: Infrastructure ✅
- Phase 2: Dynamic Templates (11 files) ✅
- Phase 3: Static Templates (21 files) ✅
- Phase 4: Documentation ✅
- Phase 5: Full-Stack E2E Test ✅

Ready for issue closure.
@josecelano josecelano self-assigned this Jan 27, 2026
Markdown linting was failing because the Docker containers output
code block was missing a language specifier.

Added 'text' as the language for the code block showing docker ps
output to comply with MD040/fenced-code-language rule.

Fixes CI linting workflow failure.
@josecelano

Copy link
Copy Markdown
Member Author

ACK 17d07ca

@josecelano josecelano merged commit c68178a into main Jan 28, 2026
48 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add AI-Discoverable Documentation Headers to Template Files

1 participant