Chapter 09 — Style Deep-Dive: Industrial / SIL
The hazard is a field. Not a footnote, not a tag, not a comment — a field, required, typed, validated at instantiation.
The previous chapter opened the Style system's door and walked through DefaultStyle — the ISO/IEC/IEEE 29148 register that ships as the baseline. This chapter picks the door that leads into the machine room of a safety-critical project and walks through IndustrialStyle: the register designed to survive a HAZOP review, a TÜV route validation, an IEC 62443 cybersecurity audit, and a 21 CFR Part 11 inspection.
The claim this chapter defends is narrower than it might appear. It is not that every Requirement should carry SIL information. It is not that the industrial register is superior to the default. It is that safety-critical work already has a vocabulary — the one used in HAZOP sheets, LOPA worksheets, SRS sections, and FAT protocols — and that a requirements DSL with no way to represent that vocabulary ends up smuggling it in as comments, magic strings, and README prose. IndustrialStyle makes the vocabulary a typed surface the compiler can check.
The contrary claim the chapter also defends, honestly, is that this register is wrong for most projects. Dog-fooding it against a TypeScript library — the package itself, for instance — would be a category error. The package uses DefaultStyle, not IndustrialStyle. The distinction matters, and the chapter will draw the line where the register earns its weight and where it is pure friction.
Why industrial register exists at all
Pull up a HAZOP sheet from a mid-size integrator project. It is an Excel file. It has 1 400 rows. Every row describes a process deviation — no flow, reverse flow, more pressure, less temperature — assessed against a node of the piping-and-instrumentation diagram, with a recommendation column populated by a multidisciplinary review team over three days of sweat.
Now pull up the Safety Requirements Specification (SRS) for the same project. It is a Word document. It is 60 pages. Chapter 4 contains a section called "Derived Safety Requirements". That section will reference HAZOP rows by number: "SIF-042 is derived from HAZOP row 112, node N-12, deviation 'no flow', with a SIL 2 target established by LOPA-SIF-042 scenario 2."
The reference is a human sentence in a Word document pointing at a row number in an Excel file pointing at a node identifier in a Visio drawing. Change the HAZOP row numbering — because somebody inserted a row — and every reference in the SRS drifts silently. Change the LOPA scenario assignment — because somebody re-ran the analysis — and the SIL target in the SRS is wrong with no mechanism to detect the drift. The only thing preventing a fatal misalignment is the memory of a handful of engineers who have lived with the project long enough to know "yes, that SIL 2 is the right one".
This is not a rare state. This is the normal state of most SIS/DCS/SCADA integration projects in 2026. The vocabulary — HAZOP node, LOPA scenario, SIL level, proof-test interval, SIF, IPL, demand, response time — is precise, canonical, and rich. The tooling that carries it is Excel, Word, Visio, and an engineer's memory.
A generic register — default agile, say — cannot hold this vocabulary. There is no field for HAZOP node. There is no typed union 'SIL1' | 'SIL2' | 'SIL3' | 'SIL4' | 'NonSIL'. There is no validator that says "a Safety requirement without a SIL level is invalid". The engineer who tries to use a generic register on a safety-critical project ends up smuggling the information in as free-text tags, prose paragraphs, or JSON blobs in a comment. The information is there; the compiler cannot see it.
IndustrialStyle's premise is that the generic register is the wrong tool for this job, and that the right tool is a register whose vocabulary is the HAZOP / LOPA / SRS / FAT vocabulary. Not as a theme, not as a skin — as a set of types, a set of validators, and a set of templates that make the vocabulary mandatory at the type level. A Requirement<IndustrialStyleType> cannot omit its hazard reference or its SIL level the way a Requirement<DefaultStyleType> can. The Style narrows the Requirement's shape to the shape the standard demands.
That narrowing — the loss of the freedom to omit a SIL level — is the whole value proposition. A safety engineer who reads an IndustrialStyle spec knows, before reading a single line, that the author did not forget the hazard reference. The compiler refused the spec if the hazard reference was missing. The refusal is the guarantee.
The rest of this chapter unpacks the mechanism that makes that guarantee real.
SIL in ten minutes for a TypeScript engineer
Before the code, the vocabulary. If you have never written a SIL requirement, the following is enough to read this chapter. If you are a safety engineer, skip to the next section.
Safety Integrity Level (SIL) is a four-level scale — SIL 1 through SIL 4 — defined by IEC 61508, the parent standard for functional safety of electrical, electronic, and programmable electronic systems. It is not a severity tag. It is a two-sided commitment:
- A probability-of-dangerous-failure budget. The higher the SIL, the lower the budget. SIL 1 allows an average probability of failure on demand between 10⁻² and 10⁻¹ (at most one failure in ten demands). SIL 4 allows between 10⁻⁵ and 10⁻⁴ (at most one failure in ten thousand demands). The budget is quantitative; it lives in a spreadsheet with FMEDA data and proof-test intervals and is calculated in closed form.
- A development-rigour commitment. The higher the SIL, the more rigour the standard demands. SIL 1 can be verified by inspection and review; SIL 2 requires independent verification by a person not on the development team; SIL 3 requires formal methods (static analysis, formal specification, model checking) for some of the work products; SIL 4 requires formal proof for the safety-critical portion.
Both sides matter. A safety function can hit its failure-budget number on paper and still fail its SIL validation because the development process it went through did not meet the standard's rigour requirements. Conversely, a process that followed every rigour rule but did not produce a failure-budget calculation also fails validation.
This is why SIL is not a label you paste onto a requirement. It is a commitment the whole project makes, with auditable artefacts at every stage.
The standard family that implements SIL across industries:
- IEC 61508 — the parent standard, technology-neutral. SIL 1–4 are defined here.
- IEC 61511 — the process-industry sector-specific derivative. Refineries, petrochemical plants, chemical processing, oil and gas. The Safety Instrumented Function (SIF) concept lives here.
- IEC 62061 — machinery, with a SIL scale derived from 61508 but tailored to machine control.
- ISO 13849 — also machinery, with a parallel Performance Level (PL a–e) scale that maps loosely to SIL.
- IEC 62443 — OT cybersecurity. Not SIL but SL (Security Level) 1–4, across a seven-dimensional vector. Same ladder shape, different failure mode (adversarial rather than random).
- IEC 61131-3 — the PLC programming languages. Not a safety standard, but the language environment in which SIL-rated software is written.
- IEC 61850 — power utility automation. Different domain, same traceability discipline.
When IndustrialStyle talks about "risk levels", it means SIL. When it talks about "security levels", it means SL. When it says "verification method", the legal values include SILProofTest and SILValidation — both names with precise meanings the standards define.
A TypeScript engineer unfamiliar with the field should take away three observations:
- The vocabulary is finite and standardised. The closed sets are small — four SIL levels, a dozen verification methods, ten source kinds — and each element has a citable definition. Perfect for discriminated unions.
- The validation rules are deterministic and checkable. "A Safety requirement must carry a SIL level." "A SIF must be verified by a SIL proof test or a SIL validation." "A Regulatory requirement must cite a standard or regulation." These are the kind of rules a compiler can enforce without debate.
- The ceremonies are heavy but uniform. The SafetyApproval gate exists on every industrial project; the FAT/SAT split exists on every delivery; the MoC loop exists whenever a field change hits production. A single Style can encode them all.
The rest of the IEC alphabet — 61511, 62443, 61131-3, 61850, ISA-88, ISA-95, ISA-18.2, 21 CFR Part 11, GAMP 5 — are refinements on this core. IndustrialStyle's vocabulary is the union of the concepts these standards name. The package's docs/pitches/industrial.md enumerates the full matrix; this chapter extracts the parts that matter for the dog-food narrative.
The IndustrialStyle type, quoted
The canonical source is packages/requirements/src/styles/industrial.ts. Quote the header block:
/**
* IndustrialStyle — the ontology for industrial software engineering.
*
* Aligned with the normative stack used in factory automation, process control,
* power utility automation, and OT cybersecurity. Designed to be sold into
* regulated integrators (Schneider Electric, Siemens, Rockwell, ABB, Honeywell)
* and their end customers (pharma, oil & gas, automotive, power, water).
*//**
* IndustrialStyle — the ontology for industrial software engineering.
*
* Aligned with the normative stack used in factory automation, process control,
* power utility automation, and OT cybersecurity. Designed to be sold into
* regulated integrators (Schneider Electric, Siemens, Rockwell, ABB, Honeywell)
* and their end customers (pharma, oil & gas, automotive, power, water).
*/The header names the customer base: integrators first, end customers second. This is not an academic Style. It is a Style whose vocabulary matches the one a Schneider or Siemens FAT engineer uses in their day job, because those are the people the Style exists to serve.
The file exports five artefacts:
export const INDUSTRIAL_VOCABULARY: StyleVocabulary = { /* … */ };
export const INDUSTRIAL_VALIDATORS: StyleValidators = { /* … */ };
export const INDUSTRIAL_TEMPLATES: StyleTemplates = { /* … */ };
export const INDUSTRIAL_REPORTER: RequirementReporter = { /* … */ };
export const IndustrialStyle: RequirementStyle = {
id: '@frenchexdev/requirements/styles/industrial',
version: '1.0.0',
vocabulary: INDUSTRIAL_VOCABULARY,
validators: INDUSTRIAL_VALIDATORS,
templates: INDUSTRIAL_TEMPLATES,
reporter: INDUSTRIAL_REPORTER,
fitCriterionAdapters: [],
};
export type IndustrialStyleType = typeof IndustrialStyle;export const INDUSTRIAL_VOCABULARY: StyleVocabulary = { /* … */ };
export const INDUSTRIAL_VALIDATORS: StyleValidators = { /* … */ };
export const INDUSTRIAL_TEMPLATES: StyleTemplates = { /* … */ };
export const INDUSTRIAL_REPORTER: RequirementReporter = { /* … */ };
export const IndustrialStyle: RequirementStyle = {
id: '@frenchexdev/requirements/styles/industrial',
version: '1.0.0',
vocabulary: INDUSTRIAL_VOCABULARY,
validators: INDUSTRIAL_VALIDATORS,
templates: INDUSTRIAL_TEMPLATES,
reporter: INDUSTRIAL_REPORTER,
fitCriterionAdapters: [],
};
export type IndustrialStyleType = typeof IndustrialStyle;Four slots, one Style, one exported type. The type is what a Requirement generic parameter binds to: class HeaterTripRequirement extends Requirement<IndustrialStyleType>. Once bound, the Requirement's shape narrows — the valid values for requirementKind, status, risk.level, verificationMethod, and statement.pattern are all constrained to what IndustrialStyle's vocabulary permits.
StyleVocabulary — the closed sets
Ten requirement kinds, thirteen lifecycle states, five SIL levels (four plus NonSIL), thirteen verification methods, ten rationale kinds, ten source kinds, nine statement patterns. Quote the first three:
requirementKinds: [
'Functional', 'Safety', 'Security', 'Availability', 'Performance',
'Interoperability', 'Maintainability', 'Regulatory',
'Environmental', 'Traceability',
],
statusWorkflow: {
states: [
'Draft', 'UnderInternalReview', 'TechnicalApproval',
'SafetyApproval', 'SecurityApproval', 'CustomerApproval',
'Implemented', 'FactoryTested', 'SiteTested',
'Commissioned', 'InProduction', 'UnderChange', 'Deprecated',
],
/* … */
},
riskTaxonomy: {
levels: ['SIL4', 'SIL3', 'SIL2', 'SIL1', 'NonSIL'],
/* … */
},requirementKinds: [
'Functional', 'Safety', 'Security', 'Availability', 'Performance',
'Interoperability', 'Maintainability', 'Regulatory',
'Environmental', 'Traceability',
],
statusWorkflow: {
states: [
'Draft', 'UnderInternalReview', 'TechnicalApproval',
'SafetyApproval', 'SecurityApproval', 'CustomerApproval',
'Implemented', 'FactoryTested', 'SiteTested',
'Commissioned', 'InProduction', 'UnderChange', 'Deprecated',
],
/* … */
},
riskTaxonomy: {
levels: ['SIL4', 'SIL3', 'SIL2', 'SIL1', 'NonSIL'],
/* … */
},Each list is closed. A Requirement with requirementKind: 'BusinessContinuity' — a reasonable-sounding value — will fail validation because 'BusinessContinuity' is not in the closed set. The Style does not invite the author to invent categories. It invites the author to select from an IEC-sourced vocabulary.
The closed-set discipline is the central claim of the register. A safety engineer who reads requirementKind: 'Safety' knows exactly what the author meant because 'Safety' maps to a clause in IEC 61508. A safety engineer who reads requirementKind: 'critical-stuff' in a free-text register cannot assume anything.
StyleValidators — the three load-bearing rules
Three invariants on top of the default shape checks:
// SAFETY RULE: if kind == Safety, risk.level must be SIL1-4 (never NonSIL)
if (s.requirementKind === 'Safety') {
const risk = s.risk as Record<string, unknown> | undefined;
const level = risk?.level as string | undefined;
if (!level || !['SIL1', 'SIL2', 'SIL3', 'SIL4'].includes(level)) {
errors.push({
path: 'risk.level',
message: 'Safety requirements must carry SIL1-4 per IEC 61508 (got '
+ String(level) + ')',
});
}
}// SAFETY RULE: if kind == Safety, risk.level must be SIL1-4 (never NonSIL)
if (s.requirementKind === 'Safety') {
const risk = s.risk as Record<string, unknown> | undefined;
const level = risk?.level as string | undefined;
if (!level || !['SIL1', 'SIL2', 'SIL3', 'SIL4'].includes(level)) {
errors.push({
path: 'risk.level',
message: 'Safety requirements must carry SIL1-4 per IEC 61508 (got '
+ String(level) + ')',
});
}
}This rule catches the single most common drafting mistake on industrial projects: a SIF whose LOPA was never run, tagged with the requirement kind Safety but carrying no SIL level. In Excel, the mistake survives to FAT. In IndustrialStyle, it fails at parse time.
The second rule ties the statement pattern to the verification method:
// SAFETY RULE: safety-function statements need SILProofTest | SILValidation
if (statement?.pattern === 'safety-function') {
const vm = s.verificationMethod as string | undefined;
if (!vm || !['SILProofTest', 'SILValidation', 'Certification'].includes(vm)) {
errors.push({
path: 'verificationMethod',
message: 'safety-function statements require SILProofTest, '
+ 'SILValidation, or Certification',
});
}
}// SAFETY RULE: safety-function statements need SILProofTest | SILValidation
if (statement?.pattern === 'safety-function') {
const vm = s.verificationMethod as string | undefined;
if (!vm || !['SILProofTest', 'SILValidation', 'Certification'].includes(vm)) {
errors.push({
path: 'verificationMethod',
message: 'safety-function statements require SILProofTest, '
+ 'SILValidation, or Certification',
});
}
}A Safety Instrumented Function cannot be verified by a unit test alone. The IEC 61511-1 §12 verification route demands a proof test or a route validation. The Style encodes the clause.
The third rule binds Regulatory requirements to their source:
// COMPLIANCE RULE: Regulatory requirements must cite a standard or regulation
if (s.requirementKind === 'Regulatory') {
const source = s.source as Record<string, unknown> | undefined;
const t = source?.type as string | undefined;
if (!t || !['iec-standard', 'iso-standard', 'isa-standard', 'regulation']
.includes(t)) {
errors.push({
path: 'source.type',
message: 'Regulatory requirements must cite a standard or regulation source',
});
}
}// COMPLIANCE RULE: Regulatory requirements must cite a standard or regulation
if (s.requirementKind === 'Regulatory') {
const source = s.source as Record<string, unknown> | undefined;
const t = source?.type as string | undefined;
if (!t || !['iec-standard', 'iso-standard', 'isa-standard', 'regulation']
.includes(t)) {
errors.push({
path: 'source.type',
message: 'Regulatory requirements must cite a standard or regulation source',
});
}
}A CE, ATEX, or 21 CFR Part 11 requirement with a stakeholder source is rejected. The auditor will notice the absent citation within minutes of opening the dossier. The Style makes the check happen before the dossier leaves the repository.
Three rules. Each a one-line decision, each tied to a named clause of a named standard, each enforced at every parse. The claim is not that these are sufficient for all of safety engineering. The claim is that without them, the register is not fit for safety engineering.
StyleTemplates — the nine ready-to-instantiate skeletons
const INDUSTRIAL_TEMPLATE_LIST: readonly RequirementTemplate[] = [
{ id: 'sif-iec61511', /* … */ },
{ id: 'process-interlock', /* … */ },
{ id: 'alarm-isa182', /* … */ },
{ id: 'cyber-zone-62443', /* … */ },
{ id: 'batch-recipe-isa88', /* … */ },
{ id: 'availability-sla', /* … */ },
{ id: 'regulatory-ce-atex', /* … */ },
{ id: 'gxp-21cfr11', /* … */ },
{ id: 'interoperability-opcua', /* … */ },
];const INDUSTRIAL_TEMPLATE_LIST: readonly RequirementTemplate[] = [
{ id: 'sif-iec61511', /* … */ },
{ id: 'process-interlock', /* … */ },
{ id: 'alarm-isa182', /* … */ },
{ id: 'cyber-zone-62443', /* … */ },
{ id: 'batch-recipe-isa88', /* … */ },
{ id: 'availability-sla', /* … */ },
{ id: 'regulatory-ce-atex', /* … */ },
{ id: 'gxp-21cfr11', /* … */ },
{ id: 'interoperability-opcua', /* … */ },
];Each template pre-fills the requirement kind, the statement pattern, the priority, the default fit criterion kinds, the rationale kind, and the verification method. Running requirement new --style industrial --template sif-iec61511 against a HAZOP finding takes a principal engineer about ninety seconds to fill in.
The template is not a string substitution; it is a typed scaffold. The wizard refuses to accept a SIL level outside 1–4 for the SIF template. It refuses a verification method outside the safety-function-compatible set. The template narrows the wizard's questions to the subset the standard expects. A junior engineer with a HAZOP row in hand can produce a valid SIF requirement without memorising IEC 61511; the tooling holds the memory.
RequirementReporter — the FAT/SAT-ready output
renderMarkdown(spec: unknown): string {
/* …emits the following shape… */
// # SIF-042 — Fired heater trip on low fuel-gas flow
//
// | Field | Value |
// |---|---|
// | Kind | Safety |
// | SIL / Risk | SIL2 |
// | Priority | Critical |
// | Status | SafetyApproval |
// | Verification method | SILProofTest |
// | Source | hazop (study=HAZOP-2025-REF-C, node=N-12, …) |
}renderMarkdown(spec: unknown): string {
/* …emits the following shape… */
// # SIF-042 — Fired heater trip on low fuel-gas flow
//
// | Field | Value |
// |---|---|
// | Kind | Safety |
// | SIL / Risk | SIL2 |
// | Priority | Critical |
// | Status | SafetyApproval |
// | Verification method | SILProofTest |
// | Source | hazop (study=HAZOP-2025-REF-C, node=N-12, …) |
}The reporter's output is the binder page. A FAT engineer opens the FAT binder, finds the requirement rendered in Markdown, exported to PDF by the pipeline, with every field populated by the type system. No hand editing. No drift. If the source .spec.json changes, the next CI run regenerates the PDF. The binder is a derived artefact, not a hand-maintained document.
The four slots — vocabulary, validators, templates, reporter — together form the surface of IndustrialStyle. The next section shows them in use.
A worked industrial Requirement
The running example of this series is the trace-explorer TUI. That Feature lives in DefaultStyle, as the series has established. For this chapter, the series will use a second example, explicitly contrived to exercise IndustrialStyle: a hypothetical reactor-temperature-limit Requirement for a hypothetical industrial-control DSL project. The example is deliberately detached from the requirements package itself — which is DefaultStyle dog-food — because forcing the package to use its own Industrial register would be the category error the later section names and rejects.
The Requirement, fully typed:
import { Requirement } from '@frenchexdev/requirements';
import type { IndustrialStyleType } from '@frenchexdev/requirements/styles/industrial';
export abstract class ReactorTempLimitRequirement
extends Requirement<IndustrialStyleType>
{
readonly id = 'REQ-SIF-101';
readonly title = 'Reactor R-101 high-temperature trip';
readonly requirementKind = 'Safety' as const;
readonly priority = 'Critical' as const;
readonly status = 'SafetyApproval' as const;
readonly risk = {
level: 'SIL3' as const,
ifNotMet: 'Runaway exothermic reaction; possible vessel rupture and release of toxic process stream.',
};
readonly verificationMethod = 'SILProofTest' as const;
readonly statement = {
pattern: 'safety-function' as const,
action: 'close XV-101A and XV-101B feed isolation valves AND open HV-101 emergency vent',
demand: 'TT-101A OR TT-101B reads > 175 °C for > 2 s (1oo2)',
responseTime: '500 ms',
silLevel: 'SIL3',
proofInterval: '6 months',
};
readonly source = {
type: 'hazop' as const,
study: 'HAZOP-R101-2025-C',
node: 'N-07',
deviation: 'more temperature',
date: '2025-11-04',
};
readonly rationale = {
kind: 'hazard-analysis' as const,
claim: 'LOPA scenario 3 for node N-07 assigns SIL3 target with 4 IPLs; SIF-101 is IPL #2.',
evidence: [
{ kind: 'study' as const, id: 'LOPA-R101-2025', section: 'Scenario 3' },
{ kind: 'precedent' as const, ref: 'INC-2021-117 — near-miss at sister plant SP-04' },
],
};
abstract closesFeedValvesWithin500ms(): ACResult;
abstract opensEmergencyVentInSequence(): ACResult;
abstract passesProofTestEverySixMonths(): ACResult;
abstract maintainsSIL3UnderDemand(): ACResult;
abstract rejectsSpuriousHighTemperatureSignal(): ACResult;
}import { Requirement } from '@frenchexdev/requirements';
import type { IndustrialStyleType } from '@frenchexdev/requirements/styles/industrial';
export abstract class ReactorTempLimitRequirement
extends Requirement<IndustrialStyleType>
{
readonly id = 'REQ-SIF-101';
readonly title = 'Reactor R-101 high-temperature trip';
readonly requirementKind = 'Safety' as const;
readonly priority = 'Critical' as const;
readonly status = 'SafetyApproval' as const;
readonly risk = {
level: 'SIL3' as const,
ifNotMet: 'Runaway exothermic reaction; possible vessel rupture and release of toxic process stream.',
};
readonly verificationMethod = 'SILProofTest' as const;
readonly statement = {
pattern: 'safety-function' as const,
action: 'close XV-101A and XV-101B feed isolation valves AND open HV-101 emergency vent',
demand: 'TT-101A OR TT-101B reads > 175 °C for > 2 s (1oo2)',
responseTime: '500 ms',
silLevel: 'SIL3',
proofInterval: '6 months',
};
readonly source = {
type: 'hazop' as const,
study: 'HAZOP-R101-2025-C',
node: 'N-07',
deviation: 'more temperature',
date: '2025-11-04',
};
readonly rationale = {
kind: 'hazard-analysis' as const,
claim: 'LOPA scenario 3 for node N-07 assigns SIL3 target with 4 IPLs; SIF-101 is IPL #2.',
evidence: [
{ kind: 'study' as const, id: 'LOPA-R101-2025', section: 'Scenario 3' },
{ kind: 'precedent' as const, ref: 'INC-2021-117 — near-miss at sister plant SP-04' },
],
};
abstract closesFeedValvesWithin500ms(): ACResult;
abstract opensEmergencyVentInSequence(): ACResult;
abstract passesProofTestEverySixMonths(): ACResult;
abstract maintainsSIL3UnderDemand(): ACResult;
abstract rejectsSpuriousHighTemperatureSignal(): ACResult;
}Run this through the IndustrialStyle validators and the following checks pass:
requirementKind: 'Safety'is in the closed set.risk.level: 'SIL3'satisfies the safety-requires-SIL rule.statement.pattern: 'safety-function'+verificationMethod: 'SILProofTest'satisfies the SIF-requires-proof-test rule.- Every slot of the
safety-functionpattern (action, demand, responseTime, silLevel, proofInterval) is populated. source.type: 'hazop'is a valid source kind, and its required slots (study, node, deviation, date) are all present.
Run the same Requirement through the reporter and it renders, in Markdown, with fields populated from the typed fields — no separate binder-side copy of the information to maintain.
Drop the risk.level field. The validator emits: "Safety requirements must carry SIL1-4 per IEC 61508 (got undefined)". The CI pipeline fails. The fix is to run the LOPA that was skipped or to change the requirementKind to Functional if the requirement is not actually a safety function.
Change verificationMethod to UnitTest. The validator emits: "safety-function statements require SILProofTest, SILValidation, or Certification". A unit test does not verify a SIF; the type system refuses the claim.
Change source.type to a free-form string. TypeScript refuses at compile time — source.type must be one of the ten declared source-kind identifiers. The attempt to smuggle a free-text source through fails before the file even reaches the validator.
This is what narrowing a Requirement's shape by its Style buys. The claim "this is a SIL 3 safety function" is no longer a prose claim in a Word document; it is a set of typed fields whose consistency is mechanically checked. An auditor who reads the .spec.json file can assume, without further audit, that the file passed the gate. The gate is the audit's first layer.
The 13-state heavy-gate lifecycle
Industrial requirements do not flow through Draft → Proposed → Approved. They flow through a longer pipeline whose shape matches the way a real integration project executes. Quote the full list from the source:
states: [
'Draft',
'UnderInternalReview',
'TechnicalApproval',
'SafetyApproval', // IEC 61508 functional-safety sign-off
'SecurityApproval', // IEC 62443 CSMS sign-off
'CustomerApproval', // URS/FRS/FDS agreed
'Implemented',
'FactoryTested', // FAT passed
'SiteTested', // SAT passed
'Commissioned', // in operation, conditionally accepted
'InProduction', // post warranty / fully accepted
'UnderChange', // MoC — Management of Change raised
'Deprecated',
],states: [
'Draft',
'UnderInternalReview',
'TechnicalApproval',
'SafetyApproval', // IEC 61508 functional-safety sign-off
'SecurityApproval', // IEC 62443 CSMS sign-off
'CustomerApproval', // URS/FRS/FDS agreed
'Implemented',
'FactoryTested', // FAT passed
'SiteTested', // SAT passed
'Commissioned', // in operation, conditionally accepted
'InProduction', // post warranty / fully accepted
'UnderChange', // MoC — Management of Change raised
'Deprecated',
],Thirteen states. Each a gate. Each producing auditable artefacts. The transitions are explicit:
transitions: [
{ from: 'Draft', to: 'UnderInternalReview' },
{ from: 'UnderInternalReview', to: 'TechnicalApproval' },
{ from: 'TechnicalApproval', to: 'SafetyApproval' },
{ from: 'SafetyApproval', to: 'SecurityApproval' },
{ from: 'SecurityApproval', to: 'CustomerApproval' },
{ from: 'CustomerApproval', to: 'Implemented' },
{ from: 'Implemented', to: 'FactoryTested' },
{ from: 'FactoryTested', to: 'SiteTested' },
{ from: 'SiteTested', to: 'Commissioned' },
{ from: 'Commissioned', to: 'InProduction' },
{ from: 'InProduction', to: 'UnderChange' },
{ from: 'UnderChange', to: 'Implemented' },
{ from: 'InProduction', to: 'Deprecated' },
// Rework loops
{ from: 'TechnicalApproval', to: 'Draft' },
{ from: 'SafetyApproval', to: 'Draft' },
{ from: 'SecurityApproval', to: 'Draft' },
{ from: 'CustomerApproval', to: 'Draft' },
],transitions: [
{ from: 'Draft', to: 'UnderInternalReview' },
{ from: 'UnderInternalReview', to: 'TechnicalApproval' },
{ from: 'TechnicalApproval', to: 'SafetyApproval' },
{ from: 'SafetyApproval', to: 'SecurityApproval' },
{ from: 'SecurityApproval', to: 'CustomerApproval' },
{ from: 'CustomerApproval', to: 'Implemented' },
{ from: 'Implemented', to: 'FactoryTested' },
{ from: 'FactoryTested', to: 'SiteTested' },
{ from: 'SiteTested', to: 'Commissioned' },
{ from: 'Commissioned', to: 'InProduction' },
{ from: 'InProduction', to: 'UnderChange' },
{ from: 'UnderChange', to: 'Implemented' },
{ from: 'InProduction', to: 'Deprecated' },
// Rework loops
{ from: 'TechnicalApproval', to: 'Draft' },
{ from: 'SafetyApproval', to: 'Draft' },
{ from: 'SecurityApproval', to: 'Draft' },
{ from: 'CustomerApproval', to: 'Draft' },
],Walk the pipeline the way a project does.
Draft — the engineer who received the HAZOP finding drafts the Requirement. The .spec.json is created; git records the commit. The status field reads Draft.
UnderInternalReview — a peer engineer from the same discipline reviews. No external signature required. A mini-review, a desk check. If the draft has obvious gaps, the peer pushes it back to Draft with notes in the commit message.
TechnicalApproval — the lead engineer signs off on the technical content. This gate catches structural problems the peer review missed. A SIF draft that does not correctly identify the initiating-event logic (2oo3 vs 1oo2) gets bounced here.
SafetyApproval — the functional-safety manager signs per IEC 61508 / 61511 CSM (Certified Safety Manager or equivalent). This gate is the moment the project commits to a SIL claim. Without this signature, no SIF moves forward. Rework loop back to Draft is explicit; in practice a SIF cycles this gate two or three times.
SecurityApproval — the CSMS owner signs per IEC 62443. Until around 2018 this gate did not exist on most projects; by 2026 it is non-negotiable because connected industrial systems are targeted. The signature attests that the Requirement's cybersecurity implications (zone placement, conduit traffic, authentication requirements) have been considered and satisfied.
CustomerApproval — the customer signs the URS/FRS/FDS. The Requirement is now jointly owned; a change after this point triggers a Management of Change (MoC) dossier, not a pull request.
Implemented — the PLC/DCS code is written. The IEC 61131-3 program carries tags that match the Requirement's action and demand. The Requirement's .spec.json and the program's source code are cross-referenced.
FactoryTested — the Factory Acceptance Test passes. The integrator's test rig drives the program; the trip is observed within the specified response time; the test report is signed.
SiteTested — the Site Acceptance Test passes. The same test is replayed on the real plant, with real instrumentation, before commissioning.
Commissioned — the system is running in the plant, conditionally accepted. The plant operations team has taken over; the integrator is still on hook for warranty.
InProduction — the conditional acceptance has expired; the system is fully owned by operations. The Requirement is a permanent fixture.
UnderChange — a field change has been raised. An MoC dossier is opened. The Requirement transitions back through Implemented → FactoryTested → SiteTested once the change is executed. The HistoryEntry log records the full loop.
Deprecated — the system is retired. The Requirement is archived, not deleted. The audit trail remains for the retention period (commonly 10–30 years in regulated sectors).
Every transition is append-only in the history field:
"history": [
{ "at": "2025-11-05T09:14:02Z",
"from": "Draft", "to": "UnderInternalReview",
"by": "j.martin@integrator.com",
"reason": "Desk check" },
{ "at": "2025-11-08T16:02:11Z",
"from": "SafetyApproval", "to": "Draft",
"by": "e.berger@integrator.com",
"reason": "SIL LOPA re-run — target changed from SIL2 to SIL3" },
{ "at": "2025-11-10T11:40:00Z",
"from": "Draft", "to": "SafetyApproval",
"by": "e.berger@integrator.com",
"reason": "Revised per LOPA-R101-2025 rev 2" }
]"history": [
{ "at": "2025-11-05T09:14:02Z",
"from": "Draft", "to": "UnderInternalReview",
"by": "j.martin@integrator.com",
"reason": "Desk check" },
{ "at": "2025-11-08T16:02:11Z",
"from": "SafetyApproval", "to": "Draft",
"by": "e.berger@integrator.com",
"reason": "SIL LOPA re-run — target changed from SIL2 to SIL3" },
{ "at": "2025-11-10T11:40:00Z",
"from": "Draft", "to": "SafetyApproval",
"by": "e.berger@integrator.com",
"reason": "Revised per LOPA-R101-2025 rev 2" }
]Who signed, when, why. An auditor walking the chain finds every decision recorded. A 21 CFR Part 11 inspector can verify that the electronic record is tamper-evident because the history is cryptographically bound to the git commit that appended it.
Contrast this with DefaultStyle's three-state workflow (Draft → Proposed → Approved). DefaultStyle is correct for a software team writing a library; three states capture the decision structure that team actually has. IndustrialStyle would be wrong for that team — the SafetyApproval gate refers to a role that does not exist on a software-only project, and forcing the project to fake the gate would produce false signatures in the history log.
The lifecycle shape is the single most visible difference between Styles. It is also the most project-specific. A Style's lifecycle must match the real organisational decision structure the project has, or the signatures become rituals instead of controls.
Where the register earns its weight
IndustrialStyle earns its weight when three conditions are met. Not two. Three.
Condition 1: the output reaches physical systems that can harm humans or the environment. A Requirement whose implementation fails can injure a plant operator, release a toxic stream, or ignite a process. The failure modes are physical. The rigour is justified because the consequence is physical.
Condition 2: regulatory review is mandatory. FDA, ANSSI, DNV, TÜV, Bureau Veritas, or a sector-specific regulator will read the dossier. The reader of the artefact is not just the team; it is a regulator with statutory authority. The register's vocabulary must match the regulator's expectation, or the dossier fails review.
Condition 3: insurance liability or statutory liability depends on demonstrable rigour. When the regulator finds the dossier incomplete, the operator's insurance cover is affected. The cost of a dossier that does not survive review is not goodwill; it is an uninsured claim.
Three conditions, all present, and IndustrialStyle earns its weight. The typed hazard reference replaces the Excel-row-number citation. The SIL validator catches the missing LOPA. The lifecycle audit trail replaces the Word change log. The 13-state gate structure matches the way the project's sign-off authority is already distributed. Every field pulls weight because every field answers a question the regulator or the insurer has already asked in past projects.
Quantify the weight on a mid-size integrator project. A principal engineer on an SIS delivery costs around €800 per day, fully loaded. A FAT rework loop — triggered by an uncovered AC discovered in front of the customer's witness engineer — costs two to five engineer-weeks of schedule plus the customer-goodwill tax. One avoided rework loop on a single project pays for the integrator-wide deployment of IndustrialStyle. The investment case is not speculative; it is a straightforward comparison with the incumbent Excel-based traceability.
Where the register does not earn its weight
The three conditions cut the other way. Any one of them absent, and IndustrialStyle is the wrong tool.
Absent condition 1: the output is software with no physical side effects. A CRUD web app. A library. A backend API. A static site generator. The failure modes are user inconvenience, data loss, or security incident — all serious, none of them the kind of physical harm SIL is calibrated to prevent. Imposing SIL vocabulary on this project imports ceremony without importing justification. The engineer fills in a silLevel field whose value the team cannot honestly defend. The signature in the SafetyApproval state is from a manager whose role is not actually functional-safety manager. The ceremony is hollow.
Absent condition 2: no safety engineer is on the team. The Style's validators enforce rules derived from IEC 61508 clauses. If no one on the project understands what those rules mean, the validators still fire — but the team cannot interpret the failures. A red build saying "Safety requirements must carry SIL1-4 per IEC 61508" is actionable only if somebody on the team can say, with justification, "this is a SIL 2 because the LOPA gave us a 10⁻² target". Without that person, the team will paper over the red build by picking a SIL level at random, which is worse than not having the field at all. Using SIL vocabulary without the validation apparatus is worse than not using it. The wrong SIL is more dangerous than a missing SIL because a wrong SIL creates false confidence.
Absent condition 3: speed-to-market is the dominating constraint and no regulator reviews the output. A startup shipping its MVP weekly cannot afford a 13-state lifecycle with independent verification gates. The register's ceremony is proportional to a delivery cadence the startup does not have. A three-state workflow (DefaultStyle or something even lighter) matches the project's tempo; IndustrialStyle does not.
The practical rule the package follows: when in doubt, use DefaultStyle. The cost of under-registering a Requirement is that a future reader has to do more interpretation. The cost of over-registering it is that the typed fields contain claims the team cannot defend, and the whole trust chain is weakened.
A concrete demonstration of the rule: the requirements package itself uses DefaultStyle, not IndustrialStyle. The package is a TypeScript library; its failure modes do not reach physical systems; its output does not pass through a regulatory review. Dog-fooding IndustrialStyle on the package would be a category error. The series' running example — FEATURE-TRACE-EXPLORER-TUI — satisfies DefaultStyle Requirements, and the dog-food narrative works correctly with that choice. A dog-food claim about IndustrialStyle, if the series tried to make one, would be hollow: we would be claiming the package validates itself with vocabulary its failure modes do not warrant.
The honest position is that IndustrialStyle is for projects that need IndustrialStyle, and most projects do not. The value of the Style system is that each Style is available for the projects where it fits, without forcing the other projects to adopt it.
Overlap with Catala-style legal registers
Legal writing has the same problem industrial writing has. The vocabulary is precise, the sources are typed (articles, decrees, case law), the reader is an auditor, and the consequence of a missing citation is not a stylistic flaw — it is a legal defect. A free-text register cannot represent "this clause derives from Article 5 of Directive 2006/42/EC as transposed by French Code du Travail L.4311-1" in a form a compiler can check.
Catala is a domain-specific language for law, co-designed by lawyers and computer scientists, targeted at the French tax and social-benefits code. Its central insight is that legal texts already have a formal structure — articles, sections, exceptions, references — that a programming language can mirror. A Catala program is a set of typed clauses, each annotated with the article it implements. The compiler refuses a clause whose article annotation is absent or syntactically malformed. The compilation output is executable rules plus a traceability report from code to statute.
The structural similarity between Catala and IndustrialStyle is not coincidental. Both encode a register where:
- A reader (auditor, regulator, judge, functional-safety manager) depends on the register's structure to do their job.
- A source citation is mandatory, with typed slots (article + section + date in Catala; study + node + deviation + date in IndustrialStyle's
hazopsource kind). - A closed vocabulary constrains what the author can say (Catala's legal concepts; IndustrialStyle's SIL levels, verification methods, lifecycle states).
- A lifecycle discipline tracks the register's evolution (Catala's revision system; IndustrialStyle's HistoryEntry log).
The IndustrialStyle hazop source kind is structurally a near-twin of a Catala article-citation:
{ kind: 'hazop', label: 'HAZOP finding', slots: [
{ name: 'study', type: 'string', required: true },
{ name: 'node', type: 'string', required: true },
{ name: 'deviation', type: 'string', required: true },
{ name: 'date', type: 'iso-date', required: true },
]},{ kind: 'hazop', label: 'HAZOP finding', slots: [
{ name: 'study', type: 'string', required: true },
{ name: 'node', type: 'string', required: true },
{ name: 'deviation', type: 'string', required: true },
{ name: 'date', type: 'iso-date', required: true },
]},versus a hypothetical Catala-inspired source kind for a legal requirements Style:
{ kind: 'statute', label: 'Statute citation', slots: [
{ name: 'code', type: 'string', required: true },
{ name: 'article', type: 'string', required: true },
{ name: 'section', type: 'string', required: false },
{ name: 'date', type: 'iso-date', required: true },
]},{ kind: 'statute', label: 'Statute citation', slots: [
{ name: 'code', type: 'string', required: true },
{ name: 'article', type: 'string', required: true },
{ name: 'section', type: 'string', required: false },
{ name: 'date', type: 'iso-date', required: true },
]},Both shapes encode the same insight: the source is not a URL and not a string; it is a typed record with required fields. The compiler refuses the Requirement if the record is incomplete.
The broader claim — the one the metacratie-compilateur/ series develops over eighteen chapters — is that rules with teeth all share this shape. Safety rules have teeth (physical harm if violated). Legal rules have teeth (statutory penalty if violated). Financial rules have teeth (regulatory penalty if violated). Each register has its own vocabulary, its own source kinds, its own closed lifecycle. The fact that IndustrialStyle and a hypothetical LawStyle are structurally cousins is not because they borrow from each other; it is because both are instances of the same pattern — the typed-register-with-auditor-as-reader pattern.
The dog-food package offers five Style presets today (Default, Industrial, Lean, Agile, Kanban). A LawStyle is not in the default set. Nothing in the type system prevents one from being added. The Style registry is open-closed in the sense that new Styles extend the registry without modifying its core. A jurist who wanted to add a Catala-inspired Style would implement the five slots (vocabulary, validators, templates, reporter, fitCriterionAdapters) and register the Style with the existing StyleRegistry API. The Requirement and Feature classes do not change; only the registry grows.
This is the open-endedness the series has been developing. A Style is not a theme. A Style is a parametrisation of the Requirement's shape by a vocabulary and a set of validators. The five shipped Styles exercise the pattern; they do not exhaust it. The pattern is available for every register with teeth.
Chapter 20 (DDD revisited) will develop this observation at the policy-as-type layer: a policy — safety policy, legal policy, security policy — is a Requirement whose Style encodes the policy's register. The series has been building to that generalisation quietly across the chapters; this section is the first place it becomes visible.
Diagram 1 — the SIL ladder
Figure 9.1 — The SIL ladder. Four levels, each a two-sided commitment: a probability-of-failure-on-demand budget (PFD, per IEC 61508-4) and a required rigour increment (inspection → independent verification → formal methods → formal proof). Representative industry examples on the right. The ladder is not about severity; it is about how much the development process has had to prove. Source: IEC 61508-1 §7.6 and Annex A.
Alt: Four-level ladder diagram showing SIL 1 at the bottom and SIL 4 at the top. Each level is a node annotated with its probability-of-failure-on-demand band (from 10^-2..10^-1 at SIL 1 to 10^-5..10^-4 at SIL 4) and its required rigour (inspection and review at SIL 1, independent verification at SIL 2, formal methods on critical parts at SIL 3, formal proof on critical parts at SIL 4). Each level has a dashed branch to a representative industry example: alarm annunciation at SIL 1, pump trip at SIL 2, burner-management or ESD at SIL 3, nuclear protection or aviation autoland at SIL 4. Arrows from each level point to the level above.
Diagram 2 — industrial trace through a hazard
Figure 9.2 — An industrial trace through a single hazard. The HAZOP finding at node N-07 is the origin. The Requirement carries the typed hazard reference and the SIL 3 claim. The Feature @Satisfies the Requirement. The AC methods are the measurable decompositions of the Feature's obligation. The test binds via @FeatureTest and @Verifies and its evidence kind is SILProofTest. The auditor — TÜV for the route validation, FDA for the audit trail — reads the chain end-to-end from the rendered Markdown. The chain is the same shape this series has been drawing since chapter 01; here it is in industrial clothes, with every node typed by IndustrialStyle.
Alt: Left-to-right flowchart with six nodes. Leftmost node: Hazard, labelled HAZOP N-07 with deviation "more temperature" leading to runaway reaction. Arrow to the next node: Requirement REQ-SIF-101, tagged IndustrialStyle, SIL 3, statement pattern safety-function. Arrow to: Feature ReactorTripFeature, which @Satisfies REQ-SIF-101. Arrow to: AC cluster listing closesFeedValvesWithin500ms, opensEmergencyVentInSequence, maintainsSIL3UnderDemand. Arrow to: Test on HIL rig, bound via @FeatureTest and @Verifies, producing SILProofTest evidence. Two dashed arrows converge on a rightmost node: Auditor, which represents TÜV route validation and FDA audit trail — one dashed arrow from the Test (evidence) and one dashed arrow from the Requirement (source traceability).
Running-example recap
The series' running example — FEATURE-TRACE-EXPLORER-TUI — remains in DefaultStyle. The @Satisfies(...) decorator binds it to ReqDiscoverableTraceabilityRequirement, ReqDogFoodRequirement, and ReqParallelDeliverableRequirement, all of which are Requirement<DefaultStyleType> instances. This chapter has not ported the example to IndustrialStyle because the port would be a category error — the package has no physical failure modes, no regulatory reader, and no insurance exposure; the three conditions that earn IndustrialStyle its weight are all absent.
The chapter's worked example — REQ-SIF-101 for a reactor high-temperature trip — is deliberately decoupled from the running example. It exists to exercise IndustrialStyle, not to extend the series' running thread. Readers wanting to see the running example work through a Style deep-dive should read chapter 08 (DefaultStyle) where the running example lives natively, and chapters 10 (LeanStyle) and 11 (AgileStyle) where the exercise repeats with different registers for which the example is a near-miss rather than a category error.
The pattern the deep-dive chapters establish: each Style gets its worked example. When the running example fits the Style, the chapter uses it. When it does not, the chapter invents an example native to the Style's domain and marks the distance from the running example honestly. The series refuses to force-fit the running example into registers where it does not belong, because forcing the fit would erase the point the chapter is making about when a register applies.
Related reading
- Chapter 08 — Style Deep-Dive: Default / ISO 29148 — the baseline register. Where the running example lives natively. This chapter's direct predecessor; reading it first makes the present chapter's contrasts visible.
- Chapter 10 — Style Deep-Dive: Lean / Toyota — the next Style, with a radically lighter lifecycle and a completely different reader (the gemba walker, not the auditor). Reading this chapter alongside chapter 09 clarifies how far the Style parameter can stretch.
- a-style-is-a-tiny-compiler.md — the piece of earlier writing this series quietly extends. A Style is a tiny compiler because it has a vocabulary (the lexer), validators (the parser), templates (the code generator), and a reporter (the output pass). IndustrialStyle is the heaviest of the five shipped compilers.
- metacratie-compilateur/ — the companion series on law-as-code. The IndustrialStyle↔CatalaStyle parallel drawn in this chapter's section 7 is developed at length there, especially in 07-law-dsl.md and 11-quality-gate.md.
- ops-dsl-ecosystem/ — the parallel series on operational DSLs for physical systems. The industrial register overlaps with the operational register at the boundary where software meets physical consequence; that series explores the overlap from the ops side.
Previous: Chapter 08 — Style Deep-Dive: Default / ISO 29148 | Next: Chapter 10 — Style Deep-Dive: Lean / Toyota