
Service Design as Code: Tooling Recommendation and Setup Guide
The right tooling makes the PoC achievable — the wrong tooling makes it a project in itself. Here's the complete, verified SDaC stack with every tool selected, every choice justified, and a step-by-step setup guide from an empty repository to a working CI/CD pipeline.
Series: Service Design as Code, Post #3
Service Design as Code — Tooling Recommendation and Setup Guide
A Verified, Step-by-Step Guide to the SDaC Proof of Concept Stack
If you have read my previous post on Service Design as Code, you will know the why: version-controlled, machine-readable ITSM service design that eliminates configuration drift, produces an auditable change history, and generates your Service Design Documents automatically. This post is the how — the specific tooling selected for the PoC, the reasoning behind each choice, and a complete, step-by-step setup guide for anyone ready to stand one up.
Everything in this guide has been executed and validated in a clean environment before publication. If a step produces an error, the Troubleshooting section at the end will likely have the answer. If you deviate from the prescribed approach, you are on your own — which is fine, but worth knowing.
1. Three Principles Behind Every Tooling Decision
Before getting into specifics, it is worth being transparent about the criteria used to make these selections. Three principles governed every choice.
Lowest viable complexity. The PoC must be operable by a Service Architect with moderate technical confidence — not a DevOps engineer. Every tool selected has clear documentation, a large community, and a gentle on-ramp. Tools that require significant operational investment to maintain — Jenkins, Tekton, CUE Lang — were excluded at this stage, regardless of their technical merit. There is nothing wrong with any of those tools in the right context. This is not the right context.
Transferability. Patterns established in the PoC must transfer cleanly to production. Any tool that creates lock-in, requires significant rework to scale, or introduces a bespoke runtime dependency was treated with caution. You should be able to take what you build here and grow it, not throw it away.
Cost. All recommended tools are free to use for the PoC. Where a tool has paid tiers relevant to production use, that is noted explicitly so the organisation can plan accordingly. There are no hidden costs in this stack.
2. The Recommended Toolchain at a Glance
The full stack, layer by layer:
- Version control — GitHub (cloud), free for public repositories; $4/user/month for private
- IDE — Visual Studio Code, free
- Authoring language — YAML (1.2 spec), with JSON as the exchange format
- YAML linting — yamllint 1.35+, free
- Schema validation — JSON Schema (draft-07) + AJV 8.x, free
- Validation runtime — Node.js 22 LTS, free
- CI/CD pipeline — GitHub Actions, free (2,000 minutes/month on the free plan)
- Documentation generation — Python 3.12 + Jinja2, free
- Document conversion — Pandoc 3.x, free
- Drift detection — custom Node.js script + Mock API, free
The entire PoC runs on free tooling. Every item in the stack is either open source or has a free tier that is more than sufficient for a twelve-week proof of concept.
3. Tooling Recommendations — The Detailed Rationale
Version Control — GitHub
GitHub is recommended as the version control platform. Its pull request workflow is the most intuitive available for teams that mix developers and non-developers, which is exactly the profile of a typical SDaC team. The code review interface, inline comment capability, and required approval rules are all accessible without command-line knowledge — important when process owners are being asked to review schema changes they did not author.
GitHub Actions, GitHub's native CI/CD platform, requires no additional provisioning or configuration to activate. The pipeline definition is a YAML file committed to the repository itself, which aligns directly with the SDaC philosophy of treating everything as code. The Actions marketplace contains pre-built actions for every tool in this stack.
One note on data residency: GitHub is US-hosted. If the organisation has data sovereignty requirements that preclude US-hosted services, GitLab (self-hosted) is the recommended alternative. The pipeline patterns in this guide translate directly to GitLab CI with minor syntax changes.
On cost: a private repository under an existing organisational subscription is assumed. If no subscription exists, a public repository is acceptable for a PoC using placeholder data only — but confirm with Information Security before proceeding.
IDE — Visual Studio Code
VS Code is recommended as the authoring environment. Once configured with a JSON Schema reference, it provides real-time validation and autocompletion as authors type — meaning schema errors are caught at the keyboard rather than at the CI/CD pipeline. For non-technical authors, this is significant: the feedback loop shrinks from minutes to seconds, and the error messages are visible in context rather than buried in a pipeline log.
The YAML extension by Red Hat (the most widely installed YAML extension in the VS Code marketplace) supports yaml.schemas configuration, which associates specific YAML files with their JSON Schema definitions. Process owners and Service Architects get guided authoring without needing to understand JSON Schema internals.
Four extensions are required: the Red Hat YAML extension for validation and autocompletion; GitLens for enhanced Git history and diff visualisation; Prettier for consistent code formatting; and markdownlint for reviewing generated documentation output.
Schema Authoring Language — YAML with JSON as the Exchange Format
The full YAML vs JSON evaluation is covered in the implementation plan companion post. The short version: YAML is the right authoring choice for three reasons.
First, YAML supports inline comments. For a service design schema, the ability to embed rationale directly in the file is not a convenience — it is essential. A future Service Architect reading the schema needs to understand why a decision was made, not just what it says. JSON offers no mechanism for this.
Second, YAML is less syntactically noisy for nested structures, which are unavoidable in process schema definitions. Priority matrices, escalation paths, and automation rules all involve multiple levels of nesting; JSON's mandatory braces, quotes, and commas create significant visual overhead at that depth.
Third, YAML is the native format of GitHub Actions pipeline definitions. The team will be working with YAML throughout the toolchain regardless. Standardising on one format reduces cognitive overhead.
The JSON exchange layer is handled automatically: the AJV validator operates on JSON, so the validation pipeline converts YAML to JSON before running validation. Authors never produce JSON directly. If ITSM platform APIs are called during drift detection, the script serialises the relevant schema data to JSON for the API request. YAML authoring and JSON exchange are fully decoupled.
Linting — yamllint
yamllint is a lightweight Python-based YAML linter that catches syntax errors, formatting inconsistencies, and common YAML pitfalls before schema validation runs. It is not a schema validator — it validates that the YAML file is well-formed, not that its content is correct.
It runs first in the pipeline because it produces clear, line-level error messages that are easier for non-developers to interpret than AJV's JSON Schema errors. A malformed YAML file will fail AJV with a cryptic parser error; yamllint catches it first and points directly to the offending line. That is a meaningfully better experience for the team.
yamllint is configured via a .yamllint.yml file in the repository root, allowing rule tuning — line length, indentation depth, document start markers — to match team conventions.
Schema Validation — JSON Schema (draft-07) + AJV
JSON Schema is a mature, well-supported standard for defining the structure and constraints of JSON (and by extension, YAML) documents. Draft-07 is recommended over later drafts because it has the broadest tooling support and is sufficient for all SDaC schema requirements at this stage.
AJV (Another JSON Validator) is the most widely used and best-performing JSON Schema validator for Node.js. It compiles schemas to optimised validation functions, produces clear error messages, and has been actively maintained for over a decade. The validation script converts each YAML schema to JSON using js-yaml, then runs it through AJV. If validation fails, the script exits with code 1, which fails the CI/CD pipeline and prevents non-compliant schemas from merging to main.
Pydantic (Python) was considered and is an excellent tool — the preferred choice for a Python-first team. The reason it was not selected here: Node.js is already in the stack for the drift detection script. Keeping validation in the same runtime reduces the number of language runtimes the team must manage. If there is a strong Python preference in your team, Pydantic is a valid drop-in alternative.
CI/CD Pipeline — GitHub Actions
GitHub Actions is the zero-configuration choice for GitHub-hosted repositories. The pipeline definition is a YAML file committed to .github/workflows/ — meaning the pipeline itself is version-controlled, peer-reviewed, and subject to the same change governance as the schemas.
The pipeline runs four sequential stages. Stage 1 (Lint) runs yamllint across all schema files. Stage 2 (Validate) runs AJV against all JSON Schema definitions. Stage 3 (Generate Docs) runs the Jinja2 documentation generation script and uploads the output as a pipeline artefact. Stage 4 (Drift Detect) runs the comparator script against the mock (or live) ITSM platform API.
Each stage depends on the previous one passing. A lint failure stops the pipeline before validation runs. This fail-fast behaviour ensures that pipeline output is always trustworthy — if Stage 3 produced documentation, you know the schema is syntactically valid and structurally conformant.
On cost: the PoC pipeline runs in under two minutes per execution. At typical commit frequency during development, the free tier (2,000 minutes/month) is more than sufficient.
Documentation Generation — Jinja2 + Pandoc
Jinja2 is Python's most mature templating engine. It renders structured data — the YAML schema — into any text-based output format using template files. For SDaC, each process has a corresponding Jinja2 template that defines how the schema data should be presented in a human-readable Service Design Document section.
Jinja2 was chosen over Handlebars (JavaScript) because Python is already in the stack for yamllint. Jinja2 templates are also readable without programming knowledge — a process owner comfortable editing a Word document can, with minimal guidance, adjust a Jinja2 template.
Pandoc converts the Markdown output to Word (.docx) with a single command. It is available as a binary with no runtime dependency, making it straightforward to include in CI/CD pipelines. For production use, a reference .docx template can be provided to Pandoc to apply organisational branding and styles automatically.
MkDocs with the Material Theme was evaluated and is noted as a strong candidate for production documentation portals — it produces genuinely excellent web-based documentation sites. For the PoC, the primary output requirement is Markdown and Word, so MkDocs is deferred to the full adoption programme.
Drift Detection — Custom Node.js Script + Mock API
A custom script is recommended over any third-party drift detection tool because no off-the-shelf tool exists that understands both the SDaC schema structure and ITSM platform configuration APIs. Building a lightweight comparator is straightforward and produces output precisely tailored to the schema fields being compared.
The Mock API approach — static JSON files simulating the ITSM platform's API responses — decouples PoC development from live system access. This is a significant risk reduction. By deliberately introducing discrepancies into the mock file, drift detection can be tested thoroughly before a live platform is connected. In Phase 2, the mock file path is simply replaced with a live API call.
The script is designed to be extended. Adding a new comparison — checking Problem Management's root cause methodology against a ServiceNow field, for example — requires adding a small block to the comparator function, not a new tool or integration.
4. Prerequisites
Before beginning setup, the following must be in place.
You will need a GitHub account (a free account is sufficient; an organisation account is needed for private repositories) and a Git repository created before starting the setup steps. Git 2.x must be installed locally — git --version will confirm this.
On your local machine, install the following: Node.js 22 LTS (minimum version 18) from nodejs.org; Python 3.12 (minimum 3.10) from python.org; Git 2.x from git-scm.com; VS Code from code.visualstudio.com; and Pandoc 3.x from pandoc.org/installing.html.
Once installed, verify each with:
node --version # v22.x.x or higher
python3 --version # Python 3.12.x or similar
git --version # git version 2.x.x
pandoc --version # pandoc 3.x.x
In VS Code, install the four required extensions: redhat.vscode-yaml, eamodio.gitlens, esbenp.prettier-vscode, and davidanson.vscode-markdownlint.
5. Repository Setup — Step by Step
Step 1 — Create the GitHub Repository
Log into GitHub, click the + icon in the top-right corner, and select New repository. Name it sdac-poc, set visibility to Private (or Public if approved by Information Security), check Add a README file, and click Create repository.
Step 2 — Clone the Repository Locally
git clone https://github.com/[YOUR_ORG]/sdac-poc.git
cd sdac-poc
Step 3 — Create the Directory Structure
mkdir -p schemas/processes
mkdir -p schemas/overrides
mkdir -p validation/schemas
mkdir -p templates/markdown
mkdir -p output/docs
mkdir -p scripts
mkdir -p mock-api
mkdir -p .github/workflows
This produces the following structure:
sdac-poc/
├── .github/workflows/ # GitHub Actions pipeline definitions
├── mock-api/ # Mock ITSM API response files (Phase 1)
├── output/docs/ # Generated documentation output
├── schemas/
│ ├── overrides/ # Customer-specific schema overrides
│ └── processes/ # Baseline YAML process schemas
├── scripts/ # Validation, generation, and drift detection scripts
├── templates/markdown/ # Jinja2 templates for documentation generation
└── validation/schemas/ # JSON Schema definition files
Step 4 — Initialise Node.js
npm init -y
Replace the contents of the generated package.json with the following, which defines the four npm scripts used throughout the PoC:
{
"name": "sdac-poc",
"version": "1.0.0",
"description": "Service Design as Code — Proof of Concept",
"scripts": {
"validate": "node scripts/validate.js",
"generate": "python3 scripts/generate_docs.py",
"drift": "node scripts/detect_drift.js",
"lint": "yamllint -c .yamllint.yml schemas/"
},
"dependencies": {
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"js-yaml": "^4.1.0"
}
}
Then install the Node.js dependencies:
npm install
Step 5 — Install Python Dependencies
pip3 install yamllint jinja2 pyyaml
On Debian/Ubuntu-based systems, add --break-system-packages. On some systems, use pip instead of pip3.
Step 6 — Create the .gitignore File
node_modules/
output/docs/
*.bak
.DS_Store
__pycache__/
*.pyc
Step 7 — Configure yamllint
Create .yamllint.yml in the repository root:
---
extends: default
rules:
line-length:
max: 120
truthy:
allowed-values: ['true', 'false']
comments:
min-spaces-from-content: 1
indentation:
spaces: 2
indent-sequences: true
Step 8 — Configure VS Code Schema Association
Create .vscode/settings.json:
{
"yaml.schemas": {
"./validation/schemas/incident_management.schema.json": [
"schemas/processes/incident_management.yaml",
"schemas/overrides/**/incident_management.yaml"
]
},
"yaml.validate": true,
"yaml.format.enable": true,
"editor.insertSpaces": true,
"editor.tabSize": 2
}
Add additional schema associations as you create JSON Schema files for each process.
Step 9 — Initial Commit
git add .
git commit -m "chore: initial repository structure and tooling configuration"
git push origin main
6. Schema Authoring — Step by Step
This section walks through authoring the first process schema: Incident Management.
Step 1 — Create the JSON Schema Definition
The JSON Schema defines the structure that all Incident Management YAML files must conform to. It is the contract that the validation pipeline enforces. Create validation/schemas/incident_management.schema.json with the full schema definition covering the metadata block (requiring schema_version, service, customer, owner, last_reviewed, and status — where status must be one of draft, baseline, approved, or deprecated) and the incident_management block (requiring priority_matrix, sla_clock, tooling, and reporting).
The priority matrix uses additionalProperties to allow any priority key (P1, P2, P3, P4) whilst enforcing that each must contain label, description, response_time, resolution_target, support_hours, and escalation_path (a non-empty array of strings).
Step 2 — Author the YAML Schema
Create schemas/processes/incident_management.yaml. A key point worth emphasising: the comments embedded in the schema are not decoration. They are the design rationale for future readers. The opening comment block should state explicitly that this file is the single source of truth, that changes require a peer-reviewed pull request, and that approval from the Process Owner is required before merging to main.
A completed Incident Management schema looks like this:
---
# incident_management.yaml
# SDaC Schema — Incident Management
# Schema Version: 1.0.0
#
# PURPOSE: Defines the authoritative service design for Incident Management.
# This file is the single source of truth. Changes require a peer-reviewed
# pull request and approval from the Process Owner before merging to main.
metadata:
schema_version: "1.0.0"
service: "End User Services"
customer: "[CUSTOMER_NAME]"
owner: "[PROCESS_OWNER]"
last_reviewed: "2026-05-06"
status: "draft"
incident_management:
priority_matrix:
P1:
label: "Critical"
description: "Complete service loss or critical business impact"
response_time: "15 minutes"
resolution_target: "4 hours"
support_hours: "24x7"
escalation_path:
- "Service Desk"
- "Team Lead"
- "Head of IT"
- "Service Director"
P2:
label: "High"
description: "Significant degradation affecting multiple users"
response_time: "30 minutes"
resolution_target: "8 hours"
support_hours: "24x7"
escalation_path:
- "Service Desk"
- "Team Lead"
P3:
label: "Medium"
description: "Single user impact or workaround available"
response_time: "4 hours"
resolution_target: "3 business days"
support_hours: "business hours"
escalation_path:
- "Service Desk"
P4:
label: "Low"
description: "Minor issue; no business impact"
response_time: "next business day"
resolution_target: "5 business days"
support_hours: "business hours"
escalation_path:
- "Service Desk"
sla_clock:
pause_conditions:
- "Awaiting Customer"
- "Awaiting Third Party"
business_hours_definition: "08:00-18:00 Mon-Fri excluding UK public holidays"
exclusions:
- "UK Bank Holidays"
major_incident:
threshold: "P1 or P2 affecting more than 50 users"
bridge_process: "Bridge call initiated within 30 minutes of P1 declaration"
communications_cadence: "Every 30 minutes to named stakeholders"
post_incident_review: true
tooling:
platform: "ServiceNow"
queue_name: "INC_EUS_PROD"
auto_assign: false
automation_rules:
- trigger: "unassigned > 15 minutes AND priority = P1"
action: "auto-escalate to Team Lead and send alert"
reporting:
internal_frequency: "weekly"
customer_frequency: "monthly"
metrics:
- "Volume by priority"
- "SLA compliance %"
- "MTTR by priority"
- "Backlog trend"
Step 3 — Verify VS Code Validation Is Working
Open the file in VS Code. If the YAML extension and schema association are configured correctly, you should see no red underlines on a valid file, hovering over fields should show type hints from the JSON Schema, and removing a required field should immediately produce a red underline. If VS Code validation is not triggering, check that the .vscode/settings.json path to the JSON Schema is correct relative to the repository root.
7. Validation Pipeline — Step by Step
Step 1 — Create the Validation Script
Create scripts/validate.js. The script reads all JSON Schema definitions from validation/schemas/, finds the corresponding YAML file in schemas/processes/, converts the YAML to JSON using js-yaml, compiles the JSON Schema with AJV, and validates the data. Results are printed as a formatted report. If any schema fails, the script exits with code 1, failing the pipeline.
An optional --schema argument allows a single process to be targeted: node scripts/validate.js --schema incident_management.
Step 2 — Run Validation Locally
node scripts/validate.js
A passing run produces:
╔══════════════════════════════════════════╗
║ SDaC Schema Validation Report ║
╚══════════════════════════════════════════╝
✅ incident_management PASS
──────────────────────────────────────────
Result: ✅ ALL SCHEMAS VALID
──────────────────────────────────────────
Step 3 — Run yamllint
yamllint -c .yamllint.yml schemas/
A clean file produces no output. Warnings appear for style issues; errors appear for syntax failures. yamllint exits with code 0 on warnings and 1 on errors. Include the --- document start marker at the top of every schema file to keep linting clean.
8. Documentation Generation — Step by Step
Step 1 — Create the Jinja2 Template
Create templates/markdown/incident_management.md.j2. The template uses Jinja2's {{ variable }} syntax to inject schema field values, and {% for %} loops to iterate over the priority matrix and produce per-priority sections. The template defines the complete structure of the generated Markdown SDD section — headers, priority data, escalation paths, SLA clock, tooling configuration, and reporting metrics — all populated dynamically from the YAML schema at generation time.
Step 2 — Create the Generation Script
Create scripts/generate_docs.py. The script discovers all YAML files in schemas/processes/, looks for a matching Jinja2 template in templates/markdown/, loads the YAML data, renders the template, and writes the output to output/docs/. An optional --process argument targets a single process.
Step 3 — Run Documentation Generation
python3 scripts/generate_docs.py
Expected output:
✅ Generated: /path/to/sdac-poc/output/docs/incident_management.md
─────────────────────────────────────────────
Documentation generation complete.
Generated: 1 file(s)
─────────────────────────────────────────────
Step 4 — Convert to Word (Optional)
pandoc output/docs/incident_management.md \
-o output/docs/incident_management.docx \
--standalone
For production use, provide a reference .docx template to Pandoc using --reference-doc=your-template.docx to apply organisational branding and styles automatically.
9. Drift Detection — Step by Step
Step 1 — Create the Mock API Response File
Create mock-api/incident_management_config.json. This file simulates what a live ITSM platform API would return when queried for Incident Management configuration. It defines the platform name, queue name, and the response time and resolution target for each priority. In Phase 1, this is a static file updated manually. In Phase 2, the drift detection script is pointed at a real API endpoint and this file becomes redundant.
{
"platform": "ServiceNow",
"queue_name": "INC_EUS_PROD",
"priorities": {
"P1": { "response_time": "15 minutes", "resolution_target": "4 hours" },
"P2": { "response_time": "30 minutes", "resolution_target": "8 hours" },
"P3": { "response_time": "4 hours", "resolution_target": "3 business days" },
"P4": { "response_time": "next business day", "resolution_target": "5 business days" }
}
}
Step 2 — Create the Drift Detection Script
Create scripts/detect_drift.js. The script loads the YAML schema and the mock API response file, then compares the two across the defined field set — queue name, and response time and resolution target for each priority. Fields that match are added to a checks array; fields that differ are added to a drifts array. The report prints all matching fields first, then any drift, then the overall result. If any drift is detected, the script exits with code 1.
Step 3 — Run Drift Detection
node scripts/detect_drift.js
A compliant output looks like this:
── Matching Fields ──────────────────────────────────
✅ queue_name "INC_EUS_PROD"
✅ P1.response_time "15 minutes"
✅ P1.resolution_target "4 hours"
...
Result: ✅ COMPLIANT — no drift detected
A drift output clearly identifies each divergent field:
── Drift Detected ───────────────────────────────────
❌ P1.response_time
Schema says: "15 minutes"
Live config: "30 minutes"
Result: ❌ DRIFT DETECTED — 1 field(s) out of alignment
10. GitHub Actions Pipeline — Step by Step
Step 1 — Create the Workflow File
Create .github/workflows/validate.yml. The pipeline triggers on push to main and feature/** branches, and on pull requests targeting main. It defines four jobs: Lint, Validate, Generate Docs, and Drift Detection. Each job specifies needs: [previous-job], creating a sequential dependency chain. The Generate Docs job uploads the generated documentation as a pipeline artefact with a thirty-day retention period, so the output of every pipeline run is downloadable from the GitHub Actions interface.
Step 2 — Commit and Push
git add .
git commit -m "ci: add GitHub Actions SDaC validation pipeline"
git push origin main
Step 3 — Verify the Pipeline Runs
Navigate to your GitHub repository, click the Actions tab, and confirm the SDaC Validation Pipeline workflow appears and runs automatically. All four stages should pass and display a green tick.
Step 4 — Configure Branch Protection
This is the step that gives the pipeline its teeth. In GitHub, go to Settings → Branches, click Add branch ruleset, and set the branch name pattern to main. Enable Require status checks to pass before merging and add all four workflow jobs as required checks. Enable Require a pull request before merging and set the required number of approvals to 1.
From this point, no schema change can reach main without passing all four pipeline stages and receiving at least one peer approval. That is the change governance model in operation.
11. Testing Guide
Nine tests cover the complete PoC functionality. Each has a defined setup, expected result, and pass/fail criterion.
Test 1 — Valid Schema Passes Validation. Run yamllint and the validation script against a correctly authored schema. Both should exit with code 0 and the validation script should print ✅ ALL SCHEMAS VALID.
Test 2 — Missing Required Field Fails Validation. Remove the status: line from the metadata block and run the validation script. Expected output: ❌ incident_management FAIL with a message pointing to the missing required property. Exit code 1. Restore the file after the test.
Test 3 — Invalid Status Enum Fails Validation. Change status: "draft" to status: "in progress" and run validation. Expected output: must be equal to one of the allowed values. Exit code 1. Restore after the test.
Test 4 — YAML Syntax Error Fails Linting. Introduce a tab character at the start of a line (YAML prohibits tabs) and run yamllint. Expected output: a line-specific error. Exit code 1. Restore after the test.
Test 5 — Drift Detection Passes When Config Matches. With the mock API file exactly matching the schema values, run the drift detection script. Expected output: all fields listed under Matching Fields, and Result: ✅ COMPLIANT. Exit code 0.
Test 6 — Drift Detection Fails When Config Diverges. Edit the mock API file to change the P1 response time and the queue name, then run drift detection. Expected output: both divergent fields clearly listed with Schema says: and Live config: values. Exit code 1. Restore the mock file after the test.
Test 7 — Documentation Generation Produces Correct Output. Run the generation script and open the output Markdown file. Verify that the priority matrix has four rows, the P1 escalation path is correct, the SLA clock section is accurate, and the tooling section shows the correct platform and queue name.
Test 8 — Pull Request Pipeline Blocks Merge on Failure. Create a feature branch, introduce a schema error, push, and open a pull request targeting main. The pipeline should fail at Stage 2 and the Merge button should be greyed out, showing Merging is blocked. This test confirms that branch protection is working as intended.
Test 9 — End-to-End Schema Change Flows Through Pipeline. The most important test. Create a branch, change a P3 resolution target, commit and push, open a pull request, observe all four pipeline stages pass, have a colleague approve the PR, merge to main, and download the generated docs artefact. The updated SLA value should be visible in the generated documentation, with a complete Git audit trail of who made the change and who approved it. That is the full change governance workflow in operation.
12. Troubleshooting
A handful of issues come up reliably in a fresh setup.
yamllint reports "wrong indentation" on a file that looks correct. YAML prohibits tab characters. In VS Code, check the bottom-right status bar — it should show Spaces: 2. If it shows Tab Size, click it and select Convert Indentation to Spaces.
AJV produces "strict mode" errors. The schema references a format keyword that AJV's strict mode rejects. Add ajv-formats to the validation script, or change the AJV initialisation to strict: false.
npm ci fails with "missing package-lock.json". npm ci requires a package-lock.json. Run npm install first to generate it, then commit package-lock.json to the repository. From that point, use npm ci in pipelines.
GitHub Actions pipeline does not trigger on push. Confirm that the workflow file is at exactly .github/workflows/validate.yml (not .github/workflow/ or another path). The .github directory must be at the repository root. Also confirm that the on.push.branches list includes the branch you are pushing to.
Jinja2 template produces blank rows in output. The Jinja2 {% for %} loop produces a blank line before each row by default. This is cosmetic and does not affect Markdown rendering. To suppress it, use {%- for ... %} and {%- endfor %} with dash-based whitespace stripping.
Pandoc .docx output has incorrect formatting. Pandoc applies its own default styles to .docx output. To use organisational styles, generate a default .docx once, open it in Word and apply your heading styles, fonts, and colours, then save it as templates/reference.docx. Add --reference-doc=templates/reference.docx to all subsequent Pandoc commands.
Drift detection shows false positives. False positives occur when the mock API file format does not exactly match the YAML schema field values — for example, the schema says "15 minutes" but the mock API says "15mins". Ensure that the mock API file uses exactly the same string values as the schema. In production, the drift detection script will need to normalise values before comparison.
13. Next Steps After PoC
On successful completion of the PoC, six workstreams are recommended before moving to production.
Schema expansion. Author JSON Schema definitions and YAML process files for the remaining ITIL® practices in scope: Change Enablement, Service Level Management, Service Catalogue Management, and Configuration Management.
Live ITSM API integration. Replace the Mock API in the drift detection script with calls to the live ITSM platform REST API. For ServiceNow, this means using the Table API to query SLA definitions, assignment group configurations, and incident categorisation rules. Credentials should be stored as GitHub Actions secrets — never committed to the repository.
Customer override pattern. Implement the base schema plus customer override merge pattern prototyped in Phase 2. Each customer engagement gets its own override directory under schemas/overrides/[customer-name]/ containing only the fields that differ from the baseline.
AI-assisted generation. Integrate an LLM API call into the workflow to generate initial YAML draft schemas from a natural language prompt. A Service Architect describes the service in plain English; the AI produces a valid YAML draft conforming to the JSON Schema; the architect reviews and refines before committing.
MkDocs portal. Deploy a MkDocs documentation site — GitHub Pages is free and zero-configuration for public repositories — that serves the generated Markdown as a browsable, searchable documentation portal. Process owners and customers can view the current baseline service design without needing Git access.
Scheduled drift detection. Convert the drift detection job from a push-triggered check to a scheduled GitHub Actions cron job running daily. If drift is detected, open a GitHub Issue automatically to trigger investigation.
Final Thoughts
This guide represents a complete, verified path from an empty repository to a working SDaC PoC. The toolchain is deliberately lean — every tool is either free, widely used, or both — and the nine test cases give you clear pass/fail evidence to include in your PoC findings report.
The full pipeline, from schema change to merged documentation with a complete audit trail, can be demonstrated in under ten minutes once the environment is set up. That demonstration — Test 9 — is the one to run in front of stakeholders. It makes the value of the approach immediately tangible in a way that no amount of documentation can.
Hopefully this has been useful to you, and I wish you well on your ITSM journey.
All commands and scripts in this guide were executed and verified in a clean Ubuntu 24.04 environment using Node.js v22.22.2, Python 3.12.3, and Pandoc 3.1.3 prior to publication. Commands may require minor adjustment for Windows environments — use python instead of python3, and note that path separators differ.
Companion document: Service Design as Code — A Practical Guide to the Proof of Concept



Comments
Loading...