Skip to content
AIAssuranceLabs
Menu

Evidence pack integrity: hash manifests and how a third party verifies a file set

Category Methods

Published AIAssuranceLabs

A hash manifest lists every file in an evidence pack with a content digest, so that a reader who receives the pack later can confirm that no file changed or went missing after the set was frozen. This post gives the manifest structure that step 6 of PR-11-DOC v1.0 produces, the command a third party runs to re-verify it, and the Annex IV points the digest serves. The documentation duty it supports applies from the Annex III application date Deferred.

The method

Art. 11(1) of Regulation (EU) 2024/1689 requires the provider to draw up the technical documentation before the system is placed on the market and to keep it up to date; Art. 18(1) requires the provider to keep that documentation at the disposal of the national competent authorities for ten years after placing on the market or putting into service; Art. 21(1) and Art. 74(12) hand the set to a market surveillance authority on request. Annex IV section 1(a) and 1(c) ask for the system version and the software versions, section 2(g) asks for test logs and test reports dated and signed by the responsible persons, and section 6 asks for the changes made through the lifecycle. Over that period the file set passes between functions, storage systems and readers. A content digest fixes which bytes were the documentation on the day it was frozen, which is what the version fields in the Art. 11 obligation record are compared against. The duty applies from the Annex III application date for a system under Art. 6(2) and from the Annex I date Deferred for a system under Art. 6(1).

PR-11-DOC v1.0 freezes the input set in step 6: each document version in the inventory receives an identifier and a content hash, and every later step works on that frozen set. The obligation to test to evidence traceability matrix carries an input_hash field on every row, and the delivery record in step 45 names the frozen input set identifier next to each section draft. The evidence pack deliverable lists the version and hash of every included file among its sections and a ZIP with a hash manifest among its formats. The manifest is the file that turns the frozen input set into something a receiver can check without access to the provider's systems.

The manifest is a JSON document at the root of the pack. Its top level names the pack, the system and version under review, the frozen input set identifier, the digest algorithm and the file count. Each entry names one file by its path relative to the pack root, with its SHA-256 digest as specified in the published NIST FIPS 180-4, its size in bytes, its version, its source, the Annex IV section it serves and its status from the pack index. The manifest excludes itself; its own digest is written into the delivery record and sent separately from the archive, in the cover message, so that the receiver checks the manifest before checking the files.

Manifest fields and where each one comes from
FieldFilled fromWhat it lets a receiver check
pathPack index, one row per itemThe file is present at the named location
sha256, bytesFreeze run, step 6 of the protocol; copied, never recomputed at packingThe bytes are the frozen bytes
versionAnnex IV section 1(a) and 1(c); the test report register from step 29The file is the version the documentation names
sourceProtocol output identifier or the provider function that authored the itemWhich record produced the file and which signature section 2(g) asks for
annex_iv_sectionAnnex IV gap table, step 10Which sub-point the file supplies
statusEvidence pack index: present, partial, gapWhether a missing file is a gap already declared or a loss in transit

The example below is synthetic; the system, the paths, the digests and the sizes belong to no product.

{
  "_comment": "Synthetic example: manifest.json for demo-system 2.4.0. No real system, file or digest.",
  "pack_id": "EP-0007",
  "system": "demo-system",
  "version_under_review": "2.4.0",
  "frozen_input_set": "FIS-0007",
  "algorithm": "sha256",
  "file_count": 3,
  "files": [
    {
      "path": "annex-iv/01-general-description.md",
      "sha256": "9f2c1e0b4a6d7c8e5b3a2f1d0c9e8b7a6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c",
      "bytes": 48213,
      "version": "0.4",
      "source": "provider documentation function",
      "annex_iv_section": "1(a) to 1(h)",
      "status": "present"
    },
    {
      "path": "evidence/TR-0012-robustness-test-report.pdf",
      "sha256": "3b7d9e1f5a2c4e6b8d0f2a4c6e8b0d2f4a6c8e0b2d4f6a8c0e2b4d6f8a0c2e4b",
      "bytes": 1120544,
      "version": "1.0",
      "source": "PR-15-ROB output",
      "annex_iv_section": "2(g)",
      "status": "present"
    },
    {
      "path": "annex-iv/09-post-market-monitoring-plan.md",
      "sha256": null,
      "bytes": 0,
      "version": null,
      "source": "provider QMS function",
      "annex_iv_section": "9",
      "status": "gap: PR-17-QMS step 27 produces the template"
    }
  ]
}

