Skip to content
open-ismsDocs

Search documentation

Find a page by title or topic

Evidence and sign-off

Owners, deadlines, sign-offs, and the append-only audit log behind them.

The claim behind the product is that evidence should accumulate while you operate, rather than be assembled the week before an audit. This page is what that means in tables and columns, so you can judge the claim rather than take it.

Assignment#

A requirement's status row is the unit of work. Someone assigns it to a person, and that assignment records who assigned it and when.

requirement_assignment
  status_id        which requirement, for which company
  user_id          who owns it
  assigned_by      who gave it to them
  assigned_at
  signed_off_at    null until they sign
  signed_off_role  the role they signed in

Sign-off is not a separate approval workflow bolted on afterwards. The assignment is the thing that gets signed, which is why the two live in one row.

Sign-off history and the hash chain#

Every sign-off appends a row to sign_off_history, and those rows are chained.

sign_off_history
  status_id         the requirement being signed off
  version           1, 2, 3 ... per status
  signed_off_by     user id
  signed_off_role
  snapshot          JSONB: the answers, the company profile, the template
                    version, and a reference to every evidence file with its
                    content hash
  checksum          SHA-256 over the above plus the previous checksum
  previous_checksum null on the first entry

The checksum is computed over a canonicalised JSON payload: object keys sorted recursively, dates as ISO strings, arrays left in order. The same input always produces the same hash, and the first entry chains against the literal string GENESIS.

What that buys you: altering a past sign-off, or removing one from the middle, breaks every checksum after it. Recomputing the chain to hide the change requires rewriting every later row.

What it does not buy you: this is a tamper-evident structure, not a signature. It proves the sequence has not been edited by someone who did not also rewrite what follows. It is not an electronic signature under eIDAS, and nothing here should be described as an advanced or qualified one.

Evidence files#

evidence
  requirement_status_id   what it evidences
  file_name, file_type, file_size
  storage_key             the object in S3 or MinIO
  content_hash            SHA-256 of the bytes
  version, previous_version_id
  uploaded_by, uploaded_at
  reviewed_by, reviewed_at, rejection_reason

Files are versioned rather than overwritten, and each version keeps its own hash. Because the sign-off snapshot stores the evidence reference with its content hash, a sign-off points at the exact bytes that were signed. Swapping the file afterwards does not silently change what was approved.

The bytes themselves never pass through the application server. The browser uploads straight to object storage with a presigned URL, which is why Evidence storage is worth reading before you configure anything.

The audit log#

audit_log
  company_id, user_id
  action, entity_type, entity_id
  description
  previous_value, new_value   JSONB before and after
  ip_address, user_agent
  checksum                    SHA-256 of the entry
  created_at

Append-only, and worth being precise about what that means here. Nothing in the application updates or deletes these rows in the course of normal work; the write path only inserts. It is a convention held by the code, not a database grant or a trigger, and a Postgres superuser with a psql prompt can do as they like.

Two paths do write, both on purpose:

  • GDPR erasure. A person's right to erasure reaches audit rows too. The erasure path minimises them rather than pretending the obligation does not exist, and the daily job drops the raw email from erasure records once their three-year window has passed.
  • The development router, which is excluded from the production build.

What an auditor can be shown#

For any requirement: who owned it, when they signed it, in what role, what the answers said at that moment, which documents were attached and the hash of each, and whether anything in that chain has been edited since. Produced from rows that were written while the work happened, which is the only kind of evidence that is cheap to produce and hard to fabricate.