Skip to content
CNMLCertificat Numérique de Métrologie Légale
← Docs

Roles

For developers

5 min read · Roles

For developers

CNML is open source. The codebase spans two repositories: the OIML-CS certificates repository, which holds the canonical Ruby schemas and the corpus of real certificate instances, and the digital-certificates repository, which holds the TypeScript web application, the TypeScript packages, and the Ruby air-gapped PKI server. This document covers the repository layout, the build and test commands, the contribution workflow, the schema change procedure, and the code quality rules.

CNML is a proposal for OIML from the OIML SMART programme. The codebase described here is the reference implementation of that proposal. The architecture, the package boundaries, and the contribution workflow are subject to revision as the proposal evolves.

Repository layout

The OIML-CS certificates repository is the canonical source. It contains the schema set (the CORE schema in _core.yaml, the unit definitions in _units.yaml and _units_local.yaml, the shared modules under _modules/, and the per-Recommendation schemas as R<NN>.yaml), the corpus of eight hundred and eighty real certificate instances in YAML form, and the normalizer library that validates and processes the instance corpus.

The digital-certificates repository holds the implementation. The layout is as follows.

apps/cnml-web/                    Astro 7 with Vue 3 islands and Tailwind 4
  src/
    pages/                        Astro routes, auto-discovered
    islands/                      Vue components (SchemaForm, VerifyDrop, and others)
      forms/                      SchemaForm, recursive and data-driven
      checks/                     Verification pipeline modules
    layouts/                      Base shell
    content/docs/                 Markdown documentation, rendered by the /docs route
    styles/                       Global stylesheets
  astro.config.mjs                Site configuration

packages/
  cnml-schemas/                   Synced from the Ruby project
    src/
      schemas/                    R<NN>.yaml with x-oiml metadata
      index.ts                    Auto-generated by the schema generator
  cnml-xsd/                       XSD schema (cnml-1.0.xsd)
  cnml-xml/                       XML serializer and parser, ajv validator
  cnml-crypto/                    Sign, verify, scope, CRL, OpenTimestamps, checks
  cnml-types/                     TypeScript types generated from schemas
  cnml-dcoc/                      D-CoC RDF/XML and JSON-LD output
  cnml-units/                     Unit resolver, UnitsDB to BIPM Digital SI
  ptb-dcc-compat/                 DCC to CNML round-trip importer
  cnml-test-vectors/              Signed test vectors and specifications

oiml-pki-server/                  Ruby air-gapped CA, Sinatra
  lib/
    oiml_pki.rb                   Namespace and autoload entries
    oiml_pki/
      ca_store.rb                 AES-256-GCM encrypted keystore
      cert_factory.rb             X.509 cert, CSR, and CRL creation
      publisher.rb                Write artifacts to output directory
      audit_log.rb                Hash-chained tamper-evident log
      secret_sharing.rb           Shamir Secret Sharing over GF(p)
      recommendation_reader.rb    Read R-list from schemas directory
      key_provider.rb             Namespace and factory
        key_provider/
          base.rb                 Abstract interface
          software.rb             OpenSSL::PKey backend
          pkcs11.rb               PKCS#11 HSM backend
          confium.rb              Confium threshold backend
  spec/                           RSpec suite
  app.rb                          Sinatra application

e2e/                              Playwright specifications
scripts/
  generate-schemas-index.ts       Auto-discover schemas and generate index.ts
  generate-types.ts               JSON Schema to TypeScript type generation
  audience-build.ts               Split dist/ into signer, verifier, and public
apps/cnml-web/                    Astro 7 with Vue 3 islands and Tailwind 4
  src/
    pages/                        Astro routes, auto-discovered
    islands/                      Vue components (SchemaForm, VerifyDrop, and others)
      forms/                      SchemaForm, recursive and data-driven
      checks/                     Verification pipeline modules
    layouts/                      Base shell
    content/docs/                 Markdown documentation, rendered by the /docs route
    styles/                       Global stylesheets
  astro.config.mjs                Site configuration

packages/
  cnml-schemas/                   Synced from the Ruby project
    src/
      schemas/                    R<NN>.yaml with x-oiml metadata
      index.ts                    Auto-generated by the schema generator
  cnml-xsd/                       XSD schema (cnml-1.0.xsd)
  cnml-xml/                       XML serializer and parser, ajv validator
  cnml-crypto/                    Sign, verify, scope, CRL, OpenTimestamps, checks
  cnml-types/                     TypeScript types generated from schemas
  cnml-dcoc/                      D-CoC RDF/XML and JSON-LD output
  cnml-units/                     Unit resolver, UnitsDB to BIPM Digital SI
  ptb-dcc-compat/                 DCC to CNML round-trip importer
  cnml-test-vectors/              Signed test vectors and specifications

oiml-pki-server/                  Ruby air-gapped CA, Sinatra
  lib/
    oiml_pki.rb                   Namespace and autoload entries
    oiml_pki/
      ca_store.rb                 AES-256-GCM encrypted keystore
      cert_factory.rb             X.509 cert, CSR, and CRL creation
      publisher.rb                Write artifacts to output directory
      audit_log.rb                Hash-chained tamper-evident log
      secret_sharing.rb           Shamir Secret Sharing over GF(p)
      recommendation_reader.rb    Read R-list from schemas directory
      key_provider.rb             Namespace and factory
        key_provider/
          base.rb                 Abstract interface
          software.rb             OpenSSL::PKey backend
          pkcs11.rb               PKCS#11 HSM backend
          confium.rb              Confium threshold backend
  spec/                           RSpec suite
  app.rb                          Sinatra application

