Standards Mapping v1
Contract ID: CONTRACT-STANDARDS-MAPPING-V1-032 Status: Draft Owner: ExportCenter Last Updated: 2026-05-30
Purpose
standards-mapping-v1 defines the local YAML mapping files and the deterministic ExportCenter bundle used to link external security standards to StellaOps control-register controls and offline evidence refs.
Audience: ExportCenter adapter implementers, EU-standards reviewers who author and approve the mapping YAML, and operators producing offline standards-coverage bundles.
The adapter is evidence-pointer-first: it copies the reviewed local mapping YAML, emits a normalized summary, signs the bundle index, and records blockers for draft or incomplete mappings instead of inferring standard coverage at export time.
The export adapter id is standards:mapping (StandardsMappingConstants.AdapterId). The source mapping schema version is the literal standards-mapping-v1 (StandardsMappingConstants.SourceSchemaVersion); the emitted bundle index and summary carry their own schema versions (standards-mapping-bundle-v1, standards-mapping-summary-v1).
Source Mapping Schema
Mapping sources MUST be local files using:
schemaVersion: standards-mapping-v1
mappingVersion: 0.1.0
status: draft
targetStandard:
id: iso27001-2022
name: ISO/IEC 27001:2022 Annex A
controlSet:
unit: control
totalControls: 93
requirementLevelCoverageStatus: null
coverage:
mappedControls: 93
coveragePercent: 100.0
thresholdPercent: 80.0
coverageBasis: annex-a-control-count
stellaApplicableControls: 93
nonApplicableControls: 0
review:
reviewStatus: pending-eu-standards-reviewer
sourceBasis: []
evidenceSets: {}
controlMappings:
- externalControlId: A.5.1
externalControlName: Policies for information security
stellaControlId: NIS2-01-NIS-POLICY
maturity: mixed
applicability: null
mappingNote: null
applicabilityReason: null
evidenceRefs:
- ref: evidence://policy/packs/security-policy/approved
sourceType: stella-native
The line-oriented YAML reader (StandardsMappingYamlReader.Parse) recognizes a fixed set of keys at fixed indents. Keys outside this set (for example targetStandard.sourceVersion, controlSet.families, or the richer review.* block carried in the current EU pack files) are tolerated but ignored. evidenceRefs may be inline list items or a YAML alias (*anchor) that resolves against a top-level evidenceSets anchor (&anchor); anchors are the convention used by the live mapping files.
Required source fields (validated by the reader; a missing required field throws InvalidOperationException):
| Field | Requirement |
|---|---|
schemaVersion | MUST equal standards-mapping-v1 (exact ordinal match). |
mappingVersion | REQUIRED. Source mapping version independent from the target standard. |
status | REQUIRED. Emitted as targetStandard.mappingStatus; draft/pending/blocked states are carried into bundle blockers under dependency mapping-status. |
targetStandard.id | REQUIRED. MUST equal the resolved target’s targetStandardId (e.g. iso27001-2022) or the build throws. |
targetStandard.name | REQUIRED. Display name for the mapped standard. |
targetStandard.controlSet.totalControls | REQUIRED integer. Total target controls in the mapped control set. |
targetStandard.controlSet.unit | OPTIONAL. Emitted as controlSetUnit. |
targetStandard.controlSet.requirementLevelCoverageStatus | OPTIONAL. Draft/pending/blocked values raise a requirement-level-coverage blocker. |
coverage.mappedControls | REQUIRED integer. MUST equal the number of controlMappings entries or the build throws a count-mismatch error. |
coverage.coveragePercent | REQUIRED number. Source coverage percentage. |
coverage.thresholdPercent | REQUIRED number. Minimum coverage threshold; emitted on coverage.thresholdPercent. |
coverage.coverageBasis | OPTIONAL. Emitted as coverage.coverageBasis. |
coverage.stellaApplicableControls | OPTIONAL integer. Applicability count, emitted when present. |
coverage.nonApplicableControls | OPTIONAL integer. Non-applicable count, emitted when present. |
review.reviewStatus | OPTIONAL. The ONLY key the reader consumes from the review block. Draft/pending/blocked values raise a mapping-review blocker. Other review.* keys (e.g. requiredReviewerRole, assignedReviewerId) are present in the live pack files for human governance but are NOT parsed by the adapter. |
sourceBasis[] | OPTIONAL. Local docs or approved local inputs used to derive the mapping; sorted ordinally in the summary. |
controlMappings[] | REQUIRED array. One row per mapped external control; re-sorted by external control id during parsing. |
controlMappings[].externalControlId | REQUIRED. Target control id. |
controlMappings[].externalControlName | REQUIRED. Target control title. |
controlMappings[].stellaControlId | REQUIRED. Stella control id from the control register. |
controlMappings[].maturity | REQUIRED. Free-text in the reader; the live pack convention is stella-native, mixed, or tenant-supplied (counted verbatim into maturityCounts). |
controlMappings[].applicability | OPTIONAL. |
controlMappings[].mappingNote | OPTIONAL. |
controlMappings[].applicabilityReason | OPTIONAL. |
controlMappings[].evidenceRefs[] | REQUIRED. At least one offline-replayable evidence pointer (each with ref + sourceType); a mapping with zero refs throws. |
Note:
coverage.coveragePercent,coverage.thresholdPercent, and thematurityset are NOT cross-validated by the adapter. The reader trusts the reviewed source values verbatim; onlycoverage.mappedControlsvs thecontrolMappingscount andtargetStandard.idvs the resolved target are enforced.
Evidence refs MUST use immutable or replayable local evidence schemes such as evidence://.... Live SaaS, regulator, or public web endpoints are not valid source refs for deterministic bundles.
Supported Targets
StandardsMappingTargets.SupportedTargets is the canonical alias list: iso27001, iec62443-4-1, iec62443-4-2, en303645, jrc-cra. The resolver (StandardsMappingTargets.Resolve) also accepts the canonical target ids iso27001-2022 and etsi-en303645 as input aliases; both normalize to the same bundle target (iso27001 / en303645).
| CLI / resolver alias | Mapping target id | Bundle target | Local file (relative to --mapping-dir) |
|---|---|---|---|
iso27001 (also iso27001-2022) | iso27001-2022 | iso27001 | iso27001-2022.yaml |
iec62443-4-1 | iec62443-4-1 | iec62443-4-1 | iec62443-4-1.yaml |
iec62443-4-2 | iec62443-4-2 | iec62443-4-2 | iec62443-4-2.yaml |
en303645 (also etsi-en303645) | etsi-en303645 | en303645 | etsi-en303645.yaml |
jrc-cra | jrc-cra | jrc-cra | jrc-cra.yaml |
The bundle target (third column) is the slug used in bundle entry paths (mappings/<bundle-target>.yaml) and in the output file name. The reviewed mapping files live in docs/europe/standards-mapping/, which is also the CLI default for --mapping-dir.
jrc-cra is publishable only when docs/europe/standards-mapping/jrc-cra.yaml has reviewed non-zero mapping rows, no blocked/pending/draft publication state, and no bundle blockers. This gate is enforced for the CLI path only (StandardsMappingCommandGroup.EnsureTargetPublishable); the core StandardsMappingAdapter.Build/BuildFromDirectory API will materialize a jrc-cra bundle that still carries blockers without throwing. Adapters MUST preserve local blocker states rather than fetching remote content.
CLI Surface
The bundle is produced by the stella standards-mapping command (StandardsMappingCommandGroup):
| Option | Behaviour |
|---|---|
--target | REQUIRED. One of the supported aliases above. |
--mapping-dir | Local directory of reviewed YAML. Defaults to docs/europe/standards-mapping. |
--output, -o | Output .tar.gz path. Defaults to <bundle-target>.standards-mapping.tar.gz. |
--overwrite | Allow overwriting an existing output file. |
--generated-at | ISO-8601 UTC timestamp embedded in the index. Defaults to the sprint fixture timestamp (2026-04-30T00:00:00Z). |
--signing-key-file | Local HMAC key file. Falls back to STELLAOPS_STANDARDS_MAPPING_HMAC_KEY when omitted; if neither is set the command fails. |
--signing-key-id | DSSE key id. When omitted, the adapter derives hmac-sha256:<first-16-hex-of-key-sha256>. |
--json | Emit a JSON summary (output path, hashes, mapped controls, blockers). |
When run inside ExportCenter (server-side export runs), authorization is the generic ExportCenter scope set from StellaOpsScopes: export.viewer (ExportViewer), export.operator (ExportOperator), export.admin (ExportAdmin). There is no standards-mapping-specific scope.
Bundle Schema
schemaVersionMUST be exactlystandards-mapping-bundle-v1.- Bundle media type is
application/vnd.stellaops.standards-mapping.v1+tar+gzip. - JSON payloads MUST be canonicalized with ordinal-sorted object keys and UTF-8 without a BOM (
CanonJson.Canonicalize). - Hashes in bundle JSON MUST be lowercase SHA-256 with the
sha256:prefix (CanonJson.Sha256Prefixed);checksums.txtlines use bare lowercase hex (CanonJson.Sha256Hex). - Tar entries are PAX format (
TarEntryFormat.Pax) with deterministic metadata: fixed mtime2025-01-01T00:00:00Z, uid/gid0, empty user/group names, and mode0644(user rw, group/other r). - Gzip uses
CompressionLevel.SmallestSizeand the mtime field in the gzip header is overwritten with the fixed timestamp so identical inputs produce byte-identical archives.
Per-entry media types embedded in index.json.entries:
| Bundle entry | Media type |
|---|---|
mappings/<bundle-target>.yaml | application/yaml |
mappings/<bundle-target>.summary.json | application/vnd.stellaops.standards-mapping.summary.v1+json |
signatures/index.dsse.json | application/vnd.dsse+json |
index.json (artifact, not self-listed in entries) | application/vnd.stellaops.standards-mapping.index.v1+json |
checksums.txt (artifact, not self-listed) | text/plain |
Bundle layout:
index.json
checksums.txt
mappings/<bundle-target>.yaml
mappings/<bundle-target>.summary.json
signatures/index.dsse.json
index.json is authoritative. index.json.entries lists only the two mappings/<bundle-target>.* files and the DSSE signature; index.json and checksums.txt are bundle members but are not self-listed in entries. checksums.txt is a deterministic convenience file for offline operators and is excluded from its own listing.
Required index.json Fields
| Field | Requirement |
|---|---|
schemaVersion | MUST equal standards-mapping-bundle-v1. |
generatedAt | UTC timestamp supplied by the caller (request.GeneratedAt, converted to UTC), not read from wall clock inside replay paths. |
target | Resolved bundle target slug: iso27001, iec62443-4-1, iec62443-4-2, en303645, or jrc-cra. |
targetStandard | Object: id, name, mappingVersion, mappingStatus (from source status), controlSetUnit (nullable), totalControls, requirementLevelCoverageStatus (nullable). |
source | Object: path (local source path, \ normalized to /), schemaVersion, sha256 (over the LF-normalized source bytes), sizeBytes. |
coverage | Object: mappedControls, coveragePercent, thresholdPercent, coverageBasis (nullable), reviewStatus (nullable), stellaApplicableControls (nullable), nonApplicableControls (nullable). |
entries | Bundle file list (the two mappings/* files and the DSSE signature) with path, mediaType, sha256, and sizeBytes. |
maturityCounts | Counts keyed by mapping maturity value (ordinal-sorted). |
evidenceSourceTypeCounts | Counts keyed by evidence sourceType (ordinal-sorted). |
blockers | Review/publication/requirement-level blockers (dependency, reason, sprintRef) derived from the source mapping state; de-duplicated and ordinal-sorted by dependency. |
signing | Object: status (always signed), payloadType, keyId, algorithm (resolved HMAC tag, see Signing), signaturePath (signatures/index.dsse.json). |
The summary.json payload carries the same targetStandard/source/coverage shape plus the full sourceBasis[] and controlMappings[] arrays, which are NOT inlined into index.json.
Signing And Verification
index.json is signed as the DSSE payload:
{
"payloadType": "application/vnd.stellaops.standards-mapping.index.v1+json",
"payload": "<base64 canonical index.json>",
"signatures": [
{
"keyid": "hmac-sha256:<key-ref>",
"alg": "HMAC-SHA256",
"sig": "<base64 HMAC over DSSE PAE>"
}
]
}
The HMAC DSSE envelope is the local deterministic ExportCenter signing pattern for this adapter. The MAC and the persisted alg / signing.algorithm tag are NOT hard-coded to HS256. They are resolved at sign time from the central crypto abstraction (ICryptoHmac.GetAlgorithmForPurpose(HmacPurpose.Signing), upper-cased) per the active regional compliance profile (CoC §7.4): typically HMAC-SHA256 on the world / fips / kcmvp / eidas profiles, HMAC-GOST3411 on the gost profile, and HMAC-SM3 on the sm profile. (A SignatureAlgorithm = "HS256" constant exists on StandardsMappingConstants but is unused.) Sender and verifier MUST run the same regional profile, since the verifier recomputes the expected algorithm string and the MAC bytes with the same abstraction.
keyid is the signing key id. When no key id is supplied, the adapter derives hmac-sha256:<first-16-hex-chars-of-the-key's-sha256> (CreateHmacSigningKey); an explicit --signing-key-id / KeyId is recorded verbatim and is not required to start with hmac-sha256:.
Production key sourcing remains deployment-specific; the adapter MUST fail if no signing key is configured (the ProcessAsync path returns a failed result, the Build path throws on an empty key). Offline verification (StandardsMappingBundleVerifier.Verify) re-reads the tar.gz entries, requires index.json, checks the bundle schema version, validates every entries[] row against the archive (size + sha256: hash), then verifies the DSSE envelope: payload type, base64 payload equals index.json bytes, a signature whose keyid and alg match the configured key and resolved algorithm, and a fixed-time HMAC comparison over the DSSE PAE (DSSEv1 <typelen> <type> <payloadlen> <payload>). checksums.txt is a convenience listing and is not re-validated by Verify.
Determinism Rules
- Exporters MUST consume local mapping files only.
- Exporters MUST reject any source ref that is an absolute URI whose scheme is not
file:(sohttp://,https://, and any other scheme are rejected with “is not a local file”); relative/absolute filesystem paths are resolved withPath.GetFullPath. - Exporters MUST NOT infer mappings from standard prose at bundle time.
- Exporters normalize source YAML line endings to LF (both
\r\nand bare\rbecome\n) and append a trailing\nif absent before hashing; the bundledmappings/<bundle-target>.yamland thesource.sha256are computed over these normalized bytes, not the raw file. - Identical source YAML, target, generated timestamp, and signing key MUST produce byte-identical bundles and identical bundle hashes (proven by the adjacent tests).
- Draft, pending, or blocked review states MUST remain visible as bundle blockers. The blocker heuristic raises a blocker when
status,review.reviewStatus, orrequirementLevelCoverageStatuscontains the substringdraft,pending, orblocked(case-insensitive). Each blocker carries asprintRefresolved from the target standard id.
Offline And Replay Expectations
- Adapter implementation:
src/ExportCenter/StellaOps.ExportCenter.Adapters.StandardsMapping/(StandardsMappingAdapter,StandardsMappingYamlReader,StandardsMappingDeterministicArchive,StandardsMappingBundleVerifier,StandardsMappingTargets,StandardsMappingModels). - CLI command:
src/Cli/StellaOps.Cli/Commands/StandardsMappingCommandGroup.cs(stella standards-mapping). - Reviewed mapping YAML:
docs/europe/standards-mapping/{iso27001-2022,iec62443-4-1,iec62443-4-2,etsi-en303645,jrc-cra}.yaml. - Adjacent tests:
src/ExportCenter/StellaOps.ExportCenter.Adapters.StandardsMapping.Tests/StandardsMappingAdapterTests.cs(and CLI tests insrc/Cli/__Tests/StellaOps.Cli.Tests/Commands/StandardsMappingCommandTests.cs). - The adapter tests prove: byte-identical output from identical inputs (
Build_SameInputs_...), DSSE signature verification (Verification.IsValid), local-file-only processing / remote-ref rejection (ProcessAsync_RejectsRemoteSourceRefs), all supported target aliases against the live files (BuildFromDirectory_CurrentLocalMappings_...), the precise missing-file blocker forjrc-cra, and adapter registration / config validation.
Known v1 Blockers
- Current review state of the live mapping files (as exercised by
BuildFromDirectory_CurrentLocalMappings_ProducesSignedBundles):iso27001-2022.yaml—status: draft,reviewStatus: pending-eu-standards-reviewer: still producesmapping-status/mapping-reviewblockers.etsi-en303645.yaml— draft: produces blockers.iec62443-4-1.yaml/iec62443-4-2.yaml— approved at the family level (reviewStatus: approved-family-level-by-eu-standards-reviewer,requirementLevelCoverageStatus: not-claimed-family-level-approved); none of these strings match thedraft/pending/blockedheuristic, so the bundles are currently blocker-free.jrc-cra.yaml—reviewStatus: reviewed-by-eu-standards-reviewer: blocker-free and publishable through the CLI gate.
- The adapter preserves draft/pending/blocked states in bundle blockers until
eu-standards-reviewercompletes the remaining reviews; it never clears a source-declared blocked state at export time.
