CRA Compliance-Officer End-to-End Scenarios
Currency check 2026-10-07: owner paths/source placement verified against
3238bd511a37e199ae99d4f17be2f13eabec8dd7. The supported boundary is/api/offlinekit/v1/exports(src/OfflineKit/StellaOps.OfflineKit.WebService/Program.cs). Re-verify the host mount and owning endpoint extension together. This is source verification; deployment and broader audit coverage are separate evidence. Other dated findings retain their stated dates. Re-verify:rg -n "MapGroup|Map.*Endpoints|AddHostedService" src/OfflineKit/StellaOps.OfflineKit.WebService/Program.cs, then inspect the endpoint extension or library pointer cited at the operation.
Pack: CRA Assurance Packs — claim boundaries manufacturer-self / manufacturer-customer-support
Audience: a manufacturer’s compliance officer preparing certifiable CRA evidence with Stella Ops, plus the QA / Test Automation agents who execute these scenarios. Each scenario is end-to-end: a narrative, the concrete UI route + API calls against the live docker stack, expected assertions, and a claim-boundary note.
These scenarios drive the Playwright spec src/Web/StellaOps.Web/tests/e2e/compliance/cra-compliance-officer.e2e.spec.ts, executed against the live stack with the colocated live-auth fixture (tests/e2e/compliance/live-auth.fixture.ts, real OIDC login → Bearer token → apiRequest/tenantId fixtures) and the dedicated config src/Web/StellaOps.Web/playwright.compliance.config.ts (base https://stella-ops.local, ignoreHTTPSErrors, no mocking, testDir tests/e2e/compliance). The config is modelled on playwright.integrations.config.ts (the live-docker-stack target) and is shared by the sibling NIS2/DORA compliance specs.
Run (AUTHOR ONLY — a later wave executes these one-by-one):
cd src/Web/StellaOps.Web
PLAYWRIGHT_BASE_URL=https://stella-ops.local \
STELLAOPS_ADMIN_USER=admin STELLAOPS_ADMIN_PASS='<the password your installer printed>' \
npx playwright test --config=playwright.compliance.config.ts \
tests/e2e/compliance/cra-compliance-officer.e2e.spec.ts
Each test.describe is tagged with its scenario id (@cra-s1…@cra-s6); run a single scenario with --grep @cra-s3.
Two packs, never one “CRA mode”
CRA support is split into two optional Assurance packs with different manufacturer responsibilities. The scenarios keep them distinct and keep the two readiness-flag pairs un-conflated:
| Pack | Claim boundary | Surface |
|---|---|---|
| CRA Product Security Pack | manufacturer-self | Stella’s own CVD, advisory feed, support lifecycle, public publication preflight, Article 14 timeline. |
| CRA Technical Documentation Pack | manufacturer-customer-support (or manufacturer-self when the product is Stella itself) | Deterministic technical-file + conformity-dossier exports. |
Two independent readiness splits the specs must assert separately:
- ExportCenter reports
signedExportReady(local signed bundle storage + signing key/provider + tenant export trust roots) andlivePublicationReady(public mailbox / intake key / rotation roles / support-lifecycle metadata) separately. A profile can besignedExportReady=truewhilelivePublicationReady=false. Response shape:GET /api/offlinekit/v1/exports/assurance/profiles/{id}/readiness(AssuranceExportReadinessResponse). - Notify reports Article 14 timeline readiness as
operatorHandoffReady(the default: produce-and-sign local handoff) andautoSubmitReady(explicit opt-in, gated on fresh-auth + tenant SRP config).autoSubmitReady=trueis not a live regulator receipt. Response shape:GET /api/v1/assurance/reporting-timeline-profiles/cra.article14.incident/readiness(AssuranceReportingProfileReadiness).
Forbidden product copy (never assert/emit; see docs/compliance/README.md→ Forbidden product copy): “CRA compliant mode”, “Turn on CRA compliant mode”, or any copy collapsing the two packs.
Live routes used (verified against src/)
| Purpose | Gateway route | Backend | Scope/policy |
|---|---|---|---|
| List Assurance export profiles | GET /api/offlinekit/v1/exports/assurance/profiles?frameworkId=cra | ExportCenter AssuranceExportEndpoints | export.viewer |
| One Assurance export profile | GET /api/offlinekit/v1/exports/assurance/profiles/{id} | ExportCenter | export.viewer |
| Assurance export readiness (two-flag split) | GET /api/offlinekit/v1/exports/assurance/profiles/{id}/readiness | ExportCenter AssuranceExportProfileRegistry.GetReadiness | export.viewer |
| List reporting-timeline profiles | GET /api/v1/assurance/reporting-timeline-profiles?frameworkId=cra | Notify AssuranceReportingProfileEndpoints | notify.viewer |
| Article 14 timeline profile | GET /api/v1/assurance/reporting-timeline-profiles/cra.article14.incident | Notify | notify.viewer |
| Article 14 readiness (two-flag split) | GET /api/v1/assurance/reporting-timeline-profiles/cra.article14.incident/readiness | Notify | notify.viewer |
| Public product-security metadata | GET /.well-known/stella-product-security.json | Router ProductMetadataMiddleware (root, fail-closed) | anonymous |
| Public security.txt | GET /.well-known/security.txt | Router | anonymous |
Current export prefix composition is verified in src/OfflineKit/StellaOps.OfflineKit.WebService/Program.cs and AssuranceExportEndpoints. Notify maps the reporting-timeline and profile endpoints in src/Notify/StellaOps.Notify.WebService/Program.cs; both hosts publish endpoint metadata when Router registration is enabled. An absent static rewrite in Gateway configuration does not establish route unavailability. Deployment and gateway reachability require current probes.
Profile id constants (verified)
- ExportCenter:
cra.technical-file(adaptercra:tech-file, schemacra-tech-file-v1),cra.conformity-dossier(adaptercra:conformity-dossier, schemaconformity-dossier-v1); both packcra.technical-documentation, frameworkcra,readinessPrerequisitesincludecra.technical-documentation.signed-exportandcra.product-security.publication. - Notify:
cra.article14.incident(frameworkcra, packcra.product-security, claimmanufacturer-self, payload contractenisa-incident-reporting-v1), milestonesearly-warning(PT24H),notification(PT72H),final-report(P14D).
Scenario CRA-S1 — CRA Technical File export (Annex VII)
Narrative. The compliance officer must assemble the Annex VII technical documentation file for a release the company places on the EU market. They first confirm Stella advertises the cra.technical-file Assurance profile, that it is deterministic + signed (offline-verifiable), and that ExportCenter reports whether the signed local bundle can be produced (signedExportReady) independently of any public publication.
Live drive (API).
GET /api/offlinekit/v1/exports/assurance/profiles?frameworkId=cra-> list MUST containcra.technical-fileandcra.conformity-dossieronly (framework filter excludes nis2/dora).GET /api/offlinekit/v1/exports/assurance/profiles/cra.technical-file-> descriptor.GET /api/offlinekit/v1/exports/assurance/profiles/cra.technical-file/readiness→ readiness with the two-flag split.
Expected assertions.
- Descriptor:
profileId == "cra.technical-file",frameworkId == "cra",packId == "cra.technical-documentation",adapterId == "cra:tech-file",schemaId == "cra-tech-file-v1",signingRequired == true,supportsOfflineVerification == true,offlineVerifier.payloadTypeIdis the CRA tech-file EU signing payload id, andcompatibilityRoutesincludes"stella export cra-tech-file". - Readiness:
signedExportReadyis a boolean;livePublicationReadyis present and is a boolean (NOT null) for CRA profiles;secretValuesExposed == false; no reasonsummaryleaks a secret/path.reasonCodesis a sorted string array. IfsignedExportReady == false, the blocking reasons are drawn from{storage-root-missing, signing-key-missing, signing-provider-missing, tenant-export-trust-roots-missing}and carry categorysigned-export.
Determinism + signature (Tier 2d, referenced — not run by these specs). The byte-identical bundle hash + DSSE/HMAC signature proof lives in the adapter test project src/OfflineKit/__Tests/StellaOps.ExportCenter.Adapters.Cra.Tests/ (CraTechFileAdapterTests, trait Intent=Regulatory) and the CLI stella export cra-tech-file / stella verify cra-tech-file (src/Cli/.../CraTechFileCommandTests). OfflineKit’s HTTP producer calls the production-signed adapter in current source. A discovery check alone does not prove production, deterministic bytes, or signature validation in the deployed stack; a current production/readback/download run needs separate evidence. For a discovery check, retain the descriptor assertions: signingRequired, supportsOfflineVerification, and the fixed schemaId/adapterId. Record the bundle-hash proof separately.
Claim boundary. manufacturer-self when the product is Stella itself, else manufacturer-customer-support. The export publishes nothing and implies no conformity route. Do not present readiness as “CRA compliant”.
Scenario CRA-S2 — Conformity dossier (Module A default)
Narrative. Having a technical file, the officer assembles the conformity dossier, which composes the Annex VII technical file (embedded at tech-file/cra-tech-file.tar.gz) with the selected conformity route overlay (Module A internal production control is the CRA-Q5 default for Stella; B+C and H remain available for customer products).
Live drive (API).
GET /api/offlinekit/v1/exports/assurance/profiles/cra.conformity-dossier.GET /api/offlinekit/v1/exports/assurance/profiles/cra.conformity-dossier/readiness.
Expected assertions.
- Descriptor:
profileId == "cra.conformity-dossier",packId == "cra.technical-documentation",adapterId == "cra:conformity-dossier",schemaId == "conformity-dossier-v1",signingRequired == true,supportsOfflineVerification == true.deterministicInputsMUST include"cra.technical-file.bundle"(the dossier references / embeds the technical file).compatibilityRoutesincludes"stella export conformity-dossier". - Readiness shares the CRA technical-documentation signed-export checks and reports
livePublicationReadyseparately, exactly like S1.
Structure proof (Tier 2d, referenced). That the dossier index references the embedded technical file (techFile.{path,indexHash,bundleHash,sizeBytes}), declared controls + evidence links (SBOM/advisory), and Module-A overlay is asserted byte-deterministically in src/OfflineKit/__Tests/StellaOps.ExportCenter.Adapters.Cra.Tests/ (ConformityBuild_ModuleA_ProducesDeterministicSelfAssessmentBundle) and via stella export conformity-dossier --module A. OfflineKit’s HTTP producer builds a signed inner technical file and assembles the conformity dossier in current source. The dossier index remains not-signed; discovery alone proves neither production nor signature acceptance in the deployed stack.
Claim boundary. Same as S1. The dossier is an evidence export; it does not make Stella the notified body and does not auto-file.
Scenario CRA-S3 — Article 14 incident reporting (24h / 72h / 14d)
Narrative. An actively-exploited vulnerability is confirmed in the manufacturer’s product. The officer needs the CRA Article 14 reporting timeline: early warning (24h), vulnerability notification (72h), final report (14 days). The default posture must be a locally signed operator handoff — Stella must NOT imply it auto-files with ENISA/an authority.
Live drive (API).
GET /api/v1/assurance/reporting-timeline-profiles?frameworkId=cra→ list containscra.article14.incident.GET /api/v1/assurance/reporting-timeline-profiles/cra.article14.incident→ profile with milestones.GET /api/v1/assurance/reporting-timeline-profiles/cra.article14.incident/readiness→ two-flag readiness.
Expected assertions.
- Profile:
timelineProfileId == "cra.article14.incident",frameworkId == "cra",packId == "cra.product-security",claimBoundary == "manufacturer-self",payloadContract == "enisa-incident-reporting-v1",signingRequired == true. - Milestones (by
milestoneId):early-warning→dueWithin == "PT24H";notification→"PT72H";final-report→"P14D". The CRA 14-day final report (P14D) differs from NIS2/DORAP1M— assert the exact ISO-8601 duration. operatorApproval.defaultPosture == "produce-and-sign-handoff",autoSubmitAvailable == true,autoSubmitDefault == false,freshAuthRequired == true. Channels includeenisa-cra.- Readiness:
operatorHandoffReadyandautoSubmitReadyare present as separate booleans. The default is operator handoff: when the profile is not fully configured,operatorHandoffReady == falseand theoperatorHandoffReasonCodescome from{publication-handoff-not-configured, operator-approval-missing, pgp-recipient-key-missing, webhook-signing-key-missing, csirt-destination-missing}.autoSubmitReasonCodesadditionally carriesauto-submit-disabledwhenever auto-submit is not explicitly enabled — proving auto-submit is opt-in.autoSubmitReadyMUST never betruewhileoperatorHandoffReadyisfalse(auto-submit reasons are a superset of handoff reasons).secretValuesExposed == false.
Claim boundary. manufacturer-self for Stella-as-manufacturer. For customer products the customer remains the reporting party; Stella provides timeline control + signed handoff packets only. Auto-submit is not a live regulator receipt.
Scenario CRA-S4 — Publication preflight (signed export ready ≠ live publication)
Narrative. The officer has a signed local technical-file/advisory artifact but must understand that public product-security publication (mailbox, intake key, advisory feed route, support-lifecycle metadata) is a separate gate. A blocked public publication preflight must NOT block local signed export, and the public well-known metadata document must fail closed when not fully configured.
Live drive (API + public route).
GET /api/offlinekit/v1/exports/assurance/profiles/cra.technical-file/readiness— observesignedExportReadyand the SEPARATElivePublicationReady.GET /.well-known/stella-product-security.json(anonymous, gateway root, Router-owned, fail-closed).GET /.well-known/security.txt(anonymous).
Expected assertions.
- The two flags are reported independently. The spec asserts the split exists (
signedExportReadypresent ANDlivePublicationReadypresent as a boolean), and that whenlivePublicationReady == falsethe live-publication reasons are drawn from{security-mailbox-unverified, intake-key-expired-or-missing, rotation-roles-missing, support-lifecycle-metadata-missing}with categorylive-publication— and thatsignedExportReadyis evaluated independently of those (signed export is gated only onsigned-exportcategory reasons). /.well-known/stella-product-security.json: either 200 with a JSON metadata document (when configured) or a fail-closed status (e.g. 404/503) — the route is anonymous and MUST NOT 500, and MUST NOT leak private keys/mailbox credentials. When 200, the body is JSON and anycsafFeedUrlis an HTTPS URL./.well-known/security.txt: anonymous, MUST NOT 500.- Claim-boundary copy guard: no asserted/observed text contains “CRA compliant mode”.
Claim boundary. manufacturer-self. The live-publication line is about Stella-as-manufacturer publication; it must never tell a customer tenant to configure Stella’s manufacturer mailbox for their own local evidence packs.
Scenario CRA-S5 — Pack separation invariant (no “CRA mode”)
Narrative. A cross-cutting guard: the two CRA packs are distinct surfaces with distinct claim boundaries, and the two readiness-flag pairs are never collapsed.
Live drive (API). Reuse S1 + S3 responses.
Expected assertions.
- The Technical Documentation pack id (
cra.technical-documentation, on the export profiles) is distinct from the Product Security pack id (cra.product-security, on the Article 14 timeline profile). The export profiles never carrycra.product-securityaspackId, and the timeline profile never carriescra.technical-documentation. - The export readiness uses
signedExportReady/livePublicationReady; the reporting readiness usesoperatorHandoffReady/autoSubmitReady. No response mixes the two flag names (the export readiness JSON has nooperatorHandoffReady; the reporting readiness JSON has nosignedExportReady). - Claim boundaries: export profiles’ CRA claim is technical-documentation; the Article 14 profile claim is
manufacturer-self.
Scenario CRA-S6 — Assurance Console shows two distinct CRA packs (UI)
Narrative. The compliance officer opens the operator-facing pack-discovery surface and must see the CRA Product Security Pack rendered as its own card, with no “CRA compliant mode” copy and no collapsed single pack.
Live drive (UI). Navigate /assurance (the canonical pack-discovery surface, Nis2ControlRegisterPageComponent’s sibling assurance shell). Render against live data via the OIDC-authenticated liveAuthPage fixture.
Expected assertions.
assurance-frameworks-pagetest id is visible.- Page body text does NOT contain “cra compliant mode” or “turn on cra”.
- The
assurance-pack-cra.product-securitycard is present and labelled “CRA Product Security Pack”.
Claim boundary. UI copy must use “CRA technical documentation support” / “Manufacturer publication preflight” — never “CRA compliant mode”.
Note: this UI scenario depends on the live
/assuranceshell exposing the documenteddata-testids. If those test ids differ in the running build, this is a test-id contract gap to record (do NOT edit shared FE component files).
Gaps noticed (report only — do NOT fix in this wave)
- HTTP production requires its own runtime evidence. The earlier discovery/readiness-only and CLI/library-only observation is superseded by current OfflineKit source: the Assurance API maps POST production, run readback, and bundle download, and the producer handles both CRA profiles. Adapter/CLI tests and discovery receipts do not establish a current deployed production run. Measure the HTTP run and verification separately; preserve the distinction between the signed technical file and the unsigned outer conformity-dossier index.
- Persisted-records gateway reachability is UNVERIFIED here. Notify maps
ReportingTimelineEndpointsand refreshes its advertised endpoint metadata. The earlier verdict based only on a missing static Gateway rewrite is superseded. A current authenticated gateway request is required to determine reachability ofGET /api/v1/regulatory/reporting-timelines?regime=cra; this source-only currency check executes no such request. - Readiness depends on stack config.
signedExportReady,livePublicationReady,operatorHandoffReady, andautoSubmitReadyreflect the live stack’s sealed configuration (export storage/signing/trust-roots; CRA publication mailbox/key/rotation/support-lifecycle; reporting handoff + auto-submit options). The specs therefore assert the structure and the split (both flags present, correct categories, no secret leakage, auto-submit ⊇ handoff reasons) rather than hard-coding a single boolean — the boolean depends on whether the operator has configured those surfaces.
