Assurance Reporting Timeline v1

Contract ID: CONTRACT-ASSURANCE-REPORTING-TIMELINE-V1-004 Status: Implemented Owner: Notify Guild Schema version: assurance-reporting-timeline-v1 Readiness schema version: assurance-reporting-readiness-v1

This contract describes the regulatory reporting-milestone profiles for Stella Ops Assurance packs — how each framework’s deadline-driven incident reporting (NIS2 Article 23, DORA Article 19, CRA Article 14) maps onto Notify’s single shared timeline service. It is for Notify integrators, Web/CLI authors rendering reporting deadlines, and reviewers verifying that handoff and auto-submit posture is gated correctly. It is the reporting-timeline member of the Assurance contract family (assurance-pack-v1is the catalogue entry point).

Purpose

assurance-reporting-timeline-v1 describes reporting milestone profiles for Assurance packs. Notify owns one shared reporting timeline service. NIS2, DORA, CRA, and future packs register milestone profiles over that service instead of creating duplicate state machines.

Profile Fields

FieldTypeRequiredNotes
schemaVersionstringyesMUST be assurance-reporting-timeline-v1.
timelineProfileIdstringyesStable profile id.
frameworkIdstringyesFramework id such as nis2, dora, or cra.
packIdstringyesPack id from assurance-pack-v1.
reportTypestringyesIncident, vulnerability, advisory, or other report type.
claimBoundarystringyesClaim boundary from assurance-pack-v1.
runtimeRegimeIdstringyesNotify IncidentReportTimelineService regime id backing this profile.
timelineSourcestringyesRuntime service name. Current value is IncidentReportTimelineService.
milestones[]arrayyesOrdered milestone definitions.
deadlinePolicyobjectyesRules for deadline calculation.
payloadContractstringyesPack-specific payload contract.
signingRequiredbooleanyesWhether transmission/handoff payloads must be signed.
operatorApprovalobjectyesApproval and auto-submit posture.
channels[]arrayyesSupported channels or channel families.
compatibilityRoutes[]string arraynoExisting routes or commands. Always includes the shared GET /api/v1/regulatory/reporting-timelines?regime=<regimeId> route for the profile’s runtime regime.

The list response wraps the profiles in an envelope:

{
  "schemaVersion": "assurance-reporting-timeline-v1",
  "items": [ /* AssuranceReportingTimelineProfile[] */ ],
  "sourceOfTruth": "Notify"
}

All three profiles (nis2.article23.incident, dora.article19.incident, cra.article14.incident) are built deterministically at service startup from the Notify IncidentReportTimelineService regimes by AssuranceReportingProfileRegistry and held in an immutable sorted map. Profiles are not tenant-scoped or persisted; only readiness (below) is tenant-aware.

Milestone Shape

{
  "milestoneId": "early-warning",
  "displayName": "Early warning",
  "timelineMilestoneName": "early_warning",
  "transmissionKind": "early_warning",
  "dueWithin": "PT24H",
  "stateWhenComplete": "early_warning_sent",
  "payloadRequired": true,
  "signatureRequired": true,
  "ledgerEventKind": "nis2.incident.reported",
  "requiredEvidence": ["incident_opened_event_ref", "impact_summary_ref"]
}

dueWithin uses ISO 8601 duration syntax. Deadline calculation must use the timeline’s recorded opening time and deterministic clock inputs, not local wall-clock reads during replay.

Operator Approval

{
  "defaultPosture": "produce-and-sign-handoff",
  "autoSubmitAvailable": true,
  "autoSubmitDefault": false,
  "freshAuthRequired": true,
  "approverAction": "submit"
}

Auto-submit must be opt-in per tenant/channel and audit logged. The default posture for regulator-facing flows is produce-and-sign handoff.

Example: NIS2 Article 23

