How to take MedHarness into a project, and where each part is documented.
| You want to | Read |
|---|---|
| Add the CI gates, or cut a release | CI and releases |
| Let an agent plan and write a change, with your own agent or unattended in CI | The AI-assisted CR workflow |
| Keep the SOUP register, an SBOM and vulnerability checks | SOUP, SBOM and vulnerabilities |
| Change a default, or add a type or a relationship | Configuration |
| Look up a command, its options or its answer | interface.md |
Run medharness init as in the README. Then replace:
| Replace | Why |
|---|---|
DHF/items/ |
Delete the sample YAML and add your own, or keep them while you learn the schema |
DHF/config/global.yaml |
Check the project name. Anything else you set here overrides a default — see Changing the defaults |
AGENTS.md |
Describe the product, so every coding agent — and the AI stages — reason about your domain |
CI is the one piece you add yourself, and it is the whole deployment.
MedHarness stores DHF content as YAML items, one file per record. The types and
the links between them are listed in the DHF/README.md that init writes.
Artifacts from common sources map directly to item types. A requirements spreadsheet becomes SRS and SYS items — one row per item, with title and content fields. A risk register becomes RISK items paired with RCM items (each RCM carries a mitigates field pointing to the RISK ID it controls). A SOUP list becomes SOUP items with name, version, and purpose fields.
What you do not need to migrate: test code (it stays in pytest, linked to DHF items via medharness.links annotations in JUnit output), generated documents (build release renders them from the items), and CI scripts (start from the recipe in Setting up CI). Migration is writing YAML files. The schema is self-documenting — look at the sample items from init to see every field and its expected values.
Traceability links between items are typed fields on child items (derives_from, satisfies, implements, mitigates, etc.). Run medharness --dhf DHF verify dhf at any point to check link integrity. The validator names the exact item, field, and target for every broken link.
Which findings block a build and which only warn is in
interface.md. While backfilling, leave
--strict off: coverage gaps then warn, and schema, required links
and dangling links still fail.
A DHF that kept change requests before MedHarness's closure criteria existed has two things to settle once, before its first release.
A status that is no state of the type. A CR written as implementing fails verify dhf: a CR's states are new, design, develop, completed, rejected and cancelled. Set each to what it really is — develop for work in flight, completed or cancelled for work that finished or stopped:
medharness --dhf DHF item update CR-004 --data '{"status": "develop"}'Completed CRs that never recorded their closure. build release re-runs verify completion for every completed CR it includes, by default every completed CR not yet in a release, so CRs closed before implementation_notes, affected_items and triage_result existed fail it. Do not backfill those fields: a triage approval written years later is not a record of anything. Baseline the history instead. A REL item that lists those CRs in included_items takes them out of the default, and the decision is a reviewable change in your DHF:
medharness --dhf DHF item create --type REL --data '{"title": "Baseline of change requests completed before closure records", "version": "baseline", "included_items": ["CR-003", "CR-005"], "release_notes": "Completed before the closure criteria existed; accepted as they are."}'CRs completed from then on carry the closure fields, and the next build release includes only them.
MedHarness supports TDD at the DHF level. The idea is that test intent is expressed in the design, not retrofitted after code is written.
Each requirement (CRS, SYS, SRS) has a testing field where you write numbered test points at design time:
id: SRS-012
title: "Password must be at least 12 characters"
content: "The system shall reject passwords shorter than 12 characters."
testing: |
T1: Given a password of 11 characters, registration returns a validation error.
T2: Given a password of exactly 12 characters, registration succeeds.
T3: Given a password of 13+ characters, registration succeeds.During implementation, tests declare which points they cover:
# Python — pytest marker
@pytest.mark.dhf_links("SRS-012")
@pytest.mark.dhf_testing("T1", "T2", "T3")
def test_password_length_validation():
...// JS/Jest — embedded tag in the test name
test("rejects short password @links:SRS-012 @testing:T1", () => { ... });
test("accepts 12-char password @links:SRS-012 @testing:T2", () => { ... });Under pytest with --junit-xml, the markers become JUnit properties (medharness.links, medharness.testing); other runners carry the tags in the test name. The CI gate reads either:
medharness --dhf DHF verify tests --junit test-resultsThis exits non-zero if a requirement lacks coverage or if any declared test point has no covering test, making gaps in test coverage visible before merge. Combined with verify dhf (schema and links), it enforces that requirements are linked and verified before merge.
Start with the AI development, and add the checks that make it trustworthy. Each step is useful on its own.
| Step | Add | Needs |
|---|---|---|
| 1 | Your coding agent follows the process: init writes AGENTS.md, and the agent runs build plan --prompt and build code --prompt |
A coding agent you already use. No key, nothing in CI |
| 2 | verify dhf as a PR gate |
Nothing — works on day one. It is what checks the design the agent wrote |
| 3 | Unattended AI in CI: build plan and build code from an issue |
An AI key, an ephemeral runner, and branch protection that requires an approving review. Put it behind step 2 |
| 4 | verify tests |
Test annotations in JUnit output |
| 5 | build soup, verify soup |
A dependency manifest |
| 6 | build release on version tags |
Steps 2, 4 and 5 passing |
Steps 1 and 3 are optional in the strong sense: a team that writes its DHF by hand starts at step 2 and loses nothing the standard asks for.
Every MedHarness release runs its full test suite before it is published, against the exact dependency versions in the release's lockfile. The GitHub Release attaches three files that record that run:
| File | What it is |
|---|---|
medharness-<version>-tested-with.txt |
A pip constraints file: every runtime and docs dependency, pinned to the version the suite passed with |
medharness-<version>-junit.xml |
The JUnit results of the full suite |
medharness-<version>-test-summary.txt |
The pytest summary line, Python version, runner image and commit |
To install that configuration, pass the constraints file to pip:
pip install "medharness[docs]==<version>" -c medharness-<version>-tested-with.txtThe == pins MedHarness itself; the constraints pin everything it pulls in, so an install done later resolves to the versions the suite ran against rather than the newest ones on PyPI. The JUnit file and the test summary are the evidence of what that configuration passed. Keep them with your release records.
The release's evidence shows the suite passed where MedHarness was built. To show the copy you installed behaves the same in your environment, run:
medharness build validation --out-dir validationIt runs the validation set that ships in the package against a scratch project in a temporary directory, never your DHF, and writes validation/validation-report.html (--doc-format pdf with the docs extra). The report gives the result, passed, failed or incomplete; the installed files checked against the release; each installed dependency beside the version the release was tested with; your platform, Python, git and ssh-keygen; and, for each high-risk requirement of MedHarness's own DHF, every test point with the case that exercised it and its result. It ends with blank fields for who performed and reviewed the validation. The command exits 0 only when the result is passed. A run takes about half a minute on four cores: --jobs N sets how many cases run at once (default: the number of CPUs, at most 8), and --jobs 1 runs them one after another, with the same result.
- A dependency at another version is listed as a deviation, not a failure. Whether it matters is your assessment; the results show whether the tool behaves with it.
- A missing
ssh-keygenleaves the signing points not run, and the resultincomplete: a validation that skipped them has not validated signing. - An install from a source checkout or in editable mode carries no release record and cannot be checked file by file, so its result is
incomplete. Validate the wheel you install from PyPI.
Run it where MedHarness makes the decisions you rely on: on the CI runner that runs your gates and build release, or in the same image, and on any machine where people run gates or sign releases. Run it when you adopt a version, again after every upgrade, and when that environment changes in a way that matters, such as a new operating system or Python version. The changelog and the requirement IDs in the report show which gates changed. File the report with the release's JUnit and test summary: each point in your report has the same requirement ID, test point and case name as in the vendor's run. The decision that MedHarness is fit for your intended use, and the signature, are yours (ADR-005).