Introduction

This document forms the Cardano Engineering Handbook, which aims to provide documentation and policy that applies to all projects in the Cardano Open Source Consortium (COSC). To avoid confusion, projects should explicitly subscribe to the Handbook.

The target audience for the Handbook is everyone working on a project owned by the COSC. This is also the set of target contributors for the Handbook! Anyone can submit a change proposal as a PR, see CONTRIBUTING for the process.

Purpose

The purpose of this handbook is both descriptive and prescriptive:

  • Firstly, it prescribes mandatory or optional policies that should be followed by any software project that's part of the COSC in order to be a good citizen within the eco-system.
  • Secondly, it describes practices, processes, tools, or techniques that are used across the various projects, in order to help us to learn from each other and grow towards greater consistency in how we work.

As such, it is composed of two parts:

  • Part I is about policies
  • Part II is about practices

Goals

The goals of the Handbook are to:

  • Provide a place to record decisions on topics where coordination across the engineering organization is useful.
  • Explicitly record "the way things are done" in order to minimize tribal knowledge.
  • Encourage consistency across the organization in order to minimize cognitive overheads from unnecessary differences.
  • Be open to all contributors to the Cardano Open Source Project.

These are aspirational goals: much knowledge will not be recorded here; many topics we would like to coordinate on will be difficult to get consensus on; and consistency is not always welcome or desirable. But it is important to have an open location to record this information when we do have it.

Non-goals

The following are explicitly not goals of the Handbook:

  • Enforce the following of policies which do not have widespread support amongst the Cardano developers.
    • We believe this is a bad idea which is likely to simply cause such prescriptions to be ignored. Consequently, the Handbook should only include policies that either have widespread agreement, or are essential for coordination. Even then, policies should tend towards guidelines rather than prescriptions.
  • Collect all information relating to Cardano engineering.
    • The larger the Handbook, the harder it is to maintain. For this reason we aim to keep the scope relatively tight, although we may increase it later.
  • Supersede per-project contributing guidelines.
    • All of our projects are different, and the primary documentation for the development practices of a project should be its own contributing documentation. For suitably cross-project matters, the contributing documentation may link back to this Handbook, but the Handbook will never supersede it.

Status

The Handbook is very new and will gradually acquire more content over time. The other major issue is that many policies have been adapted from IOG policies, and are therefore IOG-specific in a way that is inappropriate for the COSC as a whole. We hope to improve all of this over time!

Please consult the issue tracker to see what we're working on, and feel free to open issues for any additional problems.

Project management

Subscribing to the Cardano Engineering Handbook

A project SHOULD explicitly indicate that they follow the Cardano Engineering Handbook in their README. Projects MAY do this by using a badge.

