CRA Compliance-Officer End-to-End Scenarios
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/export-center/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/export-center/v1/exports/assurance/profiles?frameworkId=cra | ExportCenter AssuranceExportEndpoints | export.viewer |
| One Assurance export profile | GET /api/export-center/v1/exports/assurance/profiles/{id} | ExportCenter | export.viewer |
| Assurance export readiness (two-flag split) | GET /api/export-center/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 |
Gateway prefix facts (verified in src/Router/StellaOps.Gateway.WebService/appsettings.json):
^/api/export-center/v1/exports(.*)→exportcenter.../v1/exports$1.^/api/v1/assurance/reporting-timeline-profiles(.*)→notify.../api/v1/assurance/reporting-timeline-profiles$1.- The persisted-records compatibility route
/api/v1/regulatory/reporting-timelines?regime=crais NOT wired in the gateway routing table — see Gaps.
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/export-center/v1/exports/assurance/profiles?frameworkId=cra→ list MUST containcra.technical-fileandcra.conformity-dossieronly (framework filter excludes nis2/dora).GET /api/export-center/v1/exports/assurance/profiles/cra.technical-file→ descriptor.GET /api/export-center/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/ExportCenter/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). The live HTTP stack does not execute the bundle (the Assurance export route is discovery/readiness-only). The spec asserts the descriptor’s determinism contract (signingRequired, supportsOfflineVerification, fixed schemaId/adapterId) and documents the bundle-hash proof as an out-of-band reference.
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/export-center/v1/exports/assurance/profiles/cra.conformity-dossier.GET /api/export-center/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/ExportCenter/StellaOps.ExportCenter.Adapters.Cra.Tests/ (ConformityBuild_ModuleA_ProducesDeterministicSelfAssessmentBundle) and via stella export conformity-dossier --module A. The live HTTP stack exposes the profile contract, not bundle execution.
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/export-center/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)
- Bundle execution is not a live HTTP surface. Deterministic CRA technical-file / conformity-dossier bundle production (and therefore the bundle-hash + signature-valid assertions) is CLI/library-only (
stella export cra-tech-file/conformity-dossier, and theStellaOps.ExportCenter.Adapters.Cra.Testsproject). The Assurance export HTTP route is discovery/readiness-only (AssuranceExportEndpointsexposes onlyprofiles/{id}/{id}/readiness). So a true end-to-end live-stack hash/signature assertion is not available over HTTP; the bundle-determinism proof must be driven via the CLI or the adapter Tier-2d test project. - Persisted-records route not gateway-routed. The documented compatibility route
GET /api/v1/regulatory/reporting-timelines?regime=cra(NotifyReportingTimelineEndpoints,/api/v1/regulatory/reporting-timelines) has no gateway rule insrc/Router/StellaOps.Gateway.WebService/appsettings.json. Persisted CRA timeline records are therefore not reachable throughhttps://stella-ops.localin the current stack; only thereporting-timeline-profilesdiscovery route is routed. - 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.