e2e/                              Playwright specifications
scripts/
  generate-schemas-index.ts       Auto-discover schemas and generate index.ts
  generate-types.ts               JSON Schema to TypeScript type generation
  audience-build.ts               Split dist/ into signer, verifier, and public

Build and test commands

The workspace uses pnpm. Commands are run from the repository root unless noted.

pnpm install                              # Install all dependencies
pnpm dev                                  # Astro dev server at http://localhost:4321
pnpm build                                # Production build with audience-build split
pnpm gen                                  # Regenerate schema index and TypeScript types
pnpm test                                 # TypeScript unit tests
pnpm test:e2e                             # Playwright browser tests
pnpm vectors:gen                          # Regenerate signed test vectors
pnpm vectors:verify                       # Verify all test vectors round-trip
pnpm smoke                                # Minimal sign-and-verify via Node
pnpm install                              # Install all dependencies
pnpm dev                                  # Astro dev server at http://localhost:4321
pnpm build                                # Production build with audience-build split
pnpm gen                                  # Regenerate schema index and TypeScript types
pnpm test                                 # TypeScript unit tests
pnpm test:e2e                             # Playwright browser tests
pnpm vectors:gen                          # Regenerate signed test vectors
pnpm vectors:verify                       # Verify all test vectors round-trip
pnpm smoke                                # Minimal sign-and-verify via Node

The Ruby CA server uses a separate Gemfile and is operated from the oiml-pki-server/ directory.

cd oiml-pki-server && bundle install      # Install Ruby dependencies
cd oiml-pki-server && ruby app.rb         # Sinatra server at http://localhost:4455
cd oiml-pki-server && bundle exec rspec   # Full Ruby test suite
cd oiml-pki-server && bundle install      # Install Ruby dependencies
cd oiml-pki-server && ruby app.rb         # Sinatra server at http://localhost:4455
cd oiml-pki-server && bundle exec rspec   # Full Ruby test suite

Playwright runs against port 4455 (not the default 4321) to avoid dev-server lock conflicts. The Playwright configuration auto-starts the Astro dev server in the background on the correct port.

Bundle analysis is available by setting ANALYZE=1 before the build command. The analysis output is written to dist/stats.html.

Contribution workflow

Contributions follow four workflows depending on the area of change.

Schema changes begin in the OIML-CS certificates repository. A schema change is validated there with ruby scripts/validate_certs.rb, which runs the eight hundred and eighty certificate instances against the updated schema set. The updated YAML files are then synced into packages/cnml-schemas/src/schemas/ in this repository. Running pnpm gen regenerates the schema index and the TypeScript types from the synced YAML set. The schema-driven design is described in Schema-driven design.

Web application changes are made in apps/cnml-web/. The developer uses pnpm dev for local development, runs pnpm test for TypeScript unit tests, and runs pnpm test:e2e for Playwright browser tests before committing.

PKI server changes are made in oiml-pki-server/. The developer runs bundle exec rspec for the Ruby test suite. Internal library code in lib/oiml_pki/ uses autoload exclusively, never require_relative.

Documentation changes are made in src/content/docs/. The documentation is rendered by the /docs route in the web application.

All contributions go through pull requests. The contributor creates a branch, pushes it, and opens a pull request against the main branch.

Code quality rules

The codebase enforces several code quality rules that apply across the TypeScript and Ruby code.

The schema is the specification. There is no bespoke per-Recommendation form code. If the form component cannot render a field, the schema is incomplete and the schema must be fixed. This rule ensures that the rendering layer contains no Recommendation-specific logic.

Ruby autoload only. Internal library code in lib/oiml_pki/ uses autoload, defined in lib/oiml_pki.rb. The directives require_relative and string-form require are not used for files inside the library. External gems are required normally. New modules are added by creating a file under lib/oiml_pki/ and adding one autoload entry in lib/oiml_pki.rb.

Open/closed. Adding a new verification check, a new key-storage backend, or a new schema requires one new file and one registration line. No existing module is modified. The verification pipeline and the KeyProvider dispatch are both instances of this pattern.

No forbidden Ruby patterns. The send method is not used to call private methods. The instance_variable_set and instance_variable_get methods are not used. The respond_to? method is not used for type checking (the is_a? method is used instead, or the type hierarchy is redesigned so the check is not needed).

No doubles in specs. RSpec doubles are not used. Tests use real model instances. If a lightweight data object is needed in a test, a Struct is used instead of a double.

Schema-driven design and the verification pipeline

Two architectural patterns dominate the codebase and are developed in dedicated documentation pages. The schema-driven design pattern renders every Recommendation form from declarative JSON Schema YAML, and adding a new Recommendation requires only a new YAML file and a regeneration step. The verification pipeline pattern renders every check from a module that conforms to a uniform interface, and adding a new check requires only a new file and one entry in the registry array. Contributors should read Schema-driven design and Verification pipeline before working on the schema layer or the verification layer.

See also