A third party verifies the pack in three commands. First, sha256sum manifest.json on the unpacked archive, compared by eye or by diff against the digest received in the cover message. Second, the conversion of the manifest into the check format that the GNU coreutils manual specifies, one line per file with the digest, two spaces and the path: jq -r '.files[] | select(.sha256 != null) | "\(.sha256) \(.path)"' manifest.json > SHA256SUMS. Third, sha256sum --check --strict SHA256SUMS, which prints one line per file and exits with status 0 only when every file is present and matches; a changed file prints FAILED, a missing file prints an open error, and --strict also fails on a malformed line. On macOS the third command is shasum -a 256 --check --strict SHA256SUMS. The receiver keeps the printed output with the pack as the record of the check.

Two pitfalls sit outside the tool documentation. The digest in the manifest is copied from the freeze run and never recomputed when the archive is built; a manifest regenerated at packing time hashes whatever the files have become and passes a check it should fail. And a hash fixes bytes, not content: a PDF exported a second time from the same source is a different file with a different digest, so the version field and the digest travel together and a version change without a digest change, or the reverse, is a finding in its own right.

What the test produces

The pack index and the manifest are two views of the same rows: the index tells a reader what each item is and which Annex IV sub-point it serves, and the manifest tells the reader whether the item in hand is the item that was frozen. Each row of the traceability matrix from PR-11-DOC v1.0 ties a result to the input_hash of the document it was read from, so a receiver who re-verifies the manifest also knows which bytes every consistency result rests on. These are outputs of the technical documentation and evidence-pack preparation service, within the limits stated at what we cannot do, and the pack is the set the market surveillance information request scenario assembles.

A matching digest shows identity and completeness. It does not show that a value inside a file is right; the protocol page states that a test report the provider produced without an independent test is taken as given, and the declared values themselves come from the provider's testing under Art. 15. It does not replace the date and signature that Annex IV section 2(g) asks for on every test log and report. And it does not cover the document control that prEN 18286Published, published, sets out in its clauses on document and record control: version, owner and update trigger per section, which step 42 of the protocol records separately.

When the set changes, the frozen result ends. Art. 11(1) requires the documentation to be kept up to date, and the protocol page records that a change after step 6 is outside the result. A new freeze produces a new input set identifier and a new manifest under a new pack identifier; the previous manifest is kept, because the lifecycle change record under Annex IV section 6 is checked against the version history and the versions the test reports cover, and two manifests with different digests for the same path are the evidence of that history.

What to do now

  1. Freeze the file set with one digest per file before assembly begins, as step 6 of PR-11-DOC v1.0 asks and as the Annex IV assembly guide shows with sha256sum.
  2. Write the manifest with the fields in the table above, exclude the manifest from its own file list, and record its digest in the delivery record.
  3. Send the manifest digest in the cover message, separately from the archive, on every handover under Art. 21(1) or Art. 74(12).
  4. Run the check on receipt and before every onward handover, and file the printed output with the pack.
  5. After any change to the system, the documentation or the instructions for use, freeze again under a new pack identifier and record the change under Annex IV section 6.

Related pages

The obligation, protocol, guide, service and hubs this post rests on.

Obligations
Protocols
Guides
Services
ICP hubs

Every inquiry receives a reply within 24 hours. Scope and availability are discussed per request.

Ask about scopehello@aiassurancelabs.com