{
  "schemaVersion": "assurance-reporting-timeline-v1",
  "timelineProfileId": "nis2.article23.incident",
  "frameworkId": "nis2",
  "packId": "nis2",
  "reportType": "incident",
  "claimBoundary": "operator-support",
  "runtimeRegimeId": "nis2",
  "timelineSource": "IncidentReportTimelineService",
  "milestones": [
    { "milestoneId": "early-warning", "displayName": "Early warning", "timelineMilestoneName": "early_warning", "transmissionKind": "early_warning", "dueWithin": "PT24H", "stateWhenComplete": "early_warning_sent", "payloadRequired": true, "signatureRequired": true, "ledgerEventKind": "nis2.incident.reported", "requiredEvidence": ["incident_opened_event_ref", "impact_summary_ref"] },
    { "milestoneId": "notification", "displayName": "Incident notification", "timelineMilestoneName": "notification", "transmissionKind": "notification", "dueWithin": "PT72H", "stateWhenComplete": "notification_sent", "payloadRequired": true, "signatureRequired": true, "ledgerEventKind": "nis2.incident.reported", "requiredEvidence": ["incident_classified_event_ref", "operator_approval_ref", "severity_classification_ref"] },
    { "milestoneId": "final-report", "displayName": "Final report", "timelineMilestoneName": "final_report", "transmissionKind": "final_report", "dueWithin": "P1M", "stateWhenComplete": "final_report_sent", "payloadRequired": true, "signatureRequired": true, "ledgerEventKind": "nis2.incident.reported", "requiredEvidence": ["early_warning_payload_hash", "final_report_bundle_ref", "notification_payload_hash", "operator_approval_ref"] }
  ],
  "deadlinePolicy": { "startEvent": "incident.opened", "timelineOpenedEventKind": "nis2.incident.opened", "timezone": "UTC", "replaySource": "timeline_opened_at" },
  "payloadContract": "nis2-incident-report-envelope-v1",
  "signingRequired": true,
  "operatorApproval": {
    "defaultPosture": "produce-and-sign-handoff",
    "autoSubmitAvailable": true,
    "autoSubmitDefault": false,
    "freshAuthRequired": true,
    "approverAction": "submit"
  },
  "channels": ["nis2-csirt.de", "nis2-csirt.fr", "nis2-csirt.nl", "signed-json-webhook", "pgp-email"],
  "compatibilityRoutes": ["/compliance/nis2/incidents", "/api/v1/regulatory/reporting-timelines?regime=nis2"]
}

Example: CRA Article 14

{
  "schemaVersion": "assurance-reporting-timeline-v1",
  "timelineProfileId": "cra.article14.incident",
  "frameworkId": "cra",
  "packId": "cra.product-security",
  "reportType": "incident-or-actively-exploited-vulnerability",
  "claimBoundary": "manufacturer-self",
  "runtimeRegimeId": "cra",
  "timelineSource": "IncidentReportTimelineService",
  "milestones": [
    { "milestoneId": "early-warning", "displayName": "Early warning", "timelineMilestoneName": "early_warning", "transmissionKind": "early_warning", "dueWithin": "PT24H", "stateWhenComplete": "early_warning_sent", "payloadRequired": true, "signatureRequired": true, "ledgerEventKind": "cra.incident.reported", "requiredEvidence": ["exploitation_evidence_ref", "incident_classified_event_ref"] },
    { "milestoneId": "notification", "displayName": "Vulnerability notification", "timelineMilestoneName": "vulnerability_notification", "transmissionKind": "vulnerability_notification", "dueWithin": "PT72H", "stateWhenComplete": "notification_sent", "payloadRequired": true, "signatureRequired": true, "ledgerEventKind": "cra.incident.reported", "requiredEvidence": ["incident_classified_event_ref", "operator_approval_ref", "vulnerability_notification_bundle_ref"] },
    { "milestoneId": "final-report", "displayName": "Final report", "timelineMilestoneName": "final_report", "transmissionKind": "final_report", "dueWithin": "P14D", "stateWhenComplete": "final_report_sent", "payloadRequired": true, "signatureRequired": true, "ledgerEventKind": "cra.incident.reported", "requiredEvidence": ["early_warning_payload_hash", "final_report_bundle_ref", "operator_approval_ref", "vulnerability_notification_payload_hash"] }
  ],
  "deadlinePolicy": { "startEvent": "incident.classified", "timelineOpenedEventKind": "cra.incident.classified", "timezone": "UTC", "replaySource": "timeline_opened_at" },
  "payloadContract": "enisa-incident-reporting-v1",
  "signingRequired": true,
  "operatorApproval": {
    "defaultPosture": "produce-and-sign-handoff",
    "autoSubmitAvailable": true,
    "autoSubmitDefault": false,
    "freshAuthRequired": true,
    "approverAction": "submit"
  },
  "channels": ["enisa-cra", "signed-json-webhook", "pgp-email"],
  "compatibilityRoutes": ["/api/v1/regulatory/reporting-timelines?regime=cra"]
}

