Skip to content

Commit 4193558

Browse files
rjaegersCopilot
andauthored
docs: add stp and rtm documents (#1210)
* docs: add stp and rtm documents * docs: fix encoding issue * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ron <45816308+rjaegers@users.noreply.github.com> * docs: correctly fill document context * docs: apply document control to all documents * docs: fix test name rendering * docs: update document control * docs: change page numbering to x of y * docs: clearly mark non-release documents as draft * docs: add more traceability * ci: also publish SBDL model * docs: use partials and custom filter support * ci: remove dummy arguments for SBDL version directives * docs: process review comments * docs: process review comments --------- Signed-off-by: Ron <45816308+rjaegers@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent a531283 commit 4193558

9 files changed

Lines changed: 392 additions & 32 deletions

.github/workflows/release-build.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ jobs:
118118
generate-documents:
119119
name: 📄 Documentation
120120
uses: ./.github/workflows/wc-document-generation.yml
121+
with:
122+
is-release: true
121123
permissions:
122124
contents: read
123125

@@ -140,7 +142,7 @@ jobs:
140142
- name: Upload documents to release
141143
run: |
142144
set -Eeuo pipefail
143-
gh release upload "${REF_NAME}" ./*.pdf
145+
gh release upload "${REF_NAME}" ./*.pdf ./*.sbdl
144146
env:
145147
GH_REPO: ${{ github.repository }}
146148
GH_TOKEN: ${{ github.token }}

.github/workflows/wc-document-generation.yml

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ name: Document Generation
33

44
on:
55
workflow_call:
6+
inputs:
7+
is-release:
8+
description: Whether the workflow is running for a release.
9+
default: false
10+
type: boolean
611

712
permissions: {}
813

@@ -22,19 +27,54 @@ jobs:
2227
- name: Install dependencies
2328
run: |
2429
set -Eeuo pipefail
30+
2531
sudo apt-get update && sudo apt-get install --no-install-recommends -y plantuml
26-
python -m pip install gherkin-official==38.0.0 sbdl==1.21.3
32+
python -m pip install sbdl==1.22.7
2733
- name: Build & Validate SBDL model
28-
run: sbdl -m compile test/cpp/features/*.feature > amp-devcontainer.sbdl
29-
- name: Generate SRS document
30-
run: sbdl -m template-fill --template docs/templates/software-requirements-specification.md.j2 amp-devcontainer.sbdl > software-requirements-specification.md
31-
- uses: docker://pandoc/extra:3.9.0.0-ubuntu@sha256:72afa9c8d3300e5f10c9c4330e101725687f2179bffd912fb859c6d2ae85de62
34+
run: sbdl -m compile test/cpp/integration-tests.bats test/cpp/features/*.feature > amp-devcontainer.sbdl
35+
- name: Create document control context
36+
env:
37+
GITHUB_REF_NAME: ${{ github.ref_name }}
38+
IS_RELEASE: ${{ inputs.is-release }}
39+
run: |
40+
set -Eeuo pipefail
41+
42+
cat > document-control.sbdl <<SBDL
43+
#!sbdl
44+
doc_control is aspect {
45+
description is "Document control metadata"
46+
custom:version is "$(jq -r '.["."]' .release-please-manifest.json)"
47+
custom:generated_at is "[@DATE]"
48+
custom:git_sha is "[@GIT_COMMIT_HASH]"
49+
custom:git_ref is "${GITHUB_REF_NAME}"
50+
custom:sbdl_compiler_version is "[@COMPILER_VERSION]"
51+
custom:sbdl_dsl_version is "[@DSL_VERSION]"
52+
custom:is_release is "${IS_RELEASE}"
53+
}
54+
SBDL
55+
- name: 📄 Generate SRS document
56+
run: sbdl -m template-fill --set-config template_extensions_file docs/templates/jinja-extensions.py --template docs/templates/software-requirements-specification.md.j2 amp-devcontainer.sbdl document-control.sbdl > software-requirements-specification.md
57+
- name: 🧪 Generate STP document
58+
run: sbdl -m template-fill --set-config template_extensions_file docs/templates/jinja-extensions.py --template docs/templates/software-test-plan.md.j2 amp-devcontainer.sbdl document-control.sbdl > software-test-plan.md
59+
- name: 🧩 Generate RTM document
60+
run: sbdl -m template-fill --set-config template_extensions_file docs/templates/jinja-extensions.py --template docs/templates/requirements-traceability-matrix.md.j2 amp-devcontainer.sbdl document-control.sbdl > requirements-traceability-matrix.md
61+
- name: 📄 Generate SRS PDF
62+
uses: docker://pandoc/extra:3.9.0.0-ubuntu@sha256:72afa9c8d3300e5f10c9c4330e101725687f2179bffd912fb859c6d2ae85de62
3263
with:
3364
args: --template eisvogel --syntax-highlighting idiomatic --number-sections --output software-requirements-specification.pdf software-requirements-specification.md
65+
- name: 🧪 Generate STP PDF
66+
uses: docker://pandoc/extra:3.9.0.0-ubuntu@sha256:72afa9c8d3300e5f10c9c4330e101725687f2179bffd912fb859c6d2ae85de62
67+
with:
68+
args: --template eisvogel --syntax-highlighting idiomatic --number-sections --output software-test-plan.pdf software-test-plan.md
69+
- name: 🧩 Generate RTM PDF
70+
uses: docker://pandoc/extra:3.9.0.0-ubuntu@sha256:72afa9c8d3300e5f10c9c4330e101725687f2179bffd912fb859c6d2ae85de62
71+
with:
72+
args: --template eisvogel --syntax-highlighting idiomatic --number-sections --output requirements-traceability-matrix.pdf requirements-traceability-matrix.md
3473
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
3574
with:
3675
name: documents
3776
path: |
3877
*.pdf
3978
*.sbdl
79+
!document-control.sbdl
4080
retention-days: 2

docs/templates/jinja-extensions.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from datetime import datetime
2+
3+
def strftime_filter(value, fmt):
4+
if value.endswith('Z'):
5+
value = value[:-1] + '+00:00'
6+
7+
return datetime.fromisoformat(value).strftime(fmt)
8+
9+
filters = { 'strftime': strftime_filter }
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
| Property | Value |
2+
|-------------------|---------------------------------------------------------------------------------------------------------------------------|
3+
| Document version | {{ sbdl['doc_control']['custom:version'] }} |
4+
| Generation date | {{ sbdl['doc_control']['custom:generated_at'] | strftime('%Y-%m-%d') }} |
5+
| Source revision | {{ sbdl['doc_control']['custom:git_sha'] }} |
6+
| Source branch/tag | {{ sbdl['doc_control']['custom:git_ref'] }} |
7+
| Model | SBDL {{ sbdl['doc_control']['custom:sbdl_compiler_version'] }} (DSL {{ sbdl['doc_control']['custom:sbdl_dsl_version'] }}) |
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{% macro reencode(text) -%}
2+
{{ text.encode('utf-8').decode('unicode_escape') }}
3+
{%- endmacro -%}
4+
5+
{%- macro strip_gherkin_prefix(text) -%}
6+
{{ text | replace('Rule: ', '') | replace('Feature: ', '') }}
7+
{%- endmacro -%}
8+
9+
{%- macro strip_bats_syntax(text) -%}
10+
{{ text | replace('@test "', '') | replace('" \{', '') | replace('" {', '') }}
11+
{%- endmacro -%}
12+
13+
{%- macro display_short(text) -%}
14+
{{ text[:60] }}
15+
{%- endmacro %}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
---
2+
title: "Requirements traceability matrix for amp-devcontainer"
3+
author: ["@rjaegers"]
4+
colorlinks: true
5+
date: "{{ sbdl['doc_control']['custom:generated_at'] | strftime('%Y-%m-%d') }}"
6+
keywords: [Traceability, Requirements, RTM, amp-devcontainer]
7+
lang: "en"
8+
titlepage: true
9+
titlepage-color: "0B5ED7"
10+
titlepage-text-color: "FFFFFF"
11+
titlepage-rule-color: "FFFFFF"
12+
titlepage-rule-height: 2
13+
toc: true
14+
toc-own-page: true
15+
header-includes:
16+
- \AtEndDocument{\label{lastpage}}
17+
{%- if sbdl['doc_control']['custom:is_release'] != 'true' %}
18+
watermark: "DRAFT"
19+
{%- endif %}
20+
footer-right: "\\thepage \\hspace{1pt} of \\pageref*{lastpage}"
21+
...
22+
23+
{% import "partials/text-utilities.j2" as utils %}
24+
25+
# Introduction
26+
27+
## Purpose
28+
29+
This document provides a requirements traceability matrix (RTM) for amp-devcontainer. It maps each requirement to its associated verification tests, providing evidence of test coverage across the system.
30+
31+
## Abstract
32+
33+
amp-devcontainer is a set of [devcontainers](https://containers.dev/) tailored towards modern, embedded, software development. This traceability matrix traces requirements from the *Software Requirements Specification (SRS)* to their corresponding verification tests as enumerated in the *Software Test Plan (STP)*.
34+
35+
## Document Control
36+
37+
{% include "partials/document-control.md.j2" with context %}
38+
39+
This document is generated from a formal model defined in [sbdl](https://sbdl.dev) and versioned alongside the source code in Git.
40+
The authoritative source of change history is the [Git log](https://github.com/philips-software/amp-devcontainer/commits/) of the source material from which the model is built.
41+
42+
# Traceability Matrix
43+
{%- for id, item in sbdl.items() %}
44+
{%- if item.type == 'aspect' and 'custom:title' in item %}
45+
46+
## {{ utils.reencode(utils.strip_gherkin_prefix(item['custom:title'])) }}
47+
48+
| Requirement | Test | Status |
49+
|-------------|------|--------|
50+
51+
{%- if 'requirement' in item %}
52+
{%- for req_ref in item.requirement %}
53+
{%- set req = sbdl[req_ref.identifier] %}
54+
{%- set req_name = utils.reencode(utils.strip_gherkin_prefix(req['custom:title'])) | trim %}
55+
56+
{%- set ns_tests = namespace(test_list=[]) -%}
57+
58+
{%- for test_id, test_elem in sbdl.items() -%}
59+
{%- if test_elem.type == 'test' -%}
60+
{%- if 'requirement' in test_elem -%}
61+
{%- for test_req_ref in test_elem.requirement if test_req_ref.identifier == req_ref.identifier -%}
62+
{%- set _ = ns_tests.test_list.append(test_id) -%}
63+
{%- endfor -%}
64+
{%- endif -%}
65+
{%- endif -%}
66+
{%- endfor -%}
67+
68+
{%- if ns_tests.test_list -%}
69+
{%- for test_id in ns_tests.test_list %}
70+
{%- set test_name = utils.strip_bats_syntax(sbdl[test_id]['custom:title']) | trim %}
71+
| {{ utils.display_short(req_name) }} | {{ test_id }}: {{ utils.display_short(test_name) }} | Traced |
72+
{%- endfor -%}
73+
{%- else %}
74+
| {{ utils.display_short(req_name) }} | — | **Not covered** |
75+
{%- endif -%}
76+
77+
{%- endfor %}
78+
{%- endif %}
79+
{%- endif %}
80+
{%- endfor %}
81+
82+
# Coverage Summary
83+
84+
{%- set ns_summary = namespace(total_reqs=0, covered_reqs=0, total_tests=0) -%}
85+
86+
{%- for elem_id, elem in sbdl.items() -%}
87+
{%- if elem.type == 'requirement' -%}
88+
{%- set ns_summary.total_reqs = ns_summary.total_reqs + 1 -%}
89+
90+
{%- set ns_covered = namespace(is_covered=false) -%}
91+
{%- for test_id, test_elem in sbdl.items() -%}
92+
{%- if test_elem.type == 'test' and 'requirement' in test_elem -%}
93+
{%- for req_ref in test_elem.requirement if req_ref.identifier == elem_id -%}
94+
{%- set ns_covered.is_covered = true -%}
95+
{%- endfor -%}
96+
{%- endif -%}
97+
{%- endfor -%}
98+
99+
{%- if ns_covered.is_covered -%}
100+
{%- set ns_summary.covered_reqs = ns_summary.covered_reqs + 1 -%}
101+
{%- endif -%}
102+
103+
{%- endif -%}
104+
105+
{%- if elem.type == 'test' -%}
106+
{%- set ns_summary.total_tests = ns_summary.total_tests + 1 -%}
107+
{%- endif -%}
108+
{%- endfor %}
109+
110+
| Metric | Value |
111+
|---|---|
112+
| Total requirements | {{ ns_summary.total_reqs }} |
113+
| Requirements with tests | {{ ns_summary.covered_reqs }} |
114+
| Requirements without tests | {{ ns_summary.total_reqs - ns_summary.covered_reqs }} |
115+
| Total tests | {{ ns_summary.total_tests }} |

docs/templates/software-requirements-specification.md.j2

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: "Software requirements specification for amp-devcontainer"
33
author: ["@rjaegers"]
44
colorlinks: true
5-
date: "February-2026"
5+
date: "{{ sbdl['doc_control']['custom:generated_at'] | strftime('%Y-%m-%d') }}"
66
keywords: [Software, Requirements, SRS, amp-devcontainer]
77
lang: "en"
88
titlepage: true
@@ -12,14 +12,59 @@ titlepage-rule-color: "FFFFFF"
1212
titlepage-rule-height: 2
1313
toc: true
1414
toc-own-page: true
15+
header-includes:
16+
- \AtEndDocument{\label{lastpage}}
17+
{%- if sbdl['doc_control']['custom:is_release'] != 'true' %}
18+
watermark: "DRAFT"
19+
{%- endif %}
20+
footer-right: "\\thepage \\hspace{1pt} of \\pageref*{lastpage}"
1521
...
1622

23+
{% import "partials/text-utilities.j2" as utils %}
24+
1725
# Introduction
1826

1927
## Purpose
2028

2129
This document describes the software system requirements for amp-devcontainer.
2230

31+
## Scope
32+
33+
This document covers the requirements for the amp-devcontainer project: a set of devcontainers tailored towards modern, embedded, software development.
34+
35+
The following is in scope:
36+
37+
- Container image flavors for C++ and Rust development
38+
- Tooling for compilation, debugging, static and dynamic analysis
39+
- Compatibility with IDEs, container runtimes, and ci/cd systems
40+
- Security properties of released container images
41+
- Maintainability of the container images and their build system
42+
43+
The following is out of scope:
44+
45+
- Application-level software built using the containers
46+
- Deployment of end-user products
47+
- Requirements for third-party tools and dependencies included in the containers
48+
49+
## References
50+
51+
| Identifier | Title |
52+
|------------|------------------------------------------------------------------------------------------------------------------------------------------|
53+
| RFC 2119 | [Key words for use in RFCs to Indicate Requirement Levels](https://www.rfc-editor.org/rfc/rfc2119) |
54+
| OCI | [Open Container Initiative Image Specification](https://github.com/opencontainers/image-spec/blob/main/spec.md) |
55+
| SLSA | [Supply-chain Levels for Software Artifacts v1.0](https://slsa.dev/spec/v1.0/levels) |
56+
| SemVer | [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html) |
57+
| DevC | [Development Containers Specification](https://containers.dev/) |
58+
59+
## Document Control
60+
61+
{% include "partials/document-control.md.j2" with context %}
62+
63+
This document is generated from a formal model defined in [sbdl](https://sbdl.dev) and versioned alongside the source code in Git.
64+
The authoritative source of change history is the [Git log](https://github.com/philips-software/amp-devcontainer/commits/) of the source material from which the model is built.
65+
66+
Changes to requirements are tracked at the individual requirement level through the version control system and are part of the project's standard change control process.
67+
2368
## Definitions of key words
2469

2570
The key words *MUST*, *MUST NOT*, *REQUIRED*, *SHALL*, *SHALL NOT*, *SHOULD*, *SHOULD NOT*, *RECOMMENDED*, *MAY*, and *OPTIONAL* in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119).
@@ -42,30 +87,22 @@ The containers may be used both for local development and continuous integration
4287

4388
# Requirements
4489

45-
{%- macro reencode(text) -%}
46-
{{ text.encode('utf-8').decode('unicode_escape') }}
47-
{%- endmacro -%}
48-
49-
{%- macro strip_gherkin_prefix(text) -%}
50-
{{ text | replace('Rule: ', '') | replace('Feature: ', '') }}
51-
{%- endmacro -%}
52-
53-
{%- for aspect_id, aspect in sbdl.items() %}
54-
{%- if aspect.type == 'aspect' %}
90+
{%- for id, item in sbdl.items() %}
91+
{%- if item.type == 'aspect' and 'custom:title' in item %}
5592

56-
## {{ reencode(strip_gherkin_prefix(aspect['custom:title'])) }}
93+
## {{ utils.reencode(utils.strip_gherkin_prefix(item['custom:title'])) }}
5794

58-
{{ reencode(aspect.description) }}
95+
{{ utils.reencode(item.description) }}
5996

60-
{%- if 'requirement' in aspect %}
61-
{%- for req_ref in aspect.requirement %}
97+
{%- if 'requirement' in item %}
98+
{%- for req_ref in item.requirement %}
6299
{%- set req = sbdl[req_ref.identifier] %}
63100

64-
### {{ req_ref.identifier }}: {{ reencode(strip_gherkin_prefix(req['custom:title'])) }}
101+
### {{ req_ref.identifier }}: {{ utils.reencode(utils.strip_gherkin_prefix(req['custom:title'])) }}
65102

66-
{{ reencode(req.description) }}
103+
{{ utils.reencode(req.description) }}
67104

68-
{{ reencode(req.remark) }}
105+
{{ utils.reencode(req.remark) }}
69106

70107
{%- endfor %}
71108
{%- endif %}

0 commit comments

Comments
 (0)