Skip to content

Regulation Deployment

Regulations are distributed as NuGet packages — self-contained archives combining JSON definitions, C# scripts, and an install manifest. The Payroll Console InstallRegulationPackage command reads the package and imports all files into the backend in the correct order.

Regulation Deployment

Layer Model

The number of regulation layers is freely configurable — payrolls can consist of one layer or many, depending on the use case. The diagram above shows a typical three-layer example with Country, Business, and Tenant regulations. Other common configurations are:

Configuration Layers
Single-country, single-tenant Country only
Single-country, multi-tenant Country + Tenant
Multi-country provider Country + Business + Tenant
With multi-country consolidation COM.Base + Country + Consolidation + Business + Tenant

Each layer may reference lower layers via baseRegulations. The dependency chain is enforced at install time — installing a package before its dependencies are present in the target tenant will fail with a clear error listing what is missing.

Package Structure

A regulation package is a standard .nupkg file (ZIP format) containing two parallel sets of the same files:

Embedded resources — compiled into the DLL for use by PayrollEngine.Client.Scripting and the Roslyn-based script compiler at runtime.

regulation/ folder — plain files inside the ZIP, read directly by InstallRegulationPackage without loading the DLL.

MyRegulation.nupkg
├── lib/net10.0/MyRegulation.dll        ← embedded resources (JSON + scripts)
└── regulation/
    ├── regulation-package.json         ← install manifest
    ├── Regulation/
    │     ├── MyRegulation.2026.json
    │     ├── MyRegulation.Cases.2026.json
    │     └── MyRegulation.WageTypes.2026.json
    └── Scripts/
          └── WageTypeValueFunction.Action.cs

Install Manifest

Every package contains a regulation-package.json that defines the install metadata and the ordered list of files to import:

{
  "packageId": "Acme.Regulation.Country.2026.1",
  "regulationName": "Acme.Country",
  "version": "2026.1",
  "baseRegulations": [],
  "installFiles": [
    "Regulation/Country.2026.json",
    "Regulation/Country.Cases.2026.json",
    "Regulation/Country.Collectors.2026.json",
    "Regulation/Country.WageTypes.2026.json",
    "Regulation/Country.Data.Tax.2026.json",
    "Regulation/Country.Data.SocialInsurance.2026.json"
  ]
}

The first file creates the regulation structure; all subsequent files extend it. Data regulations (annual tax rates, statutory parameters) are always last — they can be updated independently without touching the calculation logic.

InstallRegulationPackage Command

InstallRegulationPackage <packageFile|url> <tenant> [/new|/overwrite] [/execute|/dryrun]
Step Description
1. Resolve source Downloads package if URL; resolves wildcard to single file if local path
2. Open package Reads .nupkg as a ZIP archive
3. Read manifest Loads regulation/regulation-package.json
4. Resolve tenant Fails if tenant not found in backend
5. Version check /new: fails if regulation exists; /overwrite: logs overwrite active
6. Dependency check Queries backend for each baseRegulations entry — lists all missing
7. DryRun exit /dryrun: reports what would be imported, no changes made
8. Import in order Imports each file via the REST API in manifest order

Examples:

# Fresh install
InstallRegulationPackage Acme.Regulation.Country.2026.1.nupkg MyTenant

# Validate only — no changes
InstallRegulationPackage Acme.Regulation.Country.2026.1.nupkg MyTenant /dryrun

# Mid-year data update
InstallRegulationPackage Acme.Regulation.Country.2026.2.nupkg MyTenant /overwrite

# Wildcard — single match
InstallRegulationPackage Acme.Regulation.Country.*.nupkg MyTenant /dryrun

# Install directly from a URL (e.g. GitHub Release asset)
InstallRegulationPackage https://github.com/Acme/Regulation/releases/download/v2026.1/Acme.Regulation.Country.2026.1.nupkg MyTenant
InstallRegulationPackage https://github.com/Acme/Regulation/releases/download/v2026.1/Acme.Regulation.Country.2026.1.nupkg MyTenant /dryrun

Example install order for a multi-country setup:

# 1. Shared COM schema — no dependencies
InstallRegulationPackage Acme.Regulation.COM.Base.2026.1.nupkg ComTenant

# 2. Country regulation — no dependencies
InstallRegulationPackage Acme.Regulation.Country.2026.1.nupkg CountryTenant

# 3. Consolidation — depends on COM schema + Country
InstallRegulationPackage Acme.Regulation.Country.Consolidation.2026.1.nupkg CountryTenant

# 4. Business regulation — depends on Country
InstallRegulationPackage Acme.Regulation.Business.2026.1.nupkg BusinessTenant

Shared Regulations

Country and consolidation regulations are marked sharedRegulation: true in their JSON definition. This allows a single regulation instance to be shared across multiple tenants via the Regulation Sharing API — one import serves all tenants, with no per-tenant reimport needed on annual updates.

{
  "name": "Acme.Country",
  "namespace": "Country",
  "sharedRegulation": true,
  "validFrom": "2026-01-01T00:00:00Z"
}

NuGet Reference Chain

Packages reference each other as NuGet dependencies, following the same layer order:

PayrollEngine.Client.Scripting
    ↑
Acme.Regulation.COM.Base
    ↑
Acme.Regulation.Country
    ↑
Acme.Regulation.Country.Consolidation
    ↑
Acme.Regulation.Business
    ↑
Acme.Regulation.Tenant

This ensures that when a package is built, all its upstream dependencies are already available as versioned NuGet packages — locally from a packages folder during development, or from GitHub Packages in CI/CD.

Versioning

Regulation packages use calendar-semantic versioning: {Year}.{Revision}.

Version Meaning
2026.1 First release for tax year 2026
2026.2 Mid-year update (e.g. minimum wage adjustment)
2026.3 Correction after legislative change
2027.1 Tax year 2027

Pre-release suffixes are supported: 2026.1-beta.1. Annual data regulations (tax tables, statutory rates) are updated by importing a new data regulation with a matching validFrom date — the calculation logic regulation remains unchanged.

See Also