Example: DORA Major ICT Incident

dora.article19.incident is registered by AssuranceReportingProfileRegistry (method BuildDoraProfile) alongside the NIS2 and CRA profiles, so it is returned from GET /api/v1/assurance/reporting-timeline-profiles?frameworkId=dora and exposes readiness like the other profiles. DORA timeline records also remain available through the shared regulatory timeline route with regime=dora.

{
  "schemaVersion": "assurance-reporting-timeline-v1",
  "timelineProfileId": "dora.article19.incident",
  "frameworkId": "dora",
  "packId": "dora",
  "reportType": "major-ict-incident",
  "claimBoundary": "operator-support",
  "runtimeRegimeId": "dora",
  "timelineSource": "IncidentReportTimelineService",
  "milestones": [
    { "milestoneId": "initial-report", "displayName": "Initial report", "timelineMilestoneName": "initial_report", "transmissionKind": "initial_report", "dueWithin": "PT4H", "stateWhenComplete": "early_warning_sent", "payloadRequired": true, "signatureRequired": true, "ledgerEventKind": "dora.incident.reported", "requiredEvidence": ["dora_major_incident_classification_ref", "incident_opened_event_ref"] },
    { "milestoneId": "intermediate-report", "displayName": "Intermediate report", "timelineMilestoneName": "intermediate_report", "transmissionKind": "intermediate_report", "dueWithin": "PT72H", "stateWhenComplete": "notification_sent", "payloadRequired": true, "signatureRequired": true, "ledgerEventKind": "dora.incident.reported", "requiredEvidence": ["incident_classified_event_ref", "intermediate_report_bundle_ref", "operator_approval_ref"] },
    { "milestoneId": "final-report", "displayName": "Final report", "timelineMilestoneName": "final_report", "transmissionKind": "final_report", "dueWithin": "P1M", "stateWhenComplete": "final_report_sent", "payloadRequired": true, "signatureRequired": true, "ledgerEventKind": "dora.incident.reported", "requiredEvidence": ["final_report_bundle_ref", "initial_report_payload_hash", "intermediate_report_payload_hash", "operator_approval_ref"] }
  ],
  "deadlinePolicy": { "startEvent": "incident.opened", "timelineOpenedEventKind": "dora.incident.opened", "timezone": "UTC", "replaySource": "timeline_opened_at" },
  "payloadContract": "dora-major-ict-incident-report-v1",
  "signingRequired": true,
  "operatorApproval": {
    "defaultPosture": "produce-and-sign-handoff",
    "autoSubmitAvailable": true,
    "autoSubmitDefault": false,
    "freshAuthRequired": true,
    "approverAction": "submit"
  },
  "channels": ["dora-competent-authority-handoff", "signed-json-webhook", "pgp-email"],
  "compatibilityRoutes": [
    "/docs/compliance/packs/dora/incident-reporting.md",
    "/api/v1/regulatory/reporting-timelines?regime=dora"
  ]
}

The DORA signed-handoff bundle is also produced and verified offline via the CLI stella export dora-incident-report and stella verify dora-incident-report commands (DoraIncidentCommandGroup), which consume a dora-major-incident-report.v1 input JSON. These CLI commands are not listed in the profile’s compatibilityRoutes array.

Runtime API

Notify exposes profile metadata through these endpoints. The read-only profile policy accepts any of notify.viewer, policy:read, or policy:audit:

The list endpoint accepts two optional, case-insensitive query filters, frameworkId and reportType; both are normalised (trimmed, lower-cased) and applied with ordinal equality. Results are ordered by frameworkId, then reportType, then timelineProfileId. Unknown timelineProfileId values return HTTP 404 with error code profile_not_found. The readiness endpoint also requires a tenant claim and returns HTTP 400 (tenant_missing) if it is absent.

The existing runtime timeline records remain at GET /api/v1/regulatory/reporting-timelines (schema eu-reporting-timeline.v1, also notify.viewer-scoped, supporting regime, incidentId, evidenceRef, state, active, limit, and cursor query parameters). Profiles only describe how a pack maps onto the shared Notify timeline; they do not create a second persistence model.

Readiness responses use schema assurance-reporting-readiness-v1 and include separate operatorHandoffReady and autoSubmitReady booleans. The full payload shape is:

{
  "schemaVersion": "assurance-reporting-readiness-v1",
  "timelineProfileId": "nis2.article23.incident",
  "frameworkId": "nis2",
  "packId": "nis2",
  "reportType": "incident",
  "claimBoundary": "operator-support",
  "tenantId": "tenant-a",
  "status": "configured",
  "operatorHandoffReady": true,
  "autoSubmitReady": false,
  "reasonCodes": ["auto-submit-disabled"],
  "operatorHandoffReasonCodes": ["configured"],
  "autoSubmitReasonCodes": ["auto-submit-disabled"],
  "secretValuesExposed": false,
  "updatedAt": "2026-01-01T00:00:00Z"
}

status is configured when operatorHandoffReady is true, otherwise blocked. reasonCodes is the deduplicated, ordinally sorted union of the handoff and auto-submit reasons (defaulting to ["configured"] when both lanes are clear). The per-lane operatorHandoffReasonCodes and autoSubmitReasonCodes arrays each fall back to ["configured"] when their lane has no blockers. tenantId is echoed from the resolved tenant claim. Reason codes are stable:

Reason codeMeaningSetup owner
configuredThe checked readiness lane has no missing inputs.Notify
csirt-destination-missingNo NIS2 CSIRT/NCA destination is configured for a CSIRT channel profile.Tenant/operator setting
pgp-recipient-key-missingThe public recipient key for encrypted email handoff is missing.Tenant/operator setting
webhook-signing-key-missingThe sealed signing key reference for signed webhook handoff is missing.Environment/secret configuration
operator-approval-missingFresh operator approval is not configured or present.Authority operator approval
auto-submit-disabledAuto-submit is available but intentionally disabled for the tenant/channel.Tenant/operator setting
publication-handoff-not-configuredLocal handoff/output publication is not configured.Environment or tenant setup, depending on deployment

Readiness is computed from per-profile boolean flags bound from configuration section notify:assuranceReporting (class AssuranceReportingOptions). Each profile id maps to an AssuranceReportingProfileReadinessOptions block keyed under Profiles; absent profiles default every flag to false (the most blocked state). The reason codes are derived as follows:

Config flagWhen false, emits reasonApplies when
CsirtDestinationConfiguredcsirt-destination-missingProfile has any nis2-csirt.* channel
PgpRecipientKeyConfiguredpgp-recipient-key-missingProfile has the pgp-email channel
WebhookSigningKeyConfiguredwebhook-signing-key-missingProfile has the signed-json-webhook channel
OperatorApprovalConfiguredoperator-approval-missingProfile’s operatorApproval.freshAuthRequired is true
PublicationHandoffConfiguredpublication-handoff-not-configuredAlways evaluated
AutoSubmitEnabledauto-submit-disabled (auto-submit lane only)Profile’s operatorApproval.autoSubmitAvailable is true

The auto-submit lane inherits every operator-handoff reason and additionally adds auto-submit-disabled, so autoSubmitReady can never be true unless operatorHandoffReady is also true.

Readiness payloads must set secretValuesExposed=false and must not include secret values, raw keys, endpoint credentials, storage roots, or provider tokens.

Runtime Record Fields

Runtime timeline records are returned by GET /api/v1/regulatory/reporting-timelines as ReportingTimelineItem entries (schema eu-reporting-timeline.v1). Each item carries:

The list response is wrapped in { schemaVersion, items[], cursor, sourceOfTruth: "Notify" }, where cursor is a base64url-encoded offset for pagination (default page size 100, maximum 500).

Determinism Rules

Implementation Grounding