Consult shields.io for more information about how to embed this badge in your README.

  • Base badge URL: https://img.shields.io/badge/policy-Cardano%20Engineering%20Handbook-informational
  • Badge URL with link: https://img.shields.io/badge/policy-Cardano%20Engineering%20Handbook-informational?link=https://input-output-hk.github.io/cardano-engineering-handbook
  • Markdown fragment: [![handbook](https://img.shields.io/badge/policy-Cardano%20Engineering%20Handbook-informational)](https://input-output-hk.github.io/cardano-engineering-handbook)

Projects which subscribe to the Cardano Engineering Handbook SHOULD track any deviations from policy in some appropriate public location, such as their issue tracker.

A project SHOULD contain the following documents:

  • README
  • CONTRIBUTING
  • SECURITY
  • CODE_OF_CONDUCT
  • A CHANGELOG (if the project includes multiple packages or similar, then one CHANGELOG per package may be more appropriate)

The documents SHOULD be easily accessible and discoverable, ideally by being put in standard locations.

The content of these documents is largely up to the project contributors. The most important thing is that they exist, we believe that if they exist, people will mostly want to make them good.

In the following, when we say that a document should include information, this can either mean it includes it directly, or that it clearly links to where it can be found.

CONTRIBUTING

A CONTRIBUTING document SHOULD include the following information:

See the practices section for a broader discussion of what things are good to include in contributing documentation.

SECURITY

A SECURITY document MUST provide security@iohk.io as the contact email for security issues.

Individual projects SHOULD use the default security file in this repository, either by copying it or by creating a SECURITY document that just links to it.

CODE OF CONDUCT

Individual projects SHOULD use the default Code of Conduct in this repository, either by copying it or creating a CODE_OF_CONDUCT document that just links to it.

Roles and responsibilities

A project SHOULD clearly identify any people who perform key functional roles. For example, most projects will have people in the following functional roles:

  • People who can merge PRs
  • People who can do any code review that the project requires
  • People who can adjudicate technical (or other) disputes
  • People who can release the software
  • People who can resolve issues with CI

This is not an exhaustive list! Projects should list the roles that make sense for them.

A project MAY identify some individual or individuals as the "maintainers". The meaning of this is not specified, as it is a common term in usage throughout the open-source world with various meanings, but it typically indicates that they are a "default" holder of functional roles.

A project SHOULD also ensure that key functional roles have succession plans. In the simplest case this can just consist of ensuring that multiple maintainers are listed or that a backup maintainer is listed.

Examples

Projects can present this information however makes sense for them. For example, the following would be an acceptable presentation of this information for a moderately complex project:

The key maintainer for this project is Alice, who has final technical authority and handles releases. The exception is component C where Bob is the authority. Bob is also the backup maintainer in case Alice becomes unavailable.

The rest of the regular contributors are Dave, Edna, and Fahran, all of whom can merge PRs and be asked to review them. In addition, the CODEOWNERS file identifies specific reviewers who are required for PRs that affect specific components.

The CI runs on Github Actions, so any contributor with write permissions can restart workflows. Edna wrote the workflow specifications, so is the best person to help with problems, otherwise ask Dave.

For a small project it would also be sufficient to write this:

This project is maintained by Gareth. Hannah is the backup maintainer.

Rationale

Having clear roles and responsibilities is very useful for contributors. As they progress through the contribution workflow, it is important that they be able to identify people who can help them when they hit typical roadblocks, such as getting code reviews, merging PRs or adjudicating technical disputes.

It's also important for the health of a project that for important roles there is some kind of plan for what to do in case the people who currently occupy them stop being able to do so. Otherwise it can be the case that nobody is maintaining a project, and nobody even notices!

However, most of the details are project-specific. Exactly what roles make sense; who performs them; whether or not that descends from them having some other official role or not; what kind of backup plans make sense; all of these things can vary. For example, all of the following are reasonable project organization structures that we do not want to preclude:

  • A small group of co-maintainers who do everything.
  • A larger but very egalitarian team that attempts to share all responsibilities equally.
  • A very formalised team with explicit team roles and leadership positions.
  • A project which has multiple sub-components which have very different organization structures (e.g. a big project with a formal methods component that has a single maintainer)

The main goals are just for projects to:

  1. Identify the state of their roles and responsibilities explicitly, rather than relying on people just picking it up; and
  2. Make some effort to ensure that key roles don't go unfilled.
  • TODO: Need a contributor from our security teams to help write these.
  • TODO: security team to consider adopting ASF policy
  • TODO: Adapt draft audit requirements doc into the handbook.
  • TODO: Write a policy on foreign libraries.
  • DRAFTED: Write a policy on responsible disclosure.

Security

Maintaining the security of the Cardano blockchain is a paramount concern. In any open source project there is a tension between maximising security and maintaining openness/transparency. Resolving this tension is especially important where some upgrades can only be carried out simultaneously (via a "hard fork" in Cardano), requiring a balance between the need for security and the desire for clear and precise communication/explanation of code changes. Consistent with other blockchain projects (e.g. Ethereum), we have therefore adopted a policy of responsible notification, responsible development, and responsible disclosure of security patches that prioritises security, but aims to provide clarity. Given the open nature of Cardano development, this requires particular care over how security issues are handled by code maintainers.

Security Issues

Standard IOG security procedures MUST be followed when a security concern is identified. In particular, possible security concerns MUST be notified via email to security@iohk.io or via IOG internal slack to @charles.morgan.

All security concerns WILL be evaluated. If a vulnerability is deemed to exist, then a fix WILL be developed and released. All care WILL be taken not to expose the nature of the vulnerability, both during code development and as part of the release. This may include using a private fork for the development of the fix, including the fix as part of another code commit, taking appropriate care when writing code comments/descriptions, silent fixes, or other techniques.

Responsible Disclosure

In order to avoid calling attention to a vulnerability that may lead to a possible security exploit before the fix is fully deployed, it will usually be necessary to "silently" fix the vulnerability. This is particularly important in Cardano, compared to e.g. an operating system or application, since such fixes may only be enacted at (relatively infrequent) hard forks, nodes must connect to public network infrastructure, and all nodes will remain vulnerable until they are patched (the usual approach of a gradual security upgrade will not generally be possible).

Silent Fixes

Attention MUST NOT be called to security vulnerabilities before the vulnerability is fixed and deployed. Silent fixes MUST be used when necessary. That is:

  • the code SHOULD be obscured so that its purpose is not immediately clear;
  • comments, commit messages and documentation MUST not expose the security vulnerability;
  • a code patch SHOULD be included as part of a larger bundle of (possibly unrelated) changes;
  • the description of the fix SHOULD be intentionally vague, omitted or even misleading;
  • developers MUST refrain from commenting on discussions relating to the fix where this would call attention to the vulnerability.

In all cases, security concerns override transparency considerations.

Disclosure Procedures

If we silently fix a vulnerability and a hard fork is needed to enable the fix then:

  • we SHOULD publish the details about the vulnerability 4-8 weeks after the hard fork

If we silently fix a vulnerability and a hard fork is not needed to enable the fix then:

  • After 4-8 weeks, we SHOULD disclose that the release contained a security-fix;
  • After an additional 4-8 weeks, we SHOULD publish the details about the vulnerability

Build, CI and Deployment

This section contains policies on building code, CI, and deployment.

Platform support

Projects which are dependencies of the Cardano node MUST support all of:

  • Linux
  • MacOS
  • Windows (cross-compilation is acceptable, even recommended)

This means that the project must compiled and tested on those platforms.

Rationale

The Cardano node supports all of these platforms, so all of its upstream dependencies must do so also, otherwise that will block the node.

CI

Exercise supported platforms and build methods

If your project claims to support platform X then it SHOULD build on platform X in CI. Otherwise it is very easy for support to be lost without noticing, which may only be discovered much later, perhaps by a user.

Similarly, if you claim to support a particular build method (e.g. bare cabal on Linux), then you should also exercise this in your CI.

Version Control System

Projects MUST use a Version Control System to ensure changes to the code source are properly tracked and individual changes can be identified and referred to easily. git is the most widespread tool in use those days but there are many good tools to choose from.

When releasing a new version of a software package, projects MUST use tags or any similar mechanism -- symbolic reference -- to identify in the VCS the revision that corresponds to the newly released package. This tag MUST match the version number of the released package such that it's straightforward to link both.

Projects SHOULD document their tagging convention.

  • Version 1.2.3 of a package X could be tagged as 1.2.3 or v1.2.3 or X-1.2.3, etc.
  • Project P releasing two packages X and Y respectively with version 1.2.3 and 2.3.4 could tag the same revision with X-1.2.3 and Y-2.3.4

Haskell

This section contains specific policies for projects written in Haskell.

GHC Version policy

This policy explains which versions of GHC should be used in a project.

Which version of GHC should my project be using?

At any given time there will be a current major version of GHC, and there may be a next major version of GHC, recorded in this document.

New projects should build with the current version.

The choice of minor version is less important, but in general projects SHOULD move to later minor versions as they are released. In particular, when adding support for a new major version, projects should always use the latest minor version of that major version (unless it is blacklisted, see below).

Are there any versions of GHC that should never be used?

Some versions of GHC are known to be broken in a critical way and SHOULD NOT be used. This may mean that projectes need to stay on an older minor version if the newer ones are blacklisted.

How do we upgrade to new versions?

The DevX team will proactively work on making code compatible with new compilers, and add CI (allowed to fail) for new compilers, as ressources permit. There is low priority and no one should feel pressured to add compatibility into tight schedules. Due to the nature of dependencies (internal and external), this work is hard to predict, and DevX will slowly work away at it. Once all code is compatible with a new compiler developers will be able to use a new compier during development, however production builds will still be built against our current compiler version, and the current compiler version must be green in CI.

We will stay on the current compiler version, until cardano-node has signoff from performance and tracing, as well as quality engineering to move to the next version. After a grace persiod or 3mo afterwards, we can drop the old compiler version.

What is the current major version of GHC?

8.10.

What is the next major version of GHC?

The next major version of GHC is 9.2, after that GHC 9.6.

Which versions of GHC are blacklisted?

  • 9.0.x: first minor had critical bugs. 9.0.2 was released just to avoid creating an “abandoned release” precedent, as 9.2 was released before it and should be preferred.

How does the next major version change?

Cardano technical leadership will decide when to adopt new versions of GHC. This decision may be made on the basis of:

  • Maturity of the release
  • Bugs which block upgrading
  • Bugs which will be resolved by upgrading
  • Whether we are at key points in product release cycles
  • Whether essential libraries have been upgraded to the new version
  • Whether our custom tooling (e.g. GHCJS) is updated to work with it

GHC version confidence status

VersionStatusComments
8.10.(4+)StableCurrent preferred version
9.0.*UnclearNot widely deployed, avoid
9.2.(4+)StableNext preferred version.

Rationale

Different major versions of GHC can be substantially different. In particular, programs may not compile with newer versions of GHC, or may have additional warnings. Hence, it make it much harder to integrate projects if they are tested on different major versions of GHC.

That means that everything is much smoother if all the Caradano Haskell projects use the same version of GHC. But that also means that when we change the major version, we need to change it for every project relatively synchronously.

Additionally, many versions of GHC have significant problems, particularly on specific platforms like Windows, and it's useful to have a central place to record decisions to avoid.

Packaging

This section contains policies about packaging our Haskell software.

Distributing Haskell packages

Distribution via CHaP

Cardano packages which are not released to Hackage MUST be released to Cardano Haskell Packages (CHaP) as part of their release process. The process for doing this is described in the CHaP README, any additional requirements described there MUST be followed.

Cardano packages which depend on other Cardano packages SHOULD acquire them from CHaP and not through the use of source-repository-packages (exceptions). The process for doing this is also described in the CHaP README.

Rationale

CHaP offers us a clear distribution method with proper versioning based on actual releases, without requiring us to actually publish to Hackage. This means we can be more relaxed in various ways (e.g. renaming packages) and also that we can publish patched versions of broken upstream dependencies unilaterally.

In the past we used source-repository-package stanzas exclusively for distribution. This had all the problems discussed below, but also meant that downstream projects had to cobble together enormous lists of such stanzas in order to get a working build, which was very painful for cardano-node, and even worse for anything downstream of it.

Distribution via Hackage

A Cardano package which:

  1. Is stable
    • Vague, but implies that it is unlikely to be significantly re-worked, renamed, etc.
  2. Has broad value to the community
    • A test question is: would any non-Cardano package actually use it?
  3. Has clear maintainership, ideally including individuals from multiple organizations
  4. Reaches a high standard of documentation, testing, and open-source infrastructure
  5. Is in full compliance with all legal policies
  6. Is developed in its own, standalone repository

MAY be released to Hackage. The process for doing this is the normal process for releasing to Hackage.

A package which is released to Hackage that was previously released to CHaP MUST be released to Hackage with a higher version number than any version number under which it appears in CHaP.

The Hackage user iogospo MUST be included as a maintainer of the package on Hackage.

Rationale

Publishing to Hackage is desirable because it makes it easier for the wider community to benefit from our work. We should do this, but only when we think they actually will benefit: when we have a stable, high-quality, useful packge to contribute. Much of the list of criteria is designed to make the package look like a "normal" open-source Haskell package repository (of high quality).

Use of source-repository-packages

A package which relies on source-repository-package stanzas to build MUST NOT be released to CHaP or Hackage until they are removed.

Use of source-repository-package stanzas is acceptable (or even recommended) in the following circumstances:

  • To experiment with a pre-release version of a Cardano package.
  • To pull in a fixed version of a dependency (not necessarily a Cardano one) where the fix has not been released yet.

In the latter situation a long-lived or permanent fork can become unavoidable (e.g. if the upstream maintainer is unresponsive). In this case, a patched version SHOULD be released to CHaP (and must be in order to allow a release of the package which depends on it), see the CHaP README for more details. For packages which are released to Hackage, it may be necessary to request a Hackage takeover of the problematic dependency since we cannot unilaterally release a fixed version in that case.

Rationale

source-repository-packages do not interact well with package repositories like CHaP or Hackage. They implicitly assume that the .cabal file is a sufficient build recipe, but this is not true if the package relies on source-repository-packge stanzas. Therefore, uploading such a package to CHaP or Hackage simply means it probably won't work for downstream users.

Additionally, source-repository-packages interact problematically with cabal, which always wants to rebuild them even if our tooling has ensured that a prebuilt version has been provided. In the past this seriously interfered with our ability to provide developers with pre-cached development environments, since cabal would insist on rebuilding many of the dependencies.

Versioning Haskell packages

TODO: need to decide on a versioning policy

Version bounds

Scope

Library components of packages which are intended to be distributed via a package repository MUST follow this policy. Non-library components of such packages MUST also do so if it is anticipated that they will be used downstream.

(Inversely: components of packages which are not intended to be distributed, or non-library components of packages which are not intended to be used downstream do NOT need to follow this policy).

Examples

Package pkg-a contains a test suite and also an executable that is used to inspect binary blobs that can be produced by using pkg-a. The test suite is not intended to be used downstream, but the executable is, since it can be helpful for users to diagnose the products of pkg-a. Hence the test suite does not need bounds, but the executable does.

Rationale

Non-library components are much less critical, because they cannot be depended upon, and it is rarer that someone will want to e.g. run the tests or benchmarks for an upstream package. However, it can still be the case that this happens, especially for executables, which are sometimes explicitly intended to be used by downstream users. For this reason we tie the choice over whether to include bounds to the decision of the maintainer over whether the component is intended to be used downstream.

Known-bad bounds

Version bounds (both upper and lower) MUST be included when they exclude versions of dependencies that the package is known not to work with. In addition, if it is discovered that a version of a dependency does not work with a version of the package that has been released to a package repository, then the package maintainer SHOULD publish a revision of the released version to include the new bound.

Examples

Discovering an incompatible version

The developer of package pkg-a, which depends on library pkg-b, tries to build with pkg-b-N. This fails, so the developer should:

  1. Add an upper bound of pkg-b < N, since pkg-b-N is known not to work; or
  2. Fix pkg-a to work with pkg-b-N, and if this means that pkg-a will now no longer work with earlier versions of pkg-b, add a lower bound of pkg-b >= N.

And then optionally publish a revision of some released versions of pkg-a to add upper bounds on pkg-b-N

Discovering an incompatible version for an upstream dependency

The developer of package pkg-c, which depends on pkg-a-M, tries to build with pkg-b-N. This fails when building pkg-a, so the developer of pkg-c notifies the developer of pkg-a. One of them should then:

  1. Publish a revision pkg-a-M to have a bound of pkg-b < N; and
  2. Add a bound of pkg-b < N to the development branch of pkg-a if it still applies.

Rationale

Excluding dependency versions which are known not to work is a cheap way to convey information to downstream users. It means that if they try to use the non-working version then they will get a solver error from cabal, instead of a compilation error. It is common to discover this kind of version incompatibility information during development, and so this policy primarily insists that such information be recorded mechanically so that other people benefit from it.

Speculative upper bounds

A package MAY include an upper bound that excludes the next major version of a dependency, even if it is not known whether the next major version will break the package (e.g. because it has not been released yet).

Examples

The package pkg-b depends on pkg-a. pkg-a tends to have breaking changes in releases, so the developer of pkg-b decides to pin their dependency on pkg-a using a caret bound, which implies a (speculative) upper bound of the next major version.

The package pkg-c depends on pkg-b. The developer of pkg-c really doesn't want to have to deal with bumping the upper bound on pkg-b, so they just leave it off. Note that due to the above policy about discovering incompatible versions, when a version of pkg-b is released that does break pkg-c, one of the developers should publish revisions to exclude the breaking version of pkg-b from the released versions of pkg-c.

Rationale

Speculative upper bounds are controversial. They have advantages (ensure that users get a working (if old) build plan; robustness against future changes), and disadvantages (often overly cautious; require large amounts of bound-relaxing to allow even "safe" new major versions). There is no consensus amongst the Cardano engineering community about which is preferable, so we simply note that either approach is acceptable.

The cost of speculative upper bounds is somewhat lower for Cardano packages, since they are typically well-maintained and released frequently, so bounds relaxations can be made and released in a fairly timely fashion. Nonetheless, they still impose costs, in particular requiring that downstream packages be released in order for new versions to be used.

Intra-repository dependencies

A set of packages defined in the same source repository MUST include version bounds which are as tight as necessary to ensure that they function correctly when distributed via a package repository. It is not possible to give a fully-general rule for what bounds to use, but assuming that the packages are following something like PVP, typically pinning the major version is the right thing to do.

Examples

Packages pkg-a and pkg-b are defined in the same source repository, and pkg-a depends on pkg-b. pkg-a is at version 1.1.2, pkg-b is at version 2.4.3, and they both follow the PVP. Then pkg-a should bound its dependency on pkg-b to pkg-b == 2.4.*

Rationale

Within a single source repository, packages are usually built with all the packages taken from a single commit of the source repository. When the packages are built from a package repository, then cabal may try to build them with different versions, so long as the bounds are satisfied. For this reason it is important to have tight enough bounds on packages which are defined in the same source repository. Typically it should be enough to pin the major version.

Implied bounds

A component MAY omit bounds that it is otherwise required to have if those bounds are strictly implied by other dependencies that the package has within the same source repository.

Examples

Package pkg-a has both a library component and an executable component, both of which are used downstream. Both components depend on pkg-b-N, and do not work with pkg-b-(N+1), and the executable depends on the library. In this case it is acceptable to only put a pkg-b < N+1 bound on the library, because the executable component strictly depends on the library component of the same version, and the library component has the bound.

Rationale

It is common to have a repository which has many components/packages depending on some other package P. It is tedious to require every use of P to be well-bounded, especially since the components/packages in a repository should have tight bounds on each other, such that in practice bounding a single use of P should be enough to fix it for every package in the repository. Some care should be taken, however: it is easy to think that all packages in the repository are constrained, when in fact there may be a few that don't depend on the one that bounds P.

Project management

Contributing documentation

Contributing documentation is highly recommended. Contributing documentation is how we record the knowledge of how to work on the project day-to-day, rather than relying on it being passed on by word-of-mouth. So having good, comprehensive contributing documentation is vital to having a wide group of contributors.

In addition to the information required by the policy, this is a (non-exhaustive!) list of topics that you may want to cover in your contributing documentation:

  • How should a contributor build the project? Are there any special environment setup instructions? Are there any special tools that need to be used, e.g. code formatters?
  • How are common maintenance tasks performed, e.g. updating dependencies?
  • Does the project have any coding standards that should be followed?
  • How is the project documented? What are the expectations on contributors for updating or adding documentation based on their changes?
  • Under what circumstances does a change require a design discussion beforehand? Is a ticket sufficient? Does the design need to be signed off by someone before an implementation will be accepted?
  • Under what circumstances does a change require a security audit? Whatever is written here should be compatible with the audit policy, for most projects it should be sufficient to link to that.
  • Are there any additional non-automated tests that need to be run for certain kinds of change, e.g. performance tests?
  • Does the project have any requirements for how the Git history of changes is constructed, e.g. squashing changes, conventional commit messages?
  • Are code reviews required? What are the contributors responsibilities in terms of soliciting and responding to such reviews?
  • How does the project's CI work? How should a contributor interpret and diagnose failures?
  • How are releases performed?
  • Are there any legal restrictions on the project? What are the contributors obligations with respect to those, e.g. signing CLAs?

Many of our projects have contributing documentation that you can use for inspiration:

Haskell

This section contains descriptions of common practices in writing Haskell code.

Testing

This describes various testing practices which are in use in the Cardano project, along with examples for reference and links to commonly used libraries.

Unit testing

Unit testing is standard practice in the industry and Cardano is no exception.

Libraries

  • tasty: a straightforward testing framework, with many libraries that provide additional functionality
  • hspec: another testing framework, with a BDD-style specification style

Property-based testing

Cardano makes extensive use of property-based testing. We believe it to be an excellent technique that allows us to get much higher testing assurance that we would be able to otherwise.

Examples

  • cardano-ledger uses QuickCheck to generate both transactions and "traces", in order to test the ledger rules.

Libraries

  • QuickCheck: the original property-based testing library
  • hedgehog: a more recent property-based testing library
  • quickcheck-dynamic: a library for testing stateful systems using QuickCheck

Conformance testing

Often our projects have specifications or research documents as well as an implementation. It is useful to test whether the specification conforms to the implementation. This sort of test suite is also useful if there are multiple implementations, as they can all share the use of the conformance test suite.

Examples

  • plutus has a hand-written conformance test suite.

Code formatting

It's generally a good idea to have an automatic code formatter, since it reduces arguments about stylistic matters (sometimes replacing them with arguments about the choice of formatter!).

Different projects use different formatters, but the common ones are:

  • ormolu: a very opinionated formatter with no configuration that formats everything
    • Used by: cardano-ledger
  • stylish-haskell: a less opinionated and less complete formatter
    • Used by: plutus, ouroboros-network, cardano-node

Coding style

Most project teams develop some opinions on how they want to write code. It can be helpful to write these down, to help with onboarding new people and to provide a place to explain such decisions.

Here are some style guides written by Cardano teams:

And here are some public style guides:

GitHub Actions

If you are using GitHub Actions it SHOULD be configured so that pull requests from forks will trigger it. This is especially important if the maintainer set up branch protection rules which prevent merging PRs which did not pass CI. This can be also confusing when there are no branch protection rules, in which case it could lead to accepting a PR which silently breaks the code. For these reasons we recommend being permissive on pull_request event and explicit on which branches trigger CI on push event, e.g.

on:
  pull_request:
  push:
    branches:
      - main

which will trigger only pull_request event for pull requests, but not push event when a PR is created.

Merge Trains

Merge trains allow to merge multiple outstanding pull request and check if all the changes are compatible. They usualy merge into a temporary branch which SHOULD be used to run CI on, as a side effect the CI infrastructure is triggered less often as PRs are batched. Examples of merge trains include: bors, mergify or GitHub's own merge queues.

Specific configuration depends on the application one is using, for example when using bors one it's a good idea to configure GitHub Actions with:

on:
  pull_request:
  push:
    branches:
      - 'bors/*'

Running GitHub Actions for external contributors

GitHub allows to configure which actions can run automatically from public forks. The maintainer ought to decide what level of security is required, please see the GitHub documentation.