Changelog
All notable changes to Countersign are recorded in this file. The format follows Keep a Changelog, and the project adheres to Semantic Versioning.
For a system whose state is its audit trail, "compatible" has a precise meaning here: a minor or patch release
replays every audit trail written by an earlier release of the same major version with the same result. Each release
entry says so explicitly, and names changes to db/postgres.sql and to environment variables. Before upgrading,
follow the upgrade procedure.
[Unreleased]
Added
- Hierarchies of different depths. A parent reference may be optional (records without one stand at the top level)
and may point at its own record type, so records nest to any depth (units in units, locations in locations). The
engine refuses a record becoming its own ancestor, on create, edit, approval and when an approval is revoked,
counting pending changes. Options are inherited down every level, nearest first. The tree, the path above a record,
a list of what sits directly under it, and a parent picker that never offers the record itself or anything under it.
A fourth demo domain, the organisation chart (
preset:org), shows it. - Changing the configuration in the application. An editor for record types, fields, hierarchy, roles and option
groups (or the document as JSON, or a file), checked while it is edited, with every problem next to its cause and a
review of the changes and of their effect on existing records. New commands
propose-config,approve-config,reject-configandwithdraw-config: one person with theconfigurepermission proposes, a different one approves (a signature, with a recent sign-in on a server) or rejects; the proposer can withdraw. The proposal is shown on the configuration page and in the inbox, and reported by/api/healthasconfigProposal. - Protection of existing records. A proposal is refused, naming the records, while a record in use would lose data or hold a value the new configuration refuses (removed field or choice, narrowed range or pattern, new uniqueness, dropped type, changed prefix, type or reference target). A field that becomes required leaves existing records in effect and marks them until they are completed.
- Command line:
config check,config propose,config approve,config reject,config withdraw. - A narrow-screen layout of the application: the tree as a drawer behind "Records", the top bar on two rows.
- A second System Owner in every demo domain, so that the configuration flow can be tried.
- Migration assistant (
npm run migrate,docs/migration-assistant.md): surveys the repository and database of an application that Countersign replaces and writes a migration guide with a parity and gap analysis; drafts the configuration, the mapping, the export plan and the environment; exports the old data read-only with a manifest of queries and checksums; rehearses the import in a scratch store; compares every imported field with its source; and keeps a hash-chained journal of the steps. With a skill for coding agents (.claude/skills/migrate-to-countersign) andAGENTS.md. Requirements FR-MIG-09 to FR-MIG-14. - Documentation site: full-text search across every document (press
/or Ctrl/Cmd+K), an "On this page" list beside the article that follows the reader, and diagrams (Mermaid blocks, which GitHub also draws) in place of the text drawings, with new ones for the configuration: reference fields and the tree, option resolution, roles, and the path of a configuration change. New dependency:mermaid, loaded only on pages that contain a diagram.
Changed
config applyruns the same check against existing records and refuses a change that fails it;--forcerestores the earlier behaviour. Theconfigurecommand itself is unchanged, and audit trails written by 1.0.0 replay to the same hashes (a test replays one).checkConfig: "a parent or group reference must be required" became "a group reference must be required"; a parent reference to its own type must be optional.- Record data may carry an empty leftover of a field that the configuration no longer has; a value in such a field is still refused.
- The top bar shortens a long domain name instead of growing wider than the window.
[1.0.0] - 2026-11-11
Initial public release.
Added
Records and workflow
- Record types, fields, hierarchy and roles defined by a configuration document, without programming: eight field types (text, long text, number, integer, yes/no, date, selection, reference) with required, unique, range, pattern and choice checks.
- References with three roles: parent (shapes the tree), group (a grouping across parents) and link.
- The four-eyes workflow Created → Confirmed → Approved: a change takes effect only when a second, authorised person approves it; nobody approves a version they wrote or confirmed.
- Versions: an approved version is never altered; a change opens a new version while the approved one stays in effect. Step back with a reason, retire and remove with a reason and approval.
- Permissions per role for writing and approving, per record type, for option definitions, and for option values per level.
Options
- Option definitions (yes/no, number with unit and range, text, selection) and option values that can be set at any level of the hierarchy.
- Inheritance along the configured hierarchy with a defined resolution order; the effective value, where it comes from, what it overrides and what is waiting for approval are shown for every record.
- Protection against stranding values when a definition changes.
Audit trail and integrity
- One audit entry per command: who, when, what changed, why, how the person was authenticated.
- An append-only trail linked by a SHA-256 hash chain; the trail is the system of record and the state is its replay.
- Verification by replay: every stored command is re-run and compared, in the server, in the browser and from the
command line (
verify, with--expect-headand--expect-entryfor externally anchored hashes). - On an integrity fault the server keeps serving reads up to the fault and refuses writes.
- The configuration is part of the trail: every configuration change is an entry with reason and reviewer, and old entries replay under the configuration in force at their time.
Two ways to run
- Demo: no environment variables, no database, no sign-in; each browser keeps its own changes on top of a deterministic demo data set, with three demo domains (vehicle fleet, housing portfolio, hospital care settings).
- Server: a shared store and identities from an identity provider.
Storage
- PostgreSQL (schema in
db/postgres.sql, triggers that make the table append-only and refuse entries that do not continue the chain, a reporting view), SQLite (built into Node.js), a JSON-lines file, and memory. - Safe concurrent writes from several servers on PostgreSQL.
- A small
Storeinterface and conformance tests for further adapters.
Sign-in and signatures
- Microsoft Entra ID and generic OpenID Connect (authorization code flow with PKCE), with roles from app roles or groups and an optional role map.
- Sign-in through an authenticating reverse proxy with a shared secret.
- Demo sign-in for evaluation, with the presets' people or a list of your own (
COUNTERSIGN_DEMO_USERS). - Stateless sessions in a signed cookie.
- Electronic signatures: an approval records the signer, the time, the stated meaning and the authentication, and
requires a recent sign-in (
COUNTERSIGN_SIGN_MAX_AGE_MINUTES), requested from the provider when needed.
Operations
- Command-line tool:
init,status,verify,config validate | show | apply,import,export. - Health endpoint with store, head hash, configuration fingerprint and integrity state.
- Exports of the audit trail (CSV, JSON, JSON lines), the records and the configuration.
- HTTP API (
/api/bootstrap,/api/entries,/api/act,/api/health,/auth/*) with CSRF protection, and a plain Node server for it (scripts/api-server.ts). - Dockerfile and a docker compose file for evaluation with PostgreSQL 16.
Migration
- Import from another system from CSV or JSON files with a mapping document: value maps, date formats, decimal separators, yes/no texts, constants, references resolved by the old identifiers, option definitions and values.
- Two modes: records arrive as approved in the old system, marked with their origin, or as confirmed and awaiting approval.
- A rehearsal before every run, a reconciliation report with source file checksums, a cross-reference of old and new identifiers, and repeatable runs in approved mode.
User interface
- The record tree, record pages with versions, differences and the record's own history, an inbox of changes waiting for the signed-in person, the option catalogue, the full audit trail with filters and export, an integrity page, and a read-only configuration page.
Documentation and validation
- User guide, developer and operations documentation, API reference, security model.
- A validation pack: requirements, functional specification, risk assessment, IQ/OQ/PQ protocols, and a traceability
matrix generated from the tests, with a test execution report (
npm run oq). - Example configuration for laboratory equipment and an example migration.
Compatibility
- First release: nothing to be compatible with. Audit trails written by 1.0.0 are the baseline that later 1.x releases must replay unchanged.
- Requires Node.js 22.18 or newer; PostgreSQL 12 or newer for the PostgreSQL store.
Known limitations
- Every signed-in person can read all records and the whole audit trail.
- The whole trail is replayed at start and held in memory by every server and every browser (replay takes about two seconds for 30,000 entries); measure beyond tens of thousands of entries. No snapshots or archiving.
- One approval step; no attachments; no per-session revocation.
- Repeated import runs recognise earlier imports in approved mode only.