Configuration¶
TL;DR
Two files decide everything qmstrace enforces: the project's trace schema
(qmstrace.schema.toml, in the ledger repo) and each product repo's trace configuration
(qmstrace.toml, at the repo's root). No rule hides in the tool, and an unknown key anywhere
is an error — a mistyped key would be a rule nobody can see.
Three levels, three owners¶
| Level | File | Owner | Says |
|---|---|---|---|
| profile | not a file yet | the standard | the work products and their states — ISO/IEC 29110 first |
| trace schema | qmstrace.schema.toml |
the project, in its ledger repo | item types and their required fields, link types, coverage rules, where the agreement and the change requests are, which tags are baselines, the project's product repos |
| trace configuration | qmstrace.toml |
one product repo | where the schema is, the repo's subjects, which item types it declares, their ID patterns and the files to read |
A product repo holds the project's trace schema in one of three forms:
- by path —
[schema] path = "…", when the ledger repo sits beside it; - a verified copy — the schema's content under
[schema], with a digest that is checked, so a copy edited by hand is refused; - its own — the schema's content with no digest, for a repo with no ledger.
A verified copy is never written by hand: the project side generates it in the ledger with
qmstrace ledger copy-schema, and it sits between two marker comments.
While the markers stand, [schema] must carry its digest.
The files below are the annotated examples qmstrace's own tests read; each says, in its comments, what belongs in it and what does not.
A product repo's trace configuration¶
# This product repo's TRACE CONFIGURATION — what is local to it. The project's trace schema
# (link types, coverage rules, required fields, the agreement, change requests, baselines) is
# the project's, not this repo's: `qmstrace rules` prints what is in force and whose it is.
#
# What does NOT belong here: rules. If a rule seems wrong, tell the project side.
version = 1
ledger = "../ledger" # optional: where the agreement and the change requests
# are. Without it they are reported as NOT VERIFIED.
[schema] # form 1 — by path (repos side by side, a test bed)
path = "../ledger/qmstrace.schema.toml"
[subjects.service] # a named body of requirements; a baseline freezes a subject
key = "SVC" # the key inside its item IDs
[items.requirement]
extractor = "markdown"
id_pattern = '^REQ-SVC-\d{2}$'
files = ["docs/req/*-srs.md"]
[items.design_element]
extractor = "markdown"
id_pattern = '^DSN-SVC-\d{2}$'
files = ["docs/design/*.md"]
[items.component] # ID = the module's path
extractor = "python-module"
files = ["src/**/*.py"]
ignore = ["**/__init__.py"]
[items.automated_test] # ID = the test function's node id
extractor = "pytest"
files = ["tests/**/test_*.py"]
A C# repo's trace configuration¶
A C# product repo reads its components with csharp-file and its xUnit tests with xunit
(ADR-0004); see trace a C# repo.
# This product repo's TRACE CONFIGURATION — a C# agent (ADR-0004). It holds code and automated
# tests only: the requirements, the design and the test cases are in the project's ledger.
version = 1
ledger = "../ledger"
[schema] # by path, as repos side by side read it
path = "../ledger/qmstrace.schema.toml"
[items.component] # ID = the file's path
extractor = "csharp-file"
files = ["src/**/*.cs"]
[items.automated_test] # ID = path::Class::Method; the probes are not traced
extractor = "xunit"
files = ["tests/Agent.Tests/**/*Tests.cs"]
| Extractor | Reads | An item's ID |
|---|---|---|
markdown |
headings that start with an ID, and their sections | authored, matching id_pattern |
python-module |
a module's docstring | the module's path |
pytest |
test functions and their verifies marks |
the test's node ID |
csharp-file |
a .cs file's first type's /// comment |
the file's path |
xunit |
[Fact] and [Theory] methods and their DisplayName |
path::Class::Method |
The project's trace schema¶
# The project's TRACE SCHEMA — one project's declaration, authored here in the ledger repo and
# shared by every product repo of the project. A product repo either points at this file or
# carries a generated, verified copy of it.
#
# What does NOT belong here: which files a repo scans, its ID patterns, its subjects — those
# are local to a product repo and live in its own qmstrace.toml.
#
# Paths in this file are relative to the ledger's root.
version = 1
project = "MINI"
[repos] # the product repos of this project
service = "../service"
client = "../client"
# ── item types the project knows; a repo declares the ones it has ─────────────────────────
[item_types.requirement]
required = ["Category", "Origin", "Verify-by"]
[item_types.requirement.values] # allowed values of a field
Category = ["functional-suitability", "performance-efficiency", "compatibility", "usability", "reliability",
"maintainability", "portability", "security", "design-constraint", "legal-regulatory"]
"Verify-by" = ["test", "inspection", "analysis"]
[item_types.design_element]
[item_types.component]
[item_types.automated_test]
levels = ["unit", "integration", "system"]
# ── link types — a field whose name is a link marker IS the link; all are many-to-many ────
[links]
Origin = { from = ["requirement"], to = ["origin_clause"] }
Satisfies = { from = ["design_element"], to = ["requirement"] }
Implements = { from = ["component"], to = ["design_element"] }
verifies = { from = ["automated_test"], to = ["requirement", "design_element"] }
Assumes = { from = ["requirement", "design_element"], to = ["requirement", "design_element"], coverage = false }
# coverage = false: the link carries suspicion and impact, and no coverage rule may need it
# ── coverage rules — an unmet rule is a gap ───────────────────────────────────────────────
[[rules]]
code = "G01"
item = "requirement"
needs = "incoming:Satisfies"
text = "no design element satisfies it yet."
[[rules]]
code = "G02"
item = "requirement"
where = { "Verify-by" = "test" }
needs = "incoming:verifies"
text = "no test verifies it yet."
[[rules]]
code = "G03"
item = "design_element"
needs = "incoming:Implements"
text = "no component implements it yet."
[[rules]]
code = "G04"
item = "design_element"
needs = "incoming:verifies"
text = "no test verifies it yet."
[[rules]]
code = "G05"
item = "design_element"
needs = "outgoing:Satisfies"
text = "satisfies no requirement — every design element points back to one."
[[rules]]
code = "G06"
item = "component"
needs = "outgoing:Implements"
text = "implements no design element — every component points back to one."
[[rules]]
code = "G07"
item = "automated_test"
needs = "outgoing:verifies"
text = "verifies nothing."
# ── what is declared outside any product repo: cited there, declared here ─────────────────
[externals.agreement]
item_type = "origin_clause"
prefix = "AGR-"
source = "agreement.md"
declared_as = '^\s*[-*]\s+\*\*(AGR-\d+)\*\*'
[change_requests]
prefix = "CR-"
source = "change-requests" # one file per request: CR-NNN.md
accepted = "accepted"
# ── baselines — tags a project already cuts, recognised by pattern (never imposed) ────────
[baselines.requirements] # freezes one subject's requirements
tag = '^baseline/(?P<subject>[a-z0-9-]+)/requirements/(?P<n>\d+)$'
covers = ["requirement"]
[baselines.product] # a release tag IS the product baseline
tag = '^v\d+\.\d+\.\d+$'
covers = ["requirement", "design_element"]
subject = "*"
The agreement, and other externals¶
An [externals.<name>] table says where IDs declared outside every traced item are declared:
prefix, the text each such ID starts with; source, one file in the ledger; declared_as,
a pattern whose one group is the ID. A citation is judged against the external whose prefix
it starts with, so two externals whose prefixes overlap — equal, or one starting the other, as
AC- and AC-E1- do — are refused: give each engagement its own, such as AC-E1- and
AC-E2-. A source holding *, ? or [ is a pattern and is refused too; a family kept in
several files takes one table per file. A source that is simply not there — the ledger not
checked out beside the repo — is not refused: each citation of it is reported not verified.
The fields the tables carry¶
A schema may name requirement fields for the traceability matrix to carry beside the chain — a priority, say — in the order given:
Each must be a field the requirement type declares, under required or values; a link
marker (Origin), a field listed twice, or a name a derived column already has is refused.
Where automated tests do not sit in level folders, a test case may name the level instead —
level names the test-case field that holds it, for the matrix's verdict columns:
[tables.matrix]
level = "Level" # a field the test-case type declares
``` The
section is part of the trace schema, so setting it changes the schema's digest: re-copy it
into every product repo that carries a verified copy (`qmstrace ledger copy-schema`) before
`ledger check` accepts them again. [The table format](table-format.md) says what the tables
hold.
## A product repo carrying the copy
The service repo above, after `qmstrace ledger copy-schema` in the ledger — generated by the
tests from the two files above, so it cannot drift from what the command writes:
``` toml
# This product repo's TRACE CONFIGURATION — what is local to it. The project's trace schema
# (link types, coverage rules, required fields, the agreement, change requests, baselines) is
# the project's, not this repo's: `qmstrace rules` prints what is in force and whose it is.
#
# What does NOT belong here: rules. If a rule seems wrong, tell the project side.
version = 1
ledger = "../ledger" # optional: where the agreement and the change requests
# are. Without it they are reported as NOT VERIFIED.
# qmstrace:begin — the MINI trace schema, generated in its ledger by `qmstrace ledger copy-schema`; edit it there, never here
[schema]
digest = "sha256:b4129c158e6fdaa3b314b6b5e4d675085ec385e094fd57048bf9cd7796b3a3d1"
# The project's TRACE SCHEMA — one project's declaration, authored here in the ledger repo and
# shared by every product repo of the project. A product repo either points at this file or
# carries a generated, verified copy of it.
#
# What does NOT belong here: which files a repo scans, its ID patterns, its subjects — those
# are local to a product repo and live in its own qmstrace.toml.
#
# Paths in this file are relative to the ledger's root.
version = 1
project = "MINI"
[schema.repos] # the product repos of this project
service = "../service"
client = "../client"
# ── item types the project knows; a repo declares the ones it has ─────────────────────────
[schema.item_types.requirement]
required = ["Category", "Origin", "Verify-by"]
[schema.item_types.requirement.values] # allowed values of a field
Category = ["functional-suitability", "performance-efficiency", "compatibility", "usability", "reliability",
"maintainability", "portability", "security", "design-constraint", "legal-regulatory"]
"Verify-by" = ["test", "inspection", "analysis"]
[schema.item_types.design_element]
[schema.item_types.component]
[schema.item_types.automated_test]
levels = ["unit", "integration", "system"]
# ── link types — a field whose name is a link marker IS the link; all are many-to-many ────
[schema.links]
Origin = { from = ["requirement"], to = ["origin_clause"] }
Satisfies = { from = ["design_element"], to = ["requirement"] }
Implements = { from = ["component"], to = ["design_element"] }
verifies = { from = ["automated_test"], to = ["requirement", "design_element"] }
Assumes = { from = ["requirement", "design_element"], to = ["requirement", "design_element"], coverage = false }
# coverage = false: the link carries suspicion and impact, and no coverage rule may need it
# ── coverage rules — an unmet rule is a gap ───────────────────────────────────────────────
[[schema.rules]]
code = "G01"
item = "requirement"
needs = "incoming:Satisfies"
text = "no design element satisfies it yet."
[[schema.rules]]
code = "G02"
item = "requirement"
where = { "Verify-by" = "test" }
needs = "incoming:verifies"
text = "no test verifies it yet."
[[schema.rules]]
code = "G03"
item = "design_element"
needs = "incoming:Implements"
text = "no component implements it yet."
[[schema.rules]]
code = "G04"
item = "design_element"
needs = "incoming:verifies"
text = "no test verifies it yet."
[[schema.rules]]
code = "G05"
item = "design_element"
needs = "outgoing:Satisfies"
text = "satisfies no requirement — every design element points back to one."
[[schema.rules]]
code = "G06"
item = "component"
needs = "outgoing:Implements"
text = "implements no design element — every component points back to one."
[[schema.rules]]
code = "G07"
item = "automated_test"
needs = "outgoing:verifies"
text = "verifies nothing."
# ── what is declared outside any product repo: cited there, declared here ─────────────────
[schema.externals.agreement]
item_type = "origin_clause"
prefix = "AGR-"
source = "agreement.md"
declared_as = '^\s*[-*]\s+\*\*(AGR-\d+)\*\*'
[schema.change_requests]
prefix = "CR-"
source = "change-requests" # one file per request: CR-NNN.md
accepted = "accepted"
# ── baselines — tags a project already cuts, recognised by pattern (never imposed) ────────
[schema.baselines.requirements] # freezes one subject's requirements
tag = '^baseline/(?P<subject>[a-z0-9-]+)/requirements/(?P<n>\d+)$'
covers = ["requirement"]
[schema.baselines.product] # a release tag IS the product baseline
tag = '^v\d+\.\d+\.\d+$'
covers = ["requirement", "design_element"]
subject = "*"
# qmstrace:end
[subjects.service] # a named body of requirements; a baseline freezes a subject
key = "SVC" # the key inside its item IDs
[items.requirement]
extractor = "markdown"
id_pattern = '^REQ-SVC-\d{2}$'
files = ["docs/req/*-srs.md"]
[items.design_element]
extractor = "markdown"
id_pattern = '^DSN-SVC-\d{2}$'
files = ["docs/design/*.md"]
[items.component] # ID = the module's path
extractor = "python-module"
files = ["src/**/*.py"]
ignore = ["**/__init__.py"]
[items.automated_test] # ID = the test function's node id
extractor = "pytest"
files = ["tests/**/test_*.py"]
A ledger that holds the documents¶
In the layout where the ledger holds the requirements and the design, the ledger is traced
too. Its own qmstrace.toml reads the schema beside it by path — never a copy — and the
schema lists the ledger under [repos] as ".":
# This ledger's own TRACE CONFIGURATION: it holds the project's documents, so it is traced like
# any repo of the project — and reads the schema it holds by path, never as a copy.
#
# What does NOT belong here: rules. They are in qmstrace.schema.toml, beside this file.
version = 1
ledger = "." # the agreement and the change requests are here
[schema]
path = "qmstrace.schema.toml"
[subjects.service] # a named body of requirements; a baseline freezes a subject
key = "SVC"
[subjects.client]
key = "CLT"
[items.requirement]
extractor = "markdown"
id_pattern = '^REQ-(SVC|CLT)-\d{2}$'
files = ["docs/req/*-srs.md"]
[items.design_element]
extractor = "markdown"
id_pattern = '^DSN-(SVC|CLT)-\d{2}$'
files = ["docs/design/*.md"]
The product repos then declare only what they hold:
# This product repo's TRACE CONFIGURATION. It holds code and tests only: the requirements and
# the design it implements are in the project's ledger, and its markers cite them by ID.
#
# What does NOT belong here: rules, or the documents. Both are the ledger's.
version = 1
ledger = "../ledger" # where the agreement, the change requests and the documents are
[schema] # by path, as repos side by side read it
path = "../ledger/qmstrace.schema.toml"
[items.component] # ID = the module's path
extractor = "python-module"
files = ["src/**/*.py"]
ignore = ["**/__init__.py"]
[items.automated_test] # ID = the test function's node id
extractor = "pytest"
files = ["tests/**/test_*.py"]
A repo on its own¶
# A repo on its own — no ledger, no sibling. The trace schema is the repo's own: it sits under
# [schema] with no digest, and is edited here like any other file. This is what adopting
# qmstrace in one existing repo looks like.
version = 1
[schema]
version = 1
project = "SOLO"
[schema.item_types.requirement]
required = ["Origin"]
[schema.item_types.automated_test]
levels = ["unit"]
[schema.links]
Origin = { from = ["requirement"], to = ["origin_clause"] }
verifies = { from = ["automated_test"], to = ["requirement"] }
[[schema.rules]]
code = "G02"
item = "requirement"
needs = "incoming:verifies"
text = "no test verifies it yet."
[schema.externals.brief]
item_type = "origin_clause"
prefix = "BRF-"
source = "docs/brief.md"
declared_as = '^\s*[-*]\s+\*\*(BRF-\d+)\*\*'
[schema.baselines.release]
tag = '^v\d+\.\d+\.\d+$'
covers = ["requirement"]
subject = "*"
[subjects.app]
key = "APP"
[items.requirement]
extractor = "markdown"
id_pattern = '^REQ-APP-\d{2}$'
files = ["docs/req/*-srs.md"]
[items.automated_test]
extractor = "pytest"
files = ["tests/**/test_*.py"]
The comments point at qmstrace rules, which prints the rules in force
and says which part is the project's and which the repo's. For why IDs have one fixed shape, and
what can still change after a baseline, see IDs and file patterns.