NIS2 Control Register

Audience: Policy implementers, Assurance/compliance engineers, and operators authoring tenant NIS2 registers. Scope: The Policy-owned Assurance control-register model and its NIS2 pack adapter — authoring workflow, evidence modes, runtime read/publish APIs, lifecycle events, and persistence.

Policy owns the Assurance control-register model, validation, lifecycle event stream, and PostgreSQL persistence. NIS2 is the first Assurance pack adapter over that model, not a Stella Ops-wide regional product assumption. The compatibility contract is NIS2 Control Register v1, the generic contract is Assurance Control Register v1, and the seed templates live under NIS2 control templates.

Product-facing NIS2 pack navigation lives in NIS2 Control Register. This guide remains the Policy implementation and source-contract guide.

The control register is an operator-facing aid for NIS2-obligated customers. Stella supplies Policy, evidence, export, and dashboard primitives; the regulated customer/operator remains responsible for applicability decisions, tenant evidence, regulator submissions, and legal conclusions.

Operating Boundary

Authoring Workflow

  1. Select the area template from docs/templates/nis2.
  2. Copy the complianceMappings.controls[] entry into the tenant Policy document.
  3. Replace placeholder roles, evidence refs, review dates, and metadata with tenant-specific values.
  4. Keep controlId, thematicArea, art21Refs, policyDocRef, responsibleRole, implementingControls[], evidencePointers[], lastReviewed, and nextReview aligned with the contract.
  5. For tenant-supplied evidence, use evidencePointers[].sourceType: tenant-supplied and record the role that submitted or approved the evidence.
  6. Validate and approve the Policy document through the normal Policy lifecycle.
  7. Publish a control-register snapshot before downstream SoA export tooling consumes it.

Authoring Example

Add complianceMappings to a Policy document when a policy pack needs to map gates, scopes, metrics, or runbooks to NIS2 Article 21 coverage:

version: "1.0"
complianceMappings:
  controls:
    - controlId: NIS2-08-SUPPLIER-EVIDENCE
      thematicArea: 8
      art21Refs: ["21(2)(d)", "21(2)(e)"]
      policyDocRef: docs/modules/policy/guides/nis2-control-register.md#supplier-evidence
      responsibleRole: vendor-risk-owner
      implementingControls:
        - kind: gate
          ref: policy.gates.supplier-assurance
        - kind: runbook
          ref: runbooks/vendor-evidence-review
      evidencePointers:
        - ref: tenant-evidence://supplier/quarterly-attestation
          sourceType: tenant-supplied
          submittedByRole: vendor-risk-owner
          collectedAt: 2026-04-30T10:00:00Z
      lastReviewed: 2026-04-30
      nextReview: 2026-07-30
rules:
  - name: Supplier Assurance Gate
    action: block

Evidence Modes

Policy supports three control-level evidence modes:

When evidenceMode is omitted, Policy derives it from evidencePointers[].sourceType.

Runtime Read API

Policy exposes the live tenant-scoped NIS2 compatibility register at GET /api/policy/control-register/nis2 with policy:read. Tenant identity comes from the envelope-bound tenant claim; an optional tenantId query value must match that resolved tenant.

The endpoint reads the Policy IControlRegisterRepository and returns:

Supported filters are thematicArea, evidenceMode, reviewStatus, and responsibleRole. Pagination uses limit plus opaque cursor, with deterministic ordering by thematicArea then controlId. Pass asOfDate=yyyy-MM-dd when tests, exports, or replay need stable review-status evaluation; otherwise Policy uses the service TimeProvider UTC date.

Tenant override refs are derived from control metadata keys such as tenantOverrideRef or tenantOverrideRefs. Downstream Graph, Web, and export consumers must call this API or a typed client over it instead of reading Policy persistence tables.

Policy also exposes the framework-aware Assurance projection at GET /api/policy/control-register/frameworks/nis2. This route returns assurance-control-register-v1 with frameworkId: nis2, packId: nis2, generic areaOrDomain fields, externalRefs, tenantEvidenceMode, and the same tenant guards and pagination semantics. Unknown framework ids fail closed with HTTP 404 and code UNKNOWN_FRAMEWORK; consumers must treat that as pack configuration drift, not as an empty register.

Applicability Decisions

Use metadata.applicability to make the decision audit-friendly:

metadata:
  applicability: not-applicable-to-stella-native-telemetry
  applicabilityReason: Stella Ops does not operate the customer's facilities, workforce controls, or training program.
  tenantEvidenceRequired: "true"

Use applicability: applicable when Stella evidence or mixed Stella/tenant evidence can support the control. Use not-applicable-to-stella-native-telemetry only when the customer still owns the thematic area and must provide evidence outside Stella telemetry.

Supplier Evidence

Supplier and third-party evidence normally maps to area 5. If a supplier artifact also supports training, HR, physical, or other non-native customer controls, keep a separate area-specific control row and use a tenant evidence pointer rather than expanding supplier data into Stella’s native telemetry model.

Supplier evidence should record:

Review And Publishing

Snapshots

Snapshots are built with Nis2ControlRegisterSnapshotBuilder. The builder sorts controls by thematic area and control id, sorts nested arrays, serializes canonical JSON, and emits sha256: hashes. PublishSnapshotAsync stores the NIS2 compatibility snapshot JSON with:

PublishSnapshotAsync is reachable from the runtime API via POST /api/policy/control-register/nis2/snapshot (scope policy:operate). The endpoint reads the tenant’s active controls, enforces the SoA completeness gate (every thematic area needs an explicit applicability decision; otherwise HTTP 422 SOA_INCOMPLETE), builds the deterministic snapshot, persists it, emits control:snapshot_published, and returns the contentHash / soaExportHash. The Console “Publish SoA snapshot” action (NIS2 control-register page) drives this endpoint and surfaces a 13-area completeness indicator that blocks publish until every area carries a decision. The optional request body accepts stellaRelease, evidenceLockerBundleRef, soaExportHash, and correlationId; omitted hashes default to the content-addressed snapshot hash.

Sprint 040 records the Policy-side content-addressed references. CLI/API rendering of the SoA export consumes those rows in downstream sprint 043.

EvidenceLocker regulatory artifact-ledger projection records two hash-only artifacts for a published snapshot: nis2-control-register-snapshot keyed by content_hash, and nis2-statement-of-applicability keyed by soa_export_hash. Their source refs point back to the Policy snapshot/event and the EvidenceLocker bundle ref; snapshot JSON remains Policy-owned.

For module-neutral consumers, AssuranceControlRegisterSnapshotBuilder projects the same rows or snapshot controls into assurance-control-register-v1 with frameworkId and packId while preserving the stored NIS2 snapshot contract.

Persistence

The Policy persistence baseline 001_v1_policy_baseline.sql creates (folded in from the pre-1.0 014_nis2_control_register.sql, which is archived under Migrations/_archived/pre_1.0/mig061/ and is not embedded):

The migration is embedded in StellaOps.Policy.Persistence and applied by the service startup migration hook already registered for Policy.