diff --git a/.github/actions/build-proxies/action.yml b/.github/actions/build-proxies/action.yml index aa516f445..f1cc48595 100644 --- a/.github/actions/build-proxies/action.yml +++ b/.github/actions/build-proxies/action.yml @@ -25,6 +25,9 @@ inputs: description: "Name of the Component to deploy" required: true default: 'api' + nodejs_version: + description: "Node.js version, set by the CI/CD pipeline workflow" + required: true runs: using: composite @@ -34,7 +37,16 @@ runs: uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 24 + node-version: ${{ inputs.nodejs_version }} + + - name: "Cache node_modules" + uses: actions/cache@v4 + with: + path: | + **/node_modules + key: ${{ runner.os }}-node-${{ inputs.nodejs_version }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-${{ inputs.nodejs_version }}- - name: Npm install working-directory: . diff --git a/.github/workflows/pr_closed.yaml b/.github/workflows/pr_closed.yaml index 4cc942e99..d2c7e5285 100644 --- a/.github/workflows/pr_closed.yaml +++ b/.github/workflows/pr_closed.yaml @@ -62,3 +62,90 @@ jobs: --targetAccountGroup "nhs-notify-supplier-api-dev" \ --targetComponent "${{ matrix.component }}" \ --terraformAction "apply" + + check-event-schemas-version-change: + name: Check for event schemas package version change + needs: check-merge-or-workflow-dispatch + if: needs.check-merge-or-workflow-dispatch.outputs.deploy == 'true' + outputs: + version_changed: ${{ steps.check-version.outputs.version_changed }} + runs-on: ubuntu-latest + permissions: + contents: read + packages: read + steps: + - name: Checkout code + uses: actions/checkout@v5.0.0 + + - name: Setup NodeJS + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.nodejs_version }} + + - name: check if local version differs from latest published version + id: check-version + run: | + published_version=$(npm view @nhsdigital/nhs-notify-event-schemas-supplier-api --json 2>/dev/null | jq -r '.["dist-tags"].latest // "null"') + echo "Published version: $published_version" + + local_version=$(jq -r '.version' internal/events/package.json) + echo "Local version: $local_version" + + if [[ $local_version = $published_version ]]; then + echo "Local version is the same as the latest published version - skipping publish" + echo "version_changed=false" >> $GITHUB_OUTPUT + else + echo "Local version is different to the latest published version - publishing new version" + echo "version_changed=true" >> $GITHUB_OUTPUT + fi + + test-contract-provider: + name: "Test contracts (provider)" + needs: check-event-schemas-version-change + if: needs.check-event-schemas-version-change.outputs.version_changed == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + packages: read + steps: + - name: "Checkout code" + uses: actions/checkout@v5.0.0 + - name: Setup NodeJS + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.nodejs_version }} + - name: "Install dependencies" + run: npm ci + - name: "Run provider contract tests" + run: make test-contract-provider + env: + GITHUB_PACKAGES_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish-event-schemas: + name: Publish event schemas package to GitHub package registry + needs: + - check-event-schemas-version-change + - test-contract-provider + if: needs.check-event-schemas-version-change.outputs.version_changed == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v5.0.0 + + - name: Setup NodeJS + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.nodejs_version }} + registry-url: 'https://npm.pkg.github.com' + + - name: Install dependencies + run: npm ci + + - name: Publish to GitHub Packages + run: npm publish --workspace internal/events + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/stage-1-commit.yaml b/.github/workflows/stage-1-commit.yaml index a8d6b2fe2..d1c137b14 100644 --- a/.github/workflows/stage-1-commit.yaml +++ b/.github/workflows/stage-1-commit.yaml @@ -228,7 +228,7 @@ jobs: echo "changed=true" >> $GITHUB_OUTPUT fi - if content=$(git show origin/main:internal/events/schemas/package.json 2>/dev/null); then + if content=$(git show origin/main:internal/events/package.json 2>/dev/null); then version=$(jq -r .version <<< $content); else version=null; @@ -237,34 +237,33 @@ jobs: echo "Detected package version $version in main branch" echo "main_version=$version" >> $GITHUB_OUTPUT - check-schemas-generated: - name: Check event schemas have been regenerated - needs: detect-event-schema-package-changes - if: needs.detect-event-schema-package-changes.outputs.changed == 'true' - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: "Checkout code" - uses: actions/checkout@v4 - - # Simplified caching - template management has more complex caching of installed modules from another build step - - name: "Cache node_modules" - uses: actions/cache@v4 - with: - path: | - **/node_modules - key: ${{ runner.os }}-node-${{ inputs.nodejs_version }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node-${{ inputs.nodejs_version }}- - - - name: "Re-generate schemas" - run: | - npm ci --workspace internal/events - npm --workspace internal/events run gen:jsonschema - - - name: Check for schema changes - run: git diff --quiet internal/events/schemas +# check-schemas-generated: +# name: Check event schemas have been regenerated +# needs: detect-event-schema-package-changes +# if: needs.detect-event-schema-package-changes.outputs.changed == 'true' +# runs-on: ubuntu-latest +# permissions: +# contents: read +# steps: +# - name: "Checkout code" +# uses: actions/checkout@v4 +# +# - name: "Cache node_modules" +# uses: actions/cache@v4 +# with: +# path: | +# **/node_modules +# key: ${{ runner.os }}-node-${{ inputs.nodejs_version }}-${{ hashFiles('**/package-lock.json') }} +# restore-keys: | +# ${{ runner.os }}-node-${{ inputs.nodejs_version }}- +# +# - name: "Re-generate schemas" +# run: | +# npm ci +# npm --workspace internal/events run gen:jsonschema +# +# - name: Check for schema changes +# run: git diff --quiet internal/events/schemas check-schema-version-change: name: Check event schema version has been updated diff --git a/.github/workflows/stage-2-test.yaml b/.github/workflows/stage-2-test.yaml index d1705c22d..29c5cd0c3 100644 --- a/.github/workflows/stage-2-test.yaml +++ b/.github/workflows/stage-2-test.yaml @@ -48,6 +48,14 @@ jobs: steps: - name: "Checkout code" uses: actions/checkout@v5 + - name: "Cache node_modules" + uses: actions/cache@v4 + with: + path: | + **/node_modules + key: ${{ runner.os }}-node-${{ inputs.nodejs_version }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-${{ inputs.nodejs_version }}- - name: "Repo setup" run: | npm ci @@ -62,6 +70,14 @@ jobs: steps: - name: "Checkout code" uses: actions/checkout@v5 + - name: "Cache node_modules" + uses: actions/cache@v4 + with: + path: | + **/node_modules + key: ${{ runner.os }}-node-${{ inputs.nodejs_version }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-${{ inputs.nodejs_version }}- - name: "Repo setup" run: | npm ci @@ -90,6 +106,14 @@ jobs: steps: - name: "Checkout code" uses: actions/checkout@v5 + - name: "Cache node_modules" + uses: actions/cache@v4 + with: + path: | + **/node_modules + key: ${{ runner.os }}-node-${{ inputs.nodejs_version }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-${{ inputs.nodejs_version }}- - name: "Repo setup" run: | npm ci @@ -106,6 +130,14 @@ jobs: steps: - name: "Checkout code" uses: actions/checkout@v5 + - name: "Cache node_modules" + uses: actions/cache@v4 + with: + path: | + **/node_modules + key: ${{ runner.os }}-node-${{ inputs.nodejs_version }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-${{ inputs.nodejs_version }}- - name: "Repo setup" run: | npm ci diff --git a/.github/workflows/stage-3-build.yaml b/.github/workflows/stage-3-build.yaml index 73d74f4c2..5734d3304 100644 --- a/.github/workflows/stage-3-build.yaml +++ b/.github/workflows/stage-3-build.yaml @@ -109,3 +109,4 @@ jobs: runId: "${{ github.run_id }}" buildSandbox: true releaseVersion: ${{ github.head_ref || github.ref_name }} + nodejs_version: ${{ inputs.nodejs_version }} diff --git a/.github/workflows/stage-4-acceptance.yaml b/.github/workflows/stage-4-acceptance.yaml index ee42fca82..90bc140e2 100644 --- a/.github/workflows/stage-4-acceptance.yaml +++ b/.github/workflows/stage-4-acceptance.yaml @@ -36,138 +36,151 @@ on: type: string jobs: - environment-set-up: - name: "Environment set up" - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: "Checkout code" - uses: actions/checkout@v5 - - name: "Create infractructure" - run: | - echo "Creating infractructure..." - - name: "Update database" - run: | - echo "Updating database..." - - name: "Deploy application" - run: | - echo "Deploying application..." +# environment-set-up: +# name: "Environment set up" +# runs-on: ubuntu-latest +# timeout-minutes: 5 +# steps: +# - name: "Checkout code" +# uses: actions/checkout@v5 +# - name: "Create infractructure" +# run: | +# echo "Creating infractructure..." +# - name: "Update database" +# run: | +# echo "Updating database..." +# - name: "Deploy application" +# run: | +# echo "Deploying application..." + test-contract: name: "Contract test" runs-on: ubuntu-latest - needs: environment-set-up +# needs: environment-set-up timeout-minutes: 10 steps: - name: "Checkout code" uses: actions/checkout@v5 + - name: "Cache node_modules" + uses: actions/cache@v4 + with: + path: | + **/node_modules + key: ${{ runner.os }}-node-${{ inputs.nodejs_version }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-${{ inputs.nodejs_version }}- - name: "Run contract test" run: | make test-contract + env: + GITHUB_PACKAGES_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: "Save result" run: | echo "Nothing to save" - test-security: - name: "Security test" - runs-on: ubuntu-latest - needs: environment-set-up - timeout-minutes: 10 - steps: - - name: "Checkout code" - uses: actions/checkout@v5 - - name: "Run security test" - run: | - make test-security - - name: "Save result" - run: | - echo "Nothing to save" - test-ui: - name: "UI test" - runs-on: ubuntu-latest - needs: environment-set-up - timeout-minutes: 10 - steps: - - name: "Checkout code" - uses: actions/checkout@v5 - - name: "Run UI test" - run: | - make test-ui - - name: "Save result" - run: | - echo "Nothing to save" - test-ui-performance: - name: "UI performance test" - runs-on: ubuntu-latest - needs: environment-set-up - timeout-minutes: 10 - steps: - - name: "Checkout code" - uses: actions/checkout@v5 - - name: "Run UI performance test" - run: | - make test-ui-performance - - name: "Save result" - run: | - echo "Nothing to save" - test-integration: - name: "Integration test" - runs-on: ubuntu-latest - needs: environment-set-up - timeout-minutes: 10 - steps: - - name: "Checkout code" - uses: actions/checkout@v5 - - name: "Run integration test" - run: | - make test-integration - - name: "Save result" - run: | - echo "Nothing to save" - test-accessibility: - name: "Accessibility test" - runs-on: ubuntu-latest - needs: environment-set-up - timeout-minutes: 10 - steps: - - name: "Checkout code" - uses: actions/checkout@v5 - - name: "Run accessibility test" - run: | - make test-accessibility - - name: "Save result" - run: | - echo "Nothing to save" - test-load: - name: "Load test" - runs-on: ubuntu-latest - needs: environment-set-up - timeout-minutes: 10 - steps: - - name: "Checkout code" - uses: actions/checkout@v5 - - name: "Run load tests" - run: | - make test-load - - name: "Save result" - run: | - echo "Nothing to save" - environment-tear-down: - name: "Environment tear down" - runs-on: ubuntu-latest - needs: - [ - test-accessibility, - test-contract, - test-integration, - test-load, - test-security, - test-ui-performance, - test-ui, - ] - if: always() - timeout-minutes: 5 - steps: - - name: "Checkout code" - uses: actions/checkout@v5 - - name: "Tear down environment" - run: | - echo "Tearing down environment..." + +# Environment-based tests should run from notify-internal repo, not in public repo +# test-security: +# name: "Security test" +# runs-on: ubuntu-latest +# needs: environment-set-up +# timeout-minutes: 10 +# steps: +# - name: "Checkout code" +# uses: actions/checkout@v5 +# - name: "Run security test" +# run: | +# make test-security +# - name: "Save result" +# run: | +# echo "Nothing to save" +# test-ui: +# name: "UI test" +# runs-on: ubuntu-latest +# needs: environment-set-up +# timeout-minutes: 10 +# steps: +# - name: "Checkout code" +# uses: actions/checkout@v5 +# - name: "Run UI test" +# run: | +# make test-ui +# - name: "Save result" +# run: | +# echo "Nothing to save" +# test-ui-performance: +# name: "UI performance test" +# runs-on: ubuntu-latest +# needs: environment-set-up +# timeout-minutes: 10 +# steps: +# - name: "Checkout code" +# uses: actions/checkout@v5 +# - name: "Run UI performance test" +# run: | +# make test-ui-performance +# - name: "Save result" +# run: | +# echo "Nothing to save" +# test-integration: +# name: "Integration test" +# runs-on: ubuntu-latest +# needs: environment-set-up +# timeout-minutes: 10 +# steps: +# - name: "Checkout code" +# uses: actions/checkout@v5 +# - name: "Run integration test" +# run: | +# make test-integration +# - name: "Save result" +# run: | +# echo "Nothing to save" +# test-accessibility: +# name: "Accessibility test" +# runs-on: ubuntu-latest +# needs: environment-set-up +# timeout-minutes: 10 +# steps: +# - name: "Checkout code" +# uses: actions/checkout@v5 +# - name: "Run accessibility test" +# run: | +# make test-accessibility +# - name: "Save result" +# run: | +# echo "Nothing to save" +# test-load: +# name: "Load test" +# runs-on: ubuntu-latest +# needs: environment-set-up +# timeout-minutes: 10 +# steps: +# - name: "Checkout code" +# uses: actions/checkout@v5 +# - name: "Run load tests" +# run: | +# make test-load +# - name: "Save result" +# run: | +# echo "Nothing to save" +# environment-tear-down: +# name: "Environment tear down" +# runs-on: ubuntu-latest +# needs: +# [ +# test-accessibility, +# test-contract, +# test-integration, +# test-load, +# test-security, +# test-ui-performance, +# test-ui, +# ] +# if: always() +# timeout-minutes: 5 +# steps: +# - name: "Checkout code" +# uses: actions/checkout@v5 +# - name: "Tear down environment" +# run: | +# echo "Tearing down environment..." diff --git a/.github/workflows/stage-5-publish.yaml b/.github/workflows/stage-5-publish.yaml index 90ba59c14..1bf1ac454 100644 --- a/.github/workflows/stage-5-publish.yaml +++ b/.github/workflows/stage-5-publish.yaml @@ -144,7 +144,7 @@ jobs: asset_content_type: "application/gzip" - name: "zip html release asset" - # Git hub pages needs a single tar called artifact inside the zip. + # GitHub pages needs a single tar called artifact inside the zip. working-directory: ./artifacts/sdk-html-${{ inputs.version }} run: zip -r ../sdk-html-${{ inputs.version }}.zip . shell: bash @@ -160,7 +160,7 @@ jobs: asset_content_type: "application/gzip" - name: "zip sdk ts release asset" - # Git hub pages needs a single tar called artifact inside the zip. + # GitHub pages needs a single tar called artifact inside the zip. working-directory: ./artifacts/sdk-ts-${{ inputs.version }} run: zip -r ../sdk-ts-${{ inputs.version }}.zip . shell: bash @@ -176,7 +176,7 @@ jobs: asset_content_type: "application/gzip" - name: "zip sdk python release asset" - # Git hub pages needs a single tar called artifact inside the zip. + # GitHub pages needs a single tar called artifact inside the zip. working-directory: ./artifacts/sdk-python-${{ inputs.version }} run: zip -r ../sdk-python-${{ inputs.version }}.zip . shell: bash @@ -192,7 +192,7 @@ jobs: asset_content_type: "application/gzip" - name: "zip sdk csharp release asset" - # Git hub pages needs a single tar called artifact inside the zip. + # GitHub pages needs a single tar called artifact inside the zip. working-directory: ./artifacts/sdk-csharp-${{ inputs.version }} run: zip -r ../sdk-csharp-${{ inputs.version }}.zip . shell: bash @@ -208,7 +208,7 @@ jobs: asset_content_type: "application/gzip" - name: "zip api OAS specification release asset" - # Git hub pages needs a single tar called artifact inside the zip. + # GitHub pages needs a single tar called artifact inside the zip. working-directory: ./artifacts/api-oas-specification-${{ inputs.version }} run: zip -r ../api-oas-specification-${{ inputs.version }}.zip . shell: bash @@ -225,7 +225,7 @@ jobs: # Take out for now - might add again in the future # - name: "zip csharp server release asset" - # # Git hub pages needs a single tar called artifact inside the zip. + # # GitHub pages needs a single tar called artifact inside the zip. # working-directory: ./artifacts/server-csharp-${{ inputs.version }} # run: zip -r ../server-csharp-${{ inputs.version }}.zip . # shell: bash diff --git a/Makefile b/Makefile index 8ff9d89b9..2cd1466d1 100644 --- a/Makefile +++ b/Makefile @@ -68,13 +68,10 @@ construct-spec: guard-APIM_ENV $(MAKE) set-access APIM_ENV=$$APIM_ENV $(MAKE) set-security APIM_ENV=$$APIM_ENV - - build-json-oas-spec: guard-APIM_ENV $(MAKE) construct-spec APIM_ENV=$$APIM_ENV $(MAKE) publish-oas - build-yml-oas-spec: guard-APIM_ENV $(MAKE) construct-spec APIM_ENV=$$APIM_ENV $(MAKE) bundle-oas diff --git a/internal/events/jest.config.ts b/internal/events/jest.config.ts index dc227b1b8..84251001b 100644 --- a/internal/events/jest.config.ts +++ b/internal/events/jest.config.ts @@ -1,10 +1,49 @@ -export default { +import type { Config } from "jest"; + +export const baseJestConfig: Config = { preset: "ts-jest", - testEnvironment: "node", - testMatch: ["**/__tests__/**/*.ts", "**/?(*.)+(spec|test).ts"], - testPathIgnorePatterns: ["/dist/"], - moduleFileExtensions: ["ts", "js", "json", "node"], - transform: { - "^.+\\.ts$": ["ts-jest", { tsconfig: "/tsconfig.jest.json" }], + + // Automatically clear mock calls, instances, contexts and results before every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // The directory where Jest should output its coverage files + coverageDirectory: "./.reports/unit/coverage", + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: "babel", + + coverageThreshold: { + global: { + branches: 100, + functions: 100, + lines: 100, + statements: -10, + }, }, + + coveragePathIgnorePatterns: ["/__tests__/"], + transform: { "^.+\\.ts$": "ts-jest" }, + testPathIgnorePatterns: [".build"], + testMatch: ["**/?(*.)+(spec|test).[jt]s?(x)"], + + // Use this configuration option to add custom reporters to Jest + reporters: [ + "default", + [ + "jest-html-reporter", + { + pageTitle: "Test Report", + outputPath: "./.reports/unit/test-report.html", + includeFailureMsg: true, + }, + ], + ], + + // The test environment that will be used for testing + testEnvironment: "jsdom", }; + +export default baseJestConfig; diff --git a/internal/events/package.json b/internal/events/package.json index 85f504eb7..3baeb47d8 100644 --- a/internal/events/package.json +++ b/internal/events/package.json @@ -22,13 +22,16 @@ "license": "MIT", "main": "dist/index.js", "name": "@nhsdigital/nhs-notify-event-schemas-supplier-api", - "private": true, + "private": false, "publishConfig": { "registry": "https://npm.pkg.github.com" }, - "repository": "git@github.com:NHSDigital/nhs-notify-supplier-api.git", + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/NHSDigital/nhs-notify-supplier-api.git" + }, "scripts": { - "build": "tsc", + "build": "tsc -p ./tsconfig.build.json", "dev": "ts-node src/index.ts", "gen:asyncapi": "mkdir -p ./dist/asyncapi && ts-node src/cli/bundle-asyncapi.ts", "gen:jsonschema": "ts-node src/cli/generate-json.ts", @@ -40,11 +43,12 @@ "pregen:asyncapi": "npm run gen:jsonschema", "pregen:jsonschema": "rm -rf ./client-config/json", "prelint:schema": "npm run gen:jsonschema", + "prepublishOnly": "npm run build", "pretest:unit": "npm run gen:jsonschema", "start": "node dist/index.js", "test": "npm run test:unit", "test:unit": "jest" }, "types": "dist/index.d.ts", - "version": "1.0.0" + "version": "1.0.1" } diff --git a/internal/events/schemas/domain/.gitignore b/internal/events/schemas/domain/.gitignore new file mode 100644 index 000000000..a6c57f5fb --- /dev/null +++ b/internal/events/schemas/domain/.gitignore @@ -0,0 +1 @@ +*.json diff --git a/internal/events/schemas/domain/letter.schema.json b/internal/events/schemas/domain/letter.schema.json deleted file mode 100644 index e9c1dbb01..000000000 --- a/internal/events/schemas/domain/letter.schema.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "type": "object", - "properties": { - "domainId": { - "type": "string" - }, - "origin": { - "title": "Letter origin", - "description": "Identifiers captured from the original event that introduced the letter to the supplier-api domain.\n\nThe identifier will be included as the origin domain in the subject of any corresponding events emitted by the supplier-api domain.", - "examples": [ - { - "domain": "letter-rendering", - "subject": "customer/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-rendering/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", - "event": "00f3b388-bbe9-41c9-9e76-052d37ee8988" - } - ], - "type": "object", - "properties": { - "domain": { - "title": "Domain ID", - "description": "The domain which requested this letter", - "type": "string" - }, - "source": { - "title": "Event source", - "description": "The source of the event which created this letter", - "type": "string" - }, - "subject": { - "title": "Event subject", - "description": "The subject of the event which created this letter, scoped to source", - "type": "string" - }, - "event": { - "title": "Event ID", - "description": "The ID of the event which created this letter", - "type": "string" - } - }, - "required": [ - "domain", - "source", - "subject", - "event" - ] - }, - "specificationId": { - "title": "Specification ID", - "description": "Reference to the letter specification which was used to produce a letter pack for this request.", - "examples": [ - "1y3q9v1zzzz" - ], - "type": "string" - }, - "groupId": { - "title": "Group ID", - "description": "Identifier for the group which this letter assigned to for reporting purposes.", - "examples": [ - "client_template", - "00f3b388-bbe9-41c9-9e76-052d37ee8988_20a1ab22-6136-47ae-ac0f-989f382be8df" - ], - "type": "string" - }, - "status": { - "title": "Letter Status", - "description": "The status of a letter in the supplier-api domain.", - "examples": [ - "ACCEPTED", - "REJECTED", - "PRINTED" - ], - "type": "string", - "enum": [ - "PENDING", - "ACCEPTED", - "REJECTED", - "PRINTED", - "ENCLOSED", - "CANCELLED", - "DISPATCHED", - "FAILED", - "RETURNED", - "FORWARDED", - "DELIVERED" - ] - }, - "reasonCode": { - "title": "Reason Code", - "description": "Optional reason code for the status change, if applicable.", - "examples": [ - "R01", - "R08" - ], - "type": "string" - }, - "reasonText": { - "title": "Reason Text", - "description": "Optional human-readable reason for the status change, if applicable.", - "examples": [ - "Undeliverable", - "Recipient moved" - ], - "type": "string" - } - }, - "required": [ - "domainId", - "origin", - "specificationId", - "groupId", - "status" - ] -} \ No newline at end of file diff --git a/internal/events/schemas/events/.gitignore b/internal/events/schemas/events/.gitignore new file mode 100644 index 000000000..a6c57f5fb --- /dev/null +++ b/internal/events/schemas/events/.gitignore @@ -0,0 +1 @@ +*.json diff --git a/internal/events/schemas/events/letter.ACCEPTED.schema.json b/internal/events/schemas/events/letter.ACCEPTED.schema.json deleted file mode 100644 index 61d11fe67..000000000 --- a/internal/events/schemas/events/letter.ACCEPTED.schema.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "title": "letter.ACCEPTED Event", - "description": "Event schema for letter status change to ACCEPTED", - "type": "object", - "properties": { - "specversion": { - "title": "CloudEvents spec version", - "description": "CloudEvents specification version (fixed to 1.0).", - "examples": [ - "1.0" - ], - "type": "string", - "enum": [ - "1.0" - ] - }, - "id": { - "title": "Event ID", - "description": "Unique identifier for this event instance (UUID).", - "examples": [ - "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111" - ], - "type": "string", - "minLength": 1, - "format": "uuid", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" - }, - "source": { - "title": "Event Source", - "description": "Logical event producer path within the supplier-api domain", - "type": "string", - "pattern": "^\\/data-plane\\/supplier-api(?:\\/.*)?$" - }, - "subject": { - "title": "Event Subject", - "description": "Resource path (no leading slash) within the source made of segments separated by '/'.", - "examples": [ - "letter-origin/letter-rendering/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479" - ], - "type": "string", - "pattern": "^letter-origin\\/[a-z0-9-]+\\/letter\\/[^/]+(?:\\/.*)?" - }, - "type": { - "title": "Letter ACCEPTED event type", - "description": "Event type using reverse-DNS style", - "examples": [ - "uk.nhs.notify.supplier-api.letter.ACCEPTED.v1" - ], - "type": "string", - "enum": [ - "uk.nhs.notify.supplier-api.letter.ACCEPTED.v1" - ] - }, - "time": { - "title": "Event Time", - "description": "Timestamp when the event occurred (RFC 3339).", - "examples": [ - "2025-10-01T10:15:30.000Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "datacontenttype": { - "title": "Data Content Type", - "description": "Media type for the data field (fixed to application/json).", - "examples": [ - "application/json" - ], - "type": "string", - "enum": [ - "application/json" - ] - }, - "dataschema": { - "title": "Data Schema URI", - "description": "URI of a schema that describes the event data\n\nData schema version must match the major version indicated by the type", - "examples": [ - "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json" - ], - "type": "string", - "pattern": "^https:\\/\\/notify\\.nhs\\.uk\\/cloudevents\\/schemas\\/supplier-api\\/letter.ACCEPTED.1\\.\\d+\\.\\d+\\.schema.json$" - }, - "data": { - "type": "object", - "properties": { - "domainId": { - "type": "string" - }, - "origin": { - "title": "Letter origin", - "description": "Identifiers captured from the original event that introduced the letter to the supplier-api domain.\n\nThe identifier will be included as the origin domain in the subject of any corresponding events emitted by the supplier-api domain.", - "examples": [ - { - "domain": "letter-rendering", - "subject": "customer/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-rendering/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", - "event": "00f3b388-bbe9-41c9-9e76-052d37ee8988" - } - ], - "type": "object", - "properties": { - "domain": { - "title": "Domain ID", - "description": "The domain which requested this letter", - "type": "string" - }, - "source": { - "title": "Event source", - "description": "The source of the event which created this letter", - "type": "string" - }, - "subject": { - "title": "Event subject", - "description": "The subject of the event which created this letter, scoped to source", - "type": "string" - }, - "event": { - "title": "Event ID", - "description": "The ID of the event which created this letter", - "type": "string" - } - }, - "required": [ - "domain", - "source", - "subject", - "event" - ] - }, - "specificationId": { - "title": "Specification ID", - "description": "Reference to the letter specification which was used to produce a letter pack for this request.", - "examples": [ - "1y3q9v1zzzz" - ], - "type": "string" - }, - "groupId": { - "title": "Group ID", - "description": "Identifier for the group which this letter assigned to for reporting purposes.", - "examples": [ - "client_template", - "00f3b388-bbe9-41c9-9e76-052d37ee8988_20a1ab22-6136-47ae-ac0f-989f382be8df" - ], - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "ACCEPTED" - ] - }, - "reasonCode": { - "title": "Reason Code", - "description": "Optional reason code for the status change, if applicable.", - "examples": [ - "R01", - "R08" - ], - "type": "string" - }, - "reasonText": { - "title": "Reason Text", - "description": "Optional human-readable reason for the status change, if applicable.", - "examples": [ - "Undeliverable", - "Recipient moved" - ], - "type": "string" - } - }, - "required": [ - "domainId", - "origin", - "specificationId", - "groupId", - "status" - ] - }, - "traceparent": { - "title": "Traceparent", - "description": "W3C Trace Context traceparent header value.", - "examples": [ - "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" - ], - "type": "string", - "minLength": 1, - "pattern": "^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$" - }, - "tracestate": { - "title": "Tracestate", - "description": "Optional W3C Trace Context tracestate header value.", - "examples": [ - "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE" - ], - "type": "string" - }, - "partitionkey": { - "title": "Partition Key", - "description": "Partition / ordering key (lowercase alphanumerics and hyphen, 1-64 chars).", - "examples": [ - "customer-920fca11" - ], - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^[a-z0-9-]+$" - }, - "recordedtime": { - "title": "Recorded Time", - "description": "Timestamp when the event was recorded/persisted (should be >= time).", - "examples": [ - "2025-10-01T10:15:30.250Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "sampledrate": { - "title": "Sampled Rate", - "description": "Sampling factor: number of similar occurrences this event represents.", - "examples": [ - 5 - ], - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "sequence": { - "title": "Sequence", - "description": "Zero-padded 20 digit numeric sequence (lexicographically sortable).", - "examples": [ - "00000000000000000042" - ], - "type": "string", - "pattern": "^\\d{20}$" - }, - "severitytext": { - "title": "Severity Text", - "description": "Log severity level name.", - "examples": [ - "DEBUG" - ], - "type": "string", - "enum": [ - "TRACE", - "DEBUG", - "INFO", - "WARN", - "ERROR", - "FATAL" - ] - }, - "severitynumber": { - "title": "Severity Number", - "description": "Numeric severity (TRACE=0, DEBUG=1, INFO=2, WARN=3, ERROR=4, FATAL=5).", - "examples": [ - 1 - ], - "type": "integer", - "minimum": 0, - "maximum": 5 - }, - "dataclassification": { - "title": "Data Classification", - "description": "Data sensitivity classification.", - "examples": [ - "restricted" - ], - "type": "string", - "enum": [ - "public", - "internal", - "confidential", - "restricted" - ] - }, - "dataregulation": { - "title": "Data Regulation", - "description": "Regulatory regime tag applied to this data.", - "examples": [ - "ISO-27001" - ], - "type": "string", - "enum": [ - "GDPR", - "HIPAA", - "PCI-DSS", - "ISO-27001", - "NIST-800-53", - "CCPA" - ] - }, - "datacategory": { - "title": "Data Category", - "description": "Data category classification (e.g. standard, special-category).", - "examples": [ - "sensitive" - ], - "type": "string", - "enum": [ - "non-sensitive", - "standard", - "sensitive", - "special-category" - ] - }, - "dataschemaversion": { - "title": "Data Schema Version", - "description": "Matches semantic versioning format with fixed major version (Not part of cloudevents spec?)", - "type": "string", - "pattern": "^1\\.\\d+\\.\\d+$" - } - }, - "required": [ - "specversion", - "id", - "source", - "subject", - "type", - "time", - "dataschema", - "data", - "traceparent", - "recordedtime", - "severitynumber", - "dataschemaversion" - ] -} \ No newline at end of file diff --git a/internal/events/schemas/events/letter.CANCELLED.schema.json b/internal/events/schemas/events/letter.CANCELLED.schema.json deleted file mode 100644 index c276b168f..000000000 --- a/internal/events/schemas/events/letter.CANCELLED.schema.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "title": "letter.CANCELLED Event", - "description": "Event schema for letter status change to CANCELLED", - "type": "object", - "properties": { - "specversion": { - "title": "CloudEvents spec version", - "description": "CloudEvents specification version (fixed to 1.0).", - "examples": [ - "1.0" - ], - "type": "string", - "enum": [ - "1.0" - ] - }, - "id": { - "title": "Event ID", - "description": "Unique identifier for this event instance (UUID).", - "examples": [ - "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111" - ], - "type": "string", - "minLength": 1, - "format": "uuid", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" - }, - "source": { - "title": "Event Source", - "description": "Logical event producer path within the supplier-api domain", - "type": "string", - "pattern": "^\\/data-plane\\/supplier-api(?:\\/.*)?$" - }, - "subject": { - "title": "Event Subject", - "description": "Resource path (no leading slash) within the source made of segments separated by '/'.", - "examples": [ - "letter-origin/letter-rendering/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479" - ], - "type": "string", - "pattern": "^letter-origin\\/[a-z0-9-]+\\/letter\\/[^/]+(?:\\/.*)?" - }, - "type": { - "title": "Letter CANCELLED event type", - "description": "Event type using reverse-DNS style", - "examples": [ - "uk.nhs.notify.supplier-api.letter.CANCELLED.v1" - ], - "type": "string", - "enum": [ - "uk.nhs.notify.supplier-api.letter.CANCELLED.v1" - ] - }, - "time": { - "title": "Event Time", - "description": "Timestamp when the event occurred (RFC 3339).", - "examples": [ - "2025-10-01T10:15:30.000Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "datacontenttype": { - "title": "Data Content Type", - "description": "Media type for the data field (fixed to application/json).", - "examples": [ - "application/json" - ], - "type": "string", - "enum": [ - "application/json" - ] - }, - "dataschema": { - "title": "Data Schema URI", - "description": "URI of a schema that describes the event data\n\nData schema version must match the major version indicated by the type", - "examples": [ - "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json" - ], - "type": "string", - "pattern": "^https:\\/\\/notify\\.nhs\\.uk\\/cloudevents\\/schemas\\/supplier-api\\/letter.CANCELLED.1\\.\\d+\\.\\d+\\.schema.json$" - }, - "data": { - "type": "object", - "properties": { - "domainId": { - "type": "string" - }, - "origin": { - "title": "Letter origin", - "description": "Identifiers captured from the original event that introduced the letter to the supplier-api domain.\n\nThe identifier will be included as the origin domain in the subject of any corresponding events emitted by the supplier-api domain.", - "examples": [ - { - "domain": "letter-rendering", - "subject": "customer/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-rendering/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", - "event": "00f3b388-bbe9-41c9-9e76-052d37ee8988" - } - ], - "type": "object", - "properties": { - "domain": { - "title": "Domain ID", - "description": "The domain which requested this letter", - "type": "string" - }, - "source": { - "title": "Event source", - "description": "The source of the event which created this letter", - "type": "string" - }, - "subject": { - "title": "Event subject", - "description": "The subject of the event which created this letter, scoped to source", - "type": "string" - }, - "event": { - "title": "Event ID", - "description": "The ID of the event which created this letter", - "type": "string" - } - }, - "required": [ - "domain", - "source", - "subject", - "event" - ] - }, - "specificationId": { - "title": "Specification ID", - "description": "Reference to the letter specification which was used to produce a letter pack for this request.", - "examples": [ - "1y3q9v1zzzz" - ], - "type": "string" - }, - "groupId": { - "title": "Group ID", - "description": "Identifier for the group which this letter assigned to for reporting purposes.", - "examples": [ - "client_template", - "00f3b388-bbe9-41c9-9e76-052d37ee8988_20a1ab22-6136-47ae-ac0f-989f382be8df" - ], - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "CANCELLED" - ] - }, - "reasonCode": { - "title": "Reason Code", - "description": "Optional reason code for the status change, if applicable.", - "examples": [ - "R01", - "R08" - ], - "type": "string" - }, - "reasonText": { - "title": "Reason Text", - "description": "Optional human-readable reason for the status change, if applicable.", - "examples": [ - "Undeliverable", - "Recipient moved" - ], - "type": "string" - } - }, - "required": [ - "domainId", - "origin", - "specificationId", - "groupId", - "status" - ] - }, - "traceparent": { - "title": "Traceparent", - "description": "W3C Trace Context traceparent header value.", - "examples": [ - "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" - ], - "type": "string", - "minLength": 1, - "pattern": "^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$" - }, - "tracestate": { - "title": "Tracestate", - "description": "Optional W3C Trace Context tracestate header value.", - "examples": [ - "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE" - ], - "type": "string" - }, - "partitionkey": { - "title": "Partition Key", - "description": "Partition / ordering key (lowercase alphanumerics and hyphen, 1-64 chars).", - "examples": [ - "customer-920fca11" - ], - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^[a-z0-9-]+$" - }, - "recordedtime": { - "title": "Recorded Time", - "description": "Timestamp when the event was recorded/persisted (should be >= time).", - "examples": [ - "2025-10-01T10:15:30.250Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "sampledrate": { - "title": "Sampled Rate", - "description": "Sampling factor: number of similar occurrences this event represents.", - "examples": [ - 5 - ], - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "sequence": { - "title": "Sequence", - "description": "Zero-padded 20 digit numeric sequence (lexicographically sortable).", - "examples": [ - "00000000000000000042" - ], - "type": "string", - "pattern": "^\\d{20}$" - }, - "severitytext": { - "title": "Severity Text", - "description": "Log severity level name.", - "examples": [ - "DEBUG" - ], - "type": "string", - "enum": [ - "TRACE", - "DEBUG", - "INFO", - "WARN", - "ERROR", - "FATAL" - ] - }, - "severitynumber": { - "title": "Severity Number", - "description": "Numeric severity (TRACE=0, DEBUG=1, INFO=2, WARN=3, ERROR=4, FATAL=5).", - "examples": [ - 1 - ], - "type": "integer", - "minimum": 0, - "maximum": 5 - }, - "dataclassification": { - "title": "Data Classification", - "description": "Data sensitivity classification.", - "examples": [ - "restricted" - ], - "type": "string", - "enum": [ - "public", - "internal", - "confidential", - "restricted" - ] - }, - "dataregulation": { - "title": "Data Regulation", - "description": "Regulatory regime tag applied to this data.", - "examples": [ - "ISO-27001" - ], - "type": "string", - "enum": [ - "GDPR", - "HIPAA", - "PCI-DSS", - "ISO-27001", - "NIST-800-53", - "CCPA" - ] - }, - "datacategory": { - "title": "Data Category", - "description": "Data category classification (e.g. standard, special-category).", - "examples": [ - "sensitive" - ], - "type": "string", - "enum": [ - "non-sensitive", - "standard", - "sensitive", - "special-category" - ] - }, - "dataschemaversion": { - "title": "Data Schema Version", - "description": "Matches semantic versioning format with fixed major version (Not part of cloudevents spec?)", - "type": "string", - "pattern": "^1\\.\\d+\\.\\d+$" - } - }, - "required": [ - "specversion", - "id", - "source", - "subject", - "type", - "time", - "dataschema", - "data", - "traceparent", - "recordedtime", - "severitynumber", - "dataschemaversion" - ] -} \ No newline at end of file diff --git a/internal/events/schemas/events/letter.DELIVERED.schema.json b/internal/events/schemas/events/letter.DELIVERED.schema.json deleted file mode 100644 index 62faa382e..000000000 --- a/internal/events/schemas/events/letter.DELIVERED.schema.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "title": "letter.DELIVERED Event", - "description": "Event schema for letter status change to DELIVERED", - "type": "object", - "properties": { - "specversion": { - "title": "CloudEvents spec version", - "description": "CloudEvents specification version (fixed to 1.0).", - "examples": [ - "1.0" - ], - "type": "string", - "enum": [ - "1.0" - ] - }, - "id": { - "title": "Event ID", - "description": "Unique identifier for this event instance (UUID).", - "examples": [ - "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111" - ], - "type": "string", - "minLength": 1, - "format": "uuid", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" - }, - "source": { - "title": "Event Source", - "description": "Logical event producer path within the supplier-api domain", - "type": "string", - "pattern": "^\\/data-plane\\/supplier-api(?:\\/.*)?$" - }, - "subject": { - "title": "Event Subject", - "description": "Resource path (no leading slash) within the source made of segments separated by '/'.", - "examples": [ - "letter-origin/letter-rendering/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479" - ], - "type": "string", - "pattern": "^letter-origin\\/[a-z0-9-]+\\/letter\\/[^/]+(?:\\/.*)?" - }, - "type": { - "title": "Letter DELIVERED event type", - "description": "Event type using reverse-DNS style", - "examples": [ - "uk.nhs.notify.supplier-api.letter.DELIVERED.v1" - ], - "type": "string", - "enum": [ - "uk.nhs.notify.supplier-api.letter.DELIVERED.v1" - ] - }, - "time": { - "title": "Event Time", - "description": "Timestamp when the event occurred (RFC 3339).", - "examples": [ - "2025-10-01T10:15:30.000Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "datacontenttype": { - "title": "Data Content Type", - "description": "Media type for the data field (fixed to application/json).", - "examples": [ - "application/json" - ], - "type": "string", - "enum": [ - "application/json" - ] - }, - "dataschema": { - "title": "Data Schema URI", - "description": "URI of a schema that describes the event data\n\nData schema version must match the major version indicated by the type", - "examples": [ - "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json" - ], - "type": "string", - "pattern": "^https:\\/\\/notify\\.nhs\\.uk\\/cloudevents\\/schemas\\/supplier-api\\/letter.DELIVERED.1\\.\\d+\\.\\d+\\.schema.json$" - }, - "data": { - "type": "object", - "properties": { - "domainId": { - "type": "string" - }, - "origin": { - "title": "Letter origin", - "description": "Identifiers captured from the original event that introduced the letter to the supplier-api domain.\n\nThe identifier will be included as the origin domain in the subject of any corresponding events emitted by the supplier-api domain.", - "examples": [ - { - "domain": "letter-rendering", - "subject": "customer/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-rendering/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", - "event": "00f3b388-bbe9-41c9-9e76-052d37ee8988" - } - ], - "type": "object", - "properties": { - "domain": { - "title": "Domain ID", - "description": "The domain which requested this letter", - "type": "string" - }, - "source": { - "title": "Event source", - "description": "The source of the event which created this letter", - "type": "string" - }, - "subject": { - "title": "Event subject", - "description": "The subject of the event which created this letter, scoped to source", - "type": "string" - }, - "event": { - "title": "Event ID", - "description": "The ID of the event which created this letter", - "type": "string" - } - }, - "required": [ - "domain", - "source", - "subject", - "event" - ] - }, - "specificationId": { - "title": "Specification ID", - "description": "Reference to the letter specification which was used to produce a letter pack for this request.", - "examples": [ - "1y3q9v1zzzz" - ], - "type": "string" - }, - "groupId": { - "title": "Group ID", - "description": "Identifier for the group which this letter assigned to for reporting purposes.", - "examples": [ - "client_template", - "00f3b388-bbe9-41c9-9e76-052d37ee8988_20a1ab22-6136-47ae-ac0f-989f382be8df" - ], - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "DELIVERED" - ] - }, - "reasonCode": { - "title": "Reason Code", - "description": "Optional reason code for the status change, if applicable.", - "examples": [ - "R01", - "R08" - ], - "type": "string" - }, - "reasonText": { - "title": "Reason Text", - "description": "Optional human-readable reason for the status change, if applicable.", - "examples": [ - "Undeliverable", - "Recipient moved" - ], - "type": "string" - } - }, - "required": [ - "domainId", - "origin", - "specificationId", - "groupId", - "status" - ] - }, - "traceparent": { - "title": "Traceparent", - "description": "W3C Trace Context traceparent header value.", - "examples": [ - "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" - ], - "type": "string", - "minLength": 1, - "pattern": "^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$" - }, - "tracestate": { - "title": "Tracestate", - "description": "Optional W3C Trace Context tracestate header value.", - "examples": [ - "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE" - ], - "type": "string" - }, - "partitionkey": { - "title": "Partition Key", - "description": "Partition / ordering key (lowercase alphanumerics and hyphen, 1-64 chars).", - "examples": [ - "customer-920fca11" - ], - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^[a-z0-9-]+$" - }, - "recordedtime": { - "title": "Recorded Time", - "description": "Timestamp when the event was recorded/persisted (should be >= time).", - "examples": [ - "2025-10-01T10:15:30.250Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "sampledrate": { - "title": "Sampled Rate", - "description": "Sampling factor: number of similar occurrences this event represents.", - "examples": [ - 5 - ], - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "sequence": { - "title": "Sequence", - "description": "Zero-padded 20 digit numeric sequence (lexicographically sortable).", - "examples": [ - "00000000000000000042" - ], - "type": "string", - "pattern": "^\\d{20}$" - }, - "severitytext": { - "title": "Severity Text", - "description": "Log severity level name.", - "examples": [ - "DEBUG" - ], - "type": "string", - "enum": [ - "TRACE", - "DEBUG", - "INFO", - "WARN", - "ERROR", - "FATAL" - ] - }, - "severitynumber": { - "title": "Severity Number", - "description": "Numeric severity (TRACE=0, DEBUG=1, INFO=2, WARN=3, ERROR=4, FATAL=5).", - "examples": [ - 1 - ], - "type": "integer", - "minimum": 0, - "maximum": 5 - }, - "dataclassification": { - "title": "Data Classification", - "description": "Data sensitivity classification.", - "examples": [ - "restricted" - ], - "type": "string", - "enum": [ - "public", - "internal", - "confidential", - "restricted" - ] - }, - "dataregulation": { - "title": "Data Regulation", - "description": "Regulatory regime tag applied to this data.", - "examples": [ - "ISO-27001" - ], - "type": "string", - "enum": [ - "GDPR", - "HIPAA", - "PCI-DSS", - "ISO-27001", - "NIST-800-53", - "CCPA" - ] - }, - "datacategory": { - "title": "Data Category", - "description": "Data category classification (e.g. standard, special-category).", - "examples": [ - "sensitive" - ], - "type": "string", - "enum": [ - "non-sensitive", - "standard", - "sensitive", - "special-category" - ] - }, - "dataschemaversion": { - "title": "Data Schema Version", - "description": "Matches semantic versioning format with fixed major version (Not part of cloudevents spec?)", - "type": "string", - "pattern": "^1\\.\\d+\\.\\d+$" - } - }, - "required": [ - "specversion", - "id", - "source", - "subject", - "type", - "time", - "dataschema", - "data", - "traceparent", - "recordedtime", - "severitynumber", - "dataschemaversion" - ] -} \ No newline at end of file diff --git a/internal/events/schemas/events/letter.DISPATCHED.schema.json b/internal/events/schemas/events/letter.DISPATCHED.schema.json deleted file mode 100644 index 6ada6b749..000000000 --- a/internal/events/schemas/events/letter.DISPATCHED.schema.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "title": "letter.DISPATCHED Event", - "description": "Event schema for letter status change to DISPATCHED", - "type": "object", - "properties": { - "specversion": { - "title": "CloudEvents spec version", - "description": "CloudEvents specification version (fixed to 1.0).", - "examples": [ - "1.0" - ], - "type": "string", - "enum": [ - "1.0" - ] - }, - "id": { - "title": "Event ID", - "description": "Unique identifier for this event instance (UUID).", - "examples": [ - "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111" - ], - "type": "string", - "minLength": 1, - "format": "uuid", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" - }, - "source": { - "title": "Event Source", - "description": "Logical event producer path within the supplier-api domain", - "type": "string", - "pattern": "^\\/data-plane\\/supplier-api(?:\\/.*)?$" - }, - "subject": { - "title": "Event Subject", - "description": "Resource path (no leading slash) within the source made of segments separated by '/'.", - "examples": [ - "letter-origin/letter-rendering/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479" - ], - "type": "string", - "pattern": "^letter-origin\\/[a-z0-9-]+\\/letter\\/[^/]+(?:\\/.*)?" - }, - "type": { - "title": "Letter DISPATCHED event type", - "description": "Event type using reverse-DNS style", - "examples": [ - "uk.nhs.notify.supplier-api.letter.DISPATCHED.v1" - ], - "type": "string", - "enum": [ - "uk.nhs.notify.supplier-api.letter.DISPATCHED.v1" - ] - }, - "time": { - "title": "Event Time", - "description": "Timestamp when the event occurred (RFC 3339).", - "examples": [ - "2025-10-01T10:15:30.000Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "datacontenttype": { - "title": "Data Content Type", - "description": "Media type for the data field (fixed to application/json).", - "examples": [ - "application/json" - ], - "type": "string", - "enum": [ - "application/json" - ] - }, - "dataschema": { - "title": "Data Schema URI", - "description": "URI of a schema that describes the event data\n\nData schema version must match the major version indicated by the type", - "examples": [ - "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json" - ], - "type": "string", - "pattern": "^https:\\/\\/notify\\.nhs\\.uk\\/cloudevents\\/schemas\\/supplier-api\\/letter.DISPATCHED.1\\.\\d+\\.\\d+\\.schema.json$" - }, - "data": { - "type": "object", - "properties": { - "domainId": { - "type": "string" - }, - "origin": { - "title": "Letter origin", - "description": "Identifiers captured from the original event that introduced the letter to the supplier-api domain.\n\nThe identifier will be included as the origin domain in the subject of any corresponding events emitted by the supplier-api domain.", - "examples": [ - { - "domain": "letter-rendering", - "subject": "customer/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-rendering/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", - "event": "00f3b388-bbe9-41c9-9e76-052d37ee8988" - } - ], - "type": "object", - "properties": { - "domain": { - "title": "Domain ID", - "description": "The domain which requested this letter", - "type": "string" - }, - "source": { - "title": "Event source", - "description": "The source of the event which created this letter", - "type": "string" - }, - "subject": { - "title": "Event subject", - "description": "The subject of the event which created this letter, scoped to source", - "type": "string" - }, - "event": { - "title": "Event ID", - "description": "The ID of the event which created this letter", - "type": "string" - } - }, - "required": [ - "domain", - "source", - "subject", - "event" - ] - }, - "specificationId": { - "title": "Specification ID", - "description": "Reference to the letter specification which was used to produce a letter pack for this request.", - "examples": [ - "1y3q9v1zzzz" - ], - "type": "string" - }, - "groupId": { - "title": "Group ID", - "description": "Identifier for the group which this letter assigned to for reporting purposes.", - "examples": [ - "client_template", - "00f3b388-bbe9-41c9-9e76-052d37ee8988_20a1ab22-6136-47ae-ac0f-989f382be8df" - ], - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "DISPATCHED" - ] - }, - "reasonCode": { - "title": "Reason Code", - "description": "Optional reason code for the status change, if applicable.", - "examples": [ - "R01", - "R08" - ], - "type": "string" - }, - "reasonText": { - "title": "Reason Text", - "description": "Optional human-readable reason for the status change, if applicable.", - "examples": [ - "Undeliverable", - "Recipient moved" - ], - "type": "string" - } - }, - "required": [ - "domainId", - "origin", - "specificationId", - "groupId", - "status" - ] - }, - "traceparent": { - "title": "Traceparent", - "description": "W3C Trace Context traceparent header value.", - "examples": [ - "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" - ], - "type": "string", - "minLength": 1, - "pattern": "^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$" - }, - "tracestate": { - "title": "Tracestate", - "description": "Optional W3C Trace Context tracestate header value.", - "examples": [ - "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE" - ], - "type": "string" - }, - "partitionkey": { - "title": "Partition Key", - "description": "Partition / ordering key (lowercase alphanumerics and hyphen, 1-64 chars).", - "examples": [ - "customer-920fca11" - ], - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^[a-z0-9-]+$" - }, - "recordedtime": { - "title": "Recorded Time", - "description": "Timestamp when the event was recorded/persisted (should be >= time).", - "examples": [ - "2025-10-01T10:15:30.250Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "sampledrate": { - "title": "Sampled Rate", - "description": "Sampling factor: number of similar occurrences this event represents.", - "examples": [ - 5 - ], - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "sequence": { - "title": "Sequence", - "description": "Zero-padded 20 digit numeric sequence (lexicographically sortable).", - "examples": [ - "00000000000000000042" - ], - "type": "string", - "pattern": "^\\d{20}$" - }, - "severitytext": { - "title": "Severity Text", - "description": "Log severity level name.", - "examples": [ - "DEBUG" - ], - "type": "string", - "enum": [ - "TRACE", - "DEBUG", - "INFO", - "WARN", - "ERROR", - "FATAL" - ] - }, - "severitynumber": { - "title": "Severity Number", - "description": "Numeric severity (TRACE=0, DEBUG=1, INFO=2, WARN=3, ERROR=4, FATAL=5).", - "examples": [ - 1 - ], - "type": "integer", - "minimum": 0, - "maximum": 5 - }, - "dataclassification": { - "title": "Data Classification", - "description": "Data sensitivity classification.", - "examples": [ - "restricted" - ], - "type": "string", - "enum": [ - "public", - "internal", - "confidential", - "restricted" - ] - }, - "dataregulation": { - "title": "Data Regulation", - "description": "Regulatory regime tag applied to this data.", - "examples": [ - "ISO-27001" - ], - "type": "string", - "enum": [ - "GDPR", - "HIPAA", - "PCI-DSS", - "ISO-27001", - "NIST-800-53", - "CCPA" - ] - }, - "datacategory": { - "title": "Data Category", - "description": "Data category classification (e.g. standard, special-category).", - "examples": [ - "sensitive" - ], - "type": "string", - "enum": [ - "non-sensitive", - "standard", - "sensitive", - "special-category" - ] - }, - "dataschemaversion": { - "title": "Data Schema Version", - "description": "Matches semantic versioning format with fixed major version (Not part of cloudevents spec?)", - "type": "string", - "pattern": "^1\\.\\d+\\.\\d+$" - } - }, - "required": [ - "specversion", - "id", - "source", - "subject", - "type", - "time", - "dataschema", - "data", - "traceparent", - "recordedtime", - "severitynumber", - "dataschemaversion" - ] -} \ No newline at end of file diff --git a/internal/events/schemas/events/letter.ENCLOSED.schema.json b/internal/events/schemas/events/letter.ENCLOSED.schema.json deleted file mode 100644 index ea0c9a8df..000000000 --- a/internal/events/schemas/events/letter.ENCLOSED.schema.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "title": "letter.ENCLOSED Event", - "description": "Event schema for letter status change to ENCLOSED", - "type": "object", - "properties": { - "specversion": { - "title": "CloudEvents spec version", - "description": "CloudEvents specification version (fixed to 1.0).", - "examples": [ - "1.0" - ], - "type": "string", - "enum": [ - "1.0" - ] - }, - "id": { - "title": "Event ID", - "description": "Unique identifier for this event instance (UUID).", - "examples": [ - "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111" - ], - "type": "string", - "minLength": 1, - "format": "uuid", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" - }, - "source": { - "title": "Event Source", - "description": "Logical event producer path within the supplier-api domain", - "type": "string", - "pattern": "^\\/data-plane\\/supplier-api(?:\\/.*)?$" - }, - "subject": { - "title": "Event Subject", - "description": "Resource path (no leading slash) within the source made of segments separated by '/'.", - "examples": [ - "letter-origin/letter-rendering/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479" - ], - "type": "string", - "pattern": "^letter-origin\\/[a-z0-9-]+\\/letter\\/[^/]+(?:\\/.*)?" - }, - "type": { - "title": "Letter ENCLOSED event type", - "description": "Event type using reverse-DNS style", - "examples": [ - "uk.nhs.notify.supplier-api.letter.ENCLOSED.v1" - ], - "type": "string", - "enum": [ - "uk.nhs.notify.supplier-api.letter.ENCLOSED.v1" - ] - }, - "time": { - "title": "Event Time", - "description": "Timestamp when the event occurred (RFC 3339).", - "examples": [ - "2025-10-01T10:15:30.000Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "datacontenttype": { - "title": "Data Content Type", - "description": "Media type for the data field (fixed to application/json).", - "examples": [ - "application/json" - ], - "type": "string", - "enum": [ - "application/json" - ] - }, - "dataschema": { - "title": "Data Schema URI", - "description": "URI of a schema that describes the event data\n\nData schema version must match the major version indicated by the type", - "examples": [ - "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json" - ], - "type": "string", - "pattern": "^https:\\/\\/notify\\.nhs\\.uk\\/cloudevents\\/schemas\\/supplier-api\\/letter.ENCLOSED.1\\.\\d+\\.\\d+\\.schema.json$" - }, - "data": { - "type": "object", - "properties": { - "domainId": { - "type": "string" - }, - "origin": { - "title": "Letter origin", - "description": "Identifiers captured from the original event that introduced the letter to the supplier-api domain.\n\nThe identifier will be included as the origin domain in the subject of any corresponding events emitted by the supplier-api domain.", - "examples": [ - { - "domain": "letter-rendering", - "subject": "customer/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-rendering/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", - "event": "00f3b388-bbe9-41c9-9e76-052d37ee8988" - } - ], - "type": "object", - "properties": { - "domain": { - "title": "Domain ID", - "description": "The domain which requested this letter", - "type": "string" - }, - "source": { - "title": "Event source", - "description": "The source of the event which created this letter", - "type": "string" - }, - "subject": { - "title": "Event subject", - "description": "The subject of the event which created this letter, scoped to source", - "type": "string" - }, - "event": { - "title": "Event ID", - "description": "The ID of the event which created this letter", - "type": "string" - } - }, - "required": [ - "domain", - "source", - "subject", - "event" - ] - }, - "specificationId": { - "title": "Specification ID", - "description": "Reference to the letter specification which was used to produce a letter pack for this request.", - "examples": [ - "1y3q9v1zzzz" - ], - "type": "string" - }, - "groupId": { - "title": "Group ID", - "description": "Identifier for the group which this letter assigned to for reporting purposes.", - "examples": [ - "client_template", - "00f3b388-bbe9-41c9-9e76-052d37ee8988_20a1ab22-6136-47ae-ac0f-989f382be8df" - ], - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "ENCLOSED" - ] - }, - "reasonCode": { - "title": "Reason Code", - "description": "Optional reason code for the status change, if applicable.", - "examples": [ - "R01", - "R08" - ], - "type": "string" - }, - "reasonText": { - "title": "Reason Text", - "description": "Optional human-readable reason for the status change, if applicable.", - "examples": [ - "Undeliverable", - "Recipient moved" - ], - "type": "string" - } - }, - "required": [ - "domainId", - "origin", - "specificationId", - "groupId", - "status" - ] - }, - "traceparent": { - "title": "Traceparent", - "description": "W3C Trace Context traceparent header value.", - "examples": [ - "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" - ], - "type": "string", - "minLength": 1, - "pattern": "^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$" - }, - "tracestate": { - "title": "Tracestate", - "description": "Optional W3C Trace Context tracestate header value.", - "examples": [ - "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE" - ], - "type": "string" - }, - "partitionkey": { - "title": "Partition Key", - "description": "Partition / ordering key (lowercase alphanumerics and hyphen, 1-64 chars).", - "examples": [ - "customer-920fca11" - ], - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^[a-z0-9-]+$" - }, - "recordedtime": { - "title": "Recorded Time", - "description": "Timestamp when the event was recorded/persisted (should be >= time).", - "examples": [ - "2025-10-01T10:15:30.250Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "sampledrate": { - "title": "Sampled Rate", - "description": "Sampling factor: number of similar occurrences this event represents.", - "examples": [ - 5 - ], - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "sequence": { - "title": "Sequence", - "description": "Zero-padded 20 digit numeric sequence (lexicographically sortable).", - "examples": [ - "00000000000000000042" - ], - "type": "string", - "pattern": "^\\d{20}$" - }, - "severitytext": { - "title": "Severity Text", - "description": "Log severity level name.", - "examples": [ - "DEBUG" - ], - "type": "string", - "enum": [ - "TRACE", - "DEBUG", - "INFO", - "WARN", - "ERROR", - "FATAL" - ] - }, - "severitynumber": { - "title": "Severity Number", - "description": "Numeric severity (TRACE=0, DEBUG=1, INFO=2, WARN=3, ERROR=4, FATAL=5).", - "examples": [ - 1 - ], - "type": "integer", - "minimum": 0, - "maximum": 5 - }, - "dataclassification": { - "title": "Data Classification", - "description": "Data sensitivity classification.", - "examples": [ - "restricted" - ], - "type": "string", - "enum": [ - "public", - "internal", - "confidential", - "restricted" - ] - }, - "dataregulation": { - "title": "Data Regulation", - "description": "Regulatory regime tag applied to this data.", - "examples": [ - "ISO-27001" - ], - "type": "string", - "enum": [ - "GDPR", - "HIPAA", - "PCI-DSS", - "ISO-27001", - "NIST-800-53", - "CCPA" - ] - }, - "datacategory": { - "title": "Data Category", - "description": "Data category classification (e.g. standard, special-category).", - "examples": [ - "sensitive" - ], - "type": "string", - "enum": [ - "non-sensitive", - "standard", - "sensitive", - "special-category" - ] - }, - "dataschemaversion": { - "title": "Data Schema Version", - "description": "Matches semantic versioning format with fixed major version (Not part of cloudevents spec?)", - "type": "string", - "pattern": "^1\\.\\d+\\.\\d+$" - } - }, - "required": [ - "specversion", - "id", - "source", - "subject", - "type", - "time", - "dataschema", - "data", - "traceparent", - "recordedtime", - "severitynumber", - "dataschemaversion" - ] -} \ No newline at end of file diff --git a/internal/events/schemas/events/letter.FAILED.schema.json b/internal/events/schemas/events/letter.FAILED.schema.json deleted file mode 100644 index 29ef06658..000000000 --- a/internal/events/schemas/events/letter.FAILED.schema.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "title": "letter.FAILED Event", - "description": "Event schema for letter status change to FAILED", - "type": "object", - "properties": { - "specversion": { - "title": "CloudEvents spec version", - "description": "CloudEvents specification version (fixed to 1.0).", - "examples": [ - "1.0" - ], - "type": "string", - "enum": [ - "1.0" - ] - }, - "id": { - "title": "Event ID", - "description": "Unique identifier for this event instance (UUID).", - "examples": [ - "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111" - ], - "type": "string", - "minLength": 1, - "format": "uuid", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" - }, - "source": { - "title": "Event Source", - "description": "Logical event producer path within the supplier-api domain", - "type": "string", - "pattern": "^\\/data-plane\\/supplier-api(?:\\/.*)?$" - }, - "subject": { - "title": "Event Subject", - "description": "Resource path (no leading slash) within the source made of segments separated by '/'.", - "examples": [ - "letter-origin/letter-rendering/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479" - ], - "type": "string", - "pattern": "^letter-origin\\/[a-z0-9-]+\\/letter\\/[^/]+(?:\\/.*)?" - }, - "type": { - "title": "Letter FAILED event type", - "description": "Event type using reverse-DNS style", - "examples": [ - "uk.nhs.notify.supplier-api.letter.FAILED.v1" - ], - "type": "string", - "enum": [ - "uk.nhs.notify.supplier-api.letter.FAILED.v1" - ] - }, - "time": { - "title": "Event Time", - "description": "Timestamp when the event occurred (RFC 3339).", - "examples": [ - "2025-10-01T10:15:30.000Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "datacontenttype": { - "title": "Data Content Type", - "description": "Media type for the data field (fixed to application/json).", - "examples": [ - "application/json" - ], - "type": "string", - "enum": [ - "application/json" - ] - }, - "dataschema": { - "title": "Data Schema URI", - "description": "URI of a schema that describes the event data\n\nData schema version must match the major version indicated by the type", - "examples": [ - "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json" - ], - "type": "string", - "pattern": "^https:\\/\\/notify\\.nhs\\.uk\\/cloudevents\\/schemas\\/supplier-api\\/letter.FAILED.1\\.\\d+\\.\\d+\\.schema.json$" - }, - "data": { - "type": "object", - "properties": { - "domainId": { - "type": "string" - }, - "origin": { - "title": "Letter origin", - "description": "Identifiers captured from the original event that introduced the letter to the supplier-api domain.\n\nThe identifier will be included as the origin domain in the subject of any corresponding events emitted by the supplier-api domain.", - "examples": [ - { - "domain": "letter-rendering", - "subject": "customer/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-rendering/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", - "event": "00f3b388-bbe9-41c9-9e76-052d37ee8988" - } - ], - "type": "object", - "properties": { - "domain": { - "title": "Domain ID", - "description": "The domain which requested this letter", - "type": "string" - }, - "source": { - "title": "Event source", - "description": "The source of the event which created this letter", - "type": "string" - }, - "subject": { - "title": "Event subject", - "description": "The subject of the event which created this letter, scoped to source", - "type": "string" - }, - "event": { - "title": "Event ID", - "description": "The ID of the event which created this letter", - "type": "string" - } - }, - "required": [ - "domain", - "source", - "subject", - "event" - ] - }, - "specificationId": { - "title": "Specification ID", - "description": "Reference to the letter specification which was used to produce a letter pack for this request.", - "examples": [ - "1y3q9v1zzzz" - ], - "type": "string" - }, - "groupId": { - "title": "Group ID", - "description": "Identifier for the group which this letter assigned to for reporting purposes.", - "examples": [ - "client_template", - "00f3b388-bbe9-41c9-9e76-052d37ee8988_20a1ab22-6136-47ae-ac0f-989f382be8df" - ], - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "FAILED" - ] - }, - "reasonCode": { - "title": "Reason Code", - "description": "Optional reason code for the status change, if applicable.", - "examples": [ - "R01", - "R08" - ], - "type": "string" - }, - "reasonText": { - "title": "Reason Text", - "description": "Optional human-readable reason for the status change, if applicable.", - "examples": [ - "Undeliverable", - "Recipient moved" - ], - "type": "string" - } - }, - "required": [ - "domainId", - "origin", - "specificationId", - "groupId", - "status" - ] - }, - "traceparent": { - "title": "Traceparent", - "description": "W3C Trace Context traceparent header value.", - "examples": [ - "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" - ], - "type": "string", - "minLength": 1, - "pattern": "^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$" - }, - "tracestate": { - "title": "Tracestate", - "description": "Optional W3C Trace Context tracestate header value.", - "examples": [ - "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE" - ], - "type": "string" - }, - "partitionkey": { - "title": "Partition Key", - "description": "Partition / ordering key (lowercase alphanumerics and hyphen, 1-64 chars).", - "examples": [ - "customer-920fca11" - ], - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^[a-z0-9-]+$" - }, - "recordedtime": { - "title": "Recorded Time", - "description": "Timestamp when the event was recorded/persisted (should be >= time).", - "examples": [ - "2025-10-01T10:15:30.250Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "sampledrate": { - "title": "Sampled Rate", - "description": "Sampling factor: number of similar occurrences this event represents.", - "examples": [ - 5 - ], - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "sequence": { - "title": "Sequence", - "description": "Zero-padded 20 digit numeric sequence (lexicographically sortable).", - "examples": [ - "00000000000000000042" - ], - "type": "string", - "pattern": "^\\d{20}$" - }, - "severitytext": { - "title": "Severity Text", - "description": "Log severity level name.", - "examples": [ - "DEBUG" - ], - "type": "string", - "enum": [ - "TRACE", - "DEBUG", - "INFO", - "WARN", - "ERROR", - "FATAL" - ] - }, - "severitynumber": { - "title": "Severity Number", - "description": "Numeric severity (TRACE=0, DEBUG=1, INFO=2, WARN=3, ERROR=4, FATAL=5).", - "examples": [ - 1 - ], - "type": "integer", - "minimum": 0, - "maximum": 5 - }, - "dataclassification": { - "title": "Data Classification", - "description": "Data sensitivity classification.", - "examples": [ - "restricted" - ], - "type": "string", - "enum": [ - "public", - "internal", - "confidential", - "restricted" - ] - }, - "dataregulation": { - "title": "Data Regulation", - "description": "Regulatory regime tag applied to this data.", - "examples": [ - "ISO-27001" - ], - "type": "string", - "enum": [ - "GDPR", - "HIPAA", - "PCI-DSS", - "ISO-27001", - "NIST-800-53", - "CCPA" - ] - }, - "datacategory": { - "title": "Data Category", - "description": "Data category classification (e.g. standard, special-category).", - "examples": [ - "sensitive" - ], - "type": "string", - "enum": [ - "non-sensitive", - "standard", - "sensitive", - "special-category" - ] - }, - "dataschemaversion": { - "title": "Data Schema Version", - "description": "Matches semantic versioning format with fixed major version (Not part of cloudevents spec?)", - "type": "string", - "pattern": "^1\\.\\d+\\.\\d+$" - } - }, - "required": [ - "specversion", - "id", - "source", - "subject", - "type", - "time", - "dataschema", - "data", - "traceparent", - "recordedtime", - "severitynumber", - "dataschemaversion" - ] -} \ No newline at end of file diff --git a/internal/events/schemas/events/letter.FORWARDED.schema.json b/internal/events/schemas/events/letter.FORWARDED.schema.json deleted file mode 100644 index 36c89ab28..000000000 --- a/internal/events/schemas/events/letter.FORWARDED.schema.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "title": "letter.FORWARDED Event", - "description": "Event schema for letter status change to FORWARDED", - "type": "object", - "properties": { - "specversion": { - "title": "CloudEvents spec version", - "description": "CloudEvents specification version (fixed to 1.0).", - "examples": [ - "1.0" - ], - "type": "string", - "enum": [ - "1.0" - ] - }, - "id": { - "title": "Event ID", - "description": "Unique identifier for this event instance (UUID).", - "examples": [ - "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111" - ], - "type": "string", - "minLength": 1, - "format": "uuid", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" - }, - "source": { - "title": "Event Source", - "description": "Logical event producer path within the supplier-api domain", - "type": "string", - "pattern": "^\\/data-plane\\/supplier-api(?:\\/.*)?$" - }, - "subject": { - "title": "Event Subject", - "description": "Resource path (no leading slash) within the source made of segments separated by '/'.", - "examples": [ - "letter-origin/letter-rendering/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479" - ], - "type": "string", - "pattern": "^letter-origin\\/[a-z0-9-]+\\/letter\\/[^/]+(?:\\/.*)?" - }, - "type": { - "title": "Letter FORWARDED event type", - "description": "Event type using reverse-DNS style", - "examples": [ - "uk.nhs.notify.supplier-api.letter.FORWARDED.v1" - ], - "type": "string", - "enum": [ - "uk.nhs.notify.supplier-api.letter.FORWARDED.v1" - ] - }, - "time": { - "title": "Event Time", - "description": "Timestamp when the event occurred (RFC 3339).", - "examples": [ - "2025-10-01T10:15:30.000Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "datacontenttype": { - "title": "Data Content Type", - "description": "Media type for the data field (fixed to application/json).", - "examples": [ - "application/json" - ], - "type": "string", - "enum": [ - "application/json" - ] - }, - "dataschema": { - "title": "Data Schema URI", - "description": "URI of a schema that describes the event data\n\nData schema version must match the major version indicated by the type", - "examples": [ - "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json" - ], - "type": "string", - "pattern": "^https:\\/\\/notify\\.nhs\\.uk\\/cloudevents\\/schemas\\/supplier-api\\/letter.FORWARDED.1\\.\\d+\\.\\d+\\.schema.json$" - }, - "data": { - "type": "object", - "properties": { - "domainId": { - "type": "string" - }, - "origin": { - "title": "Letter origin", - "description": "Identifiers captured from the original event that introduced the letter to the supplier-api domain.\n\nThe identifier will be included as the origin domain in the subject of any corresponding events emitted by the supplier-api domain.", - "examples": [ - { - "domain": "letter-rendering", - "subject": "customer/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-rendering/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", - "event": "00f3b388-bbe9-41c9-9e76-052d37ee8988" - } - ], - "type": "object", - "properties": { - "domain": { - "title": "Domain ID", - "description": "The domain which requested this letter", - "type": "string" - }, - "source": { - "title": "Event source", - "description": "The source of the event which created this letter", - "type": "string" - }, - "subject": { - "title": "Event subject", - "description": "The subject of the event which created this letter, scoped to source", - "type": "string" - }, - "event": { - "title": "Event ID", - "description": "The ID of the event which created this letter", - "type": "string" - } - }, - "required": [ - "domain", - "source", - "subject", - "event" - ] - }, - "specificationId": { - "title": "Specification ID", - "description": "Reference to the letter specification which was used to produce a letter pack for this request.", - "examples": [ - "1y3q9v1zzzz" - ], - "type": "string" - }, - "groupId": { - "title": "Group ID", - "description": "Identifier for the group which this letter assigned to for reporting purposes.", - "examples": [ - "client_template", - "00f3b388-bbe9-41c9-9e76-052d37ee8988_20a1ab22-6136-47ae-ac0f-989f382be8df" - ], - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "FORWARDED" - ] - }, - "reasonCode": { - "title": "Reason Code", - "description": "Optional reason code for the status change, if applicable.", - "examples": [ - "R01", - "R08" - ], - "type": "string" - }, - "reasonText": { - "title": "Reason Text", - "description": "Optional human-readable reason for the status change, if applicable.", - "examples": [ - "Undeliverable", - "Recipient moved" - ], - "type": "string" - } - }, - "required": [ - "domainId", - "origin", - "specificationId", - "groupId", - "status" - ] - }, - "traceparent": { - "title": "Traceparent", - "description": "W3C Trace Context traceparent header value.", - "examples": [ - "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" - ], - "type": "string", - "minLength": 1, - "pattern": "^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$" - }, - "tracestate": { - "title": "Tracestate", - "description": "Optional W3C Trace Context tracestate header value.", - "examples": [ - "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE" - ], - "type": "string" - }, - "partitionkey": { - "title": "Partition Key", - "description": "Partition / ordering key (lowercase alphanumerics and hyphen, 1-64 chars).", - "examples": [ - "customer-920fca11" - ], - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^[a-z0-9-]+$" - }, - "recordedtime": { - "title": "Recorded Time", - "description": "Timestamp when the event was recorded/persisted (should be >= time).", - "examples": [ - "2025-10-01T10:15:30.250Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "sampledrate": { - "title": "Sampled Rate", - "description": "Sampling factor: number of similar occurrences this event represents.", - "examples": [ - 5 - ], - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "sequence": { - "title": "Sequence", - "description": "Zero-padded 20 digit numeric sequence (lexicographically sortable).", - "examples": [ - "00000000000000000042" - ], - "type": "string", - "pattern": "^\\d{20}$" - }, - "severitytext": { - "title": "Severity Text", - "description": "Log severity level name.", - "examples": [ - "DEBUG" - ], - "type": "string", - "enum": [ - "TRACE", - "DEBUG", - "INFO", - "WARN", - "ERROR", - "FATAL" - ] - }, - "severitynumber": { - "title": "Severity Number", - "description": "Numeric severity (TRACE=0, DEBUG=1, INFO=2, WARN=3, ERROR=4, FATAL=5).", - "examples": [ - 1 - ], - "type": "integer", - "minimum": 0, - "maximum": 5 - }, - "dataclassification": { - "title": "Data Classification", - "description": "Data sensitivity classification.", - "examples": [ - "restricted" - ], - "type": "string", - "enum": [ - "public", - "internal", - "confidential", - "restricted" - ] - }, - "dataregulation": { - "title": "Data Regulation", - "description": "Regulatory regime tag applied to this data.", - "examples": [ - "ISO-27001" - ], - "type": "string", - "enum": [ - "GDPR", - "HIPAA", - "PCI-DSS", - "ISO-27001", - "NIST-800-53", - "CCPA" - ] - }, - "datacategory": { - "title": "Data Category", - "description": "Data category classification (e.g. standard, special-category).", - "examples": [ - "sensitive" - ], - "type": "string", - "enum": [ - "non-sensitive", - "standard", - "sensitive", - "special-category" - ] - }, - "dataschemaversion": { - "title": "Data Schema Version", - "description": "Matches semantic versioning format with fixed major version (Not part of cloudevents spec?)", - "type": "string", - "pattern": "^1\\.\\d+\\.\\d+$" - } - }, - "required": [ - "specversion", - "id", - "source", - "subject", - "type", - "time", - "dataschema", - "data", - "traceparent", - "recordedtime", - "severitynumber", - "dataschemaversion" - ] -} \ No newline at end of file diff --git a/internal/events/schemas/events/letter.PENDING.schema.json b/internal/events/schemas/events/letter.PENDING.schema.json deleted file mode 100644 index 852166a57..000000000 --- a/internal/events/schemas/events/letter.PENDING.schema.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "title": "letter.PENDING Event", - "description": "Event schema for letter status change to PENDING", - "type": "object", - "properties": { - "specversion": { - "title": "CloudEvents spec version", - "description": "CloudEvents specification version (fixed to 1.0).", - "examples": [ - "1.0" - ], - "type": "string", - "enum": [ - "1.0" - ] - }, - "id": { - "title": "Event ID", - "description": "Unique identifier for this event instance (UUID).", - "examples": [ - "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111" - ], - "type": "string", - "minLength": 1, - "format": "uuid", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" - }, - "source": { - "title": "Event Source", - "description": "Logical event producer path within the supplier-api domain", - "type": "string", - "pattern": "^\\/data-plane\\/supplier-api(?:\\/.*)?$" - }, - "subject": { - "title": "Event Subject", - "description": "Resource path (no leading slash) within the source made of segments separated by '/'.", - "examples": [ - "letter-origin/letter-rendering/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479" - ], - "type": "string", - "pattern": "^letter-origin\\/[a-z0-9-]+\\/letter\\/[^/]+(?:\\/.*)?" - }, - "type": { - "title": "Letter PENDING event type", - "description": "Event type using reverse-DNS style", - "examples": [ - "uk.nhs.notify.supplier-api.letter.PENDING.v1" - ], - "type": "string", - "enum": [ - "uk.nhs.notify.supplier-api.letter.PENDING.v1" - ] - }, - "time": { - "title": "Event Time", - "description": "Timestamp when the event occurred (RFC 3339).", - "examples": [ - "2025-10-01T10:15:30.000Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "datacontenttype": { - "title": "Data Content Type", - "description": "Media type for the data field (fixed to application/json).", - "examples": [ - "application/json" - ], - "type": "string", - "enum": [ - "application/json" - ] - }, - "dataschema": { - "title": "Data Schema URI", - "description": "URI of a schema that describes the event data\n\nData schema version must match the major version indicated by the type", - "examples": [ - "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json" - ], - "type": "string", - "pattern": "^https:\\/\\/notify\\.nhs\\.uk\\/cloudevents\\/schemas\\/supplier-api\\/letter.PENDING.1\\.\\d+\\.\\d+\\.schema.json$" - }, - "data": { - "type": "object", - "properties": { - "domainId": { - "type": "string" - }, - "origin": { - "title": "Letter origin", - "description": "Identifiers captured from the original event that introduced the letter to the supplier-api domain.\n\nThe identifier will be included as the origin domain in the subject of any corresponding events emitted by the supplier-api domain.", - "examples": [ - { - "domain": "letter-rendering", - "subject": "customer/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-rendering/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", - "event": "00f3b388-bbe9-41c9-9e76-052d37ee8988" - } - ], - "type": "object", - "properties": { - "domain": { - "title": "Domain ID", - "description": "The domain which requested this letter", - "type": "string" - }, - "source": { - "title": "Event source", - "description": "The source of the event which created this letter", - "type": "string" - }, - "subject": { - "title": "Event subject", - "description": "The subject of the event which created this letter, scoped to source", - "type": "string" - }, - "event": { - "title": "Event ID", - "description": "The ID of the event which created this letter", - "type": "string" - } - }, - "required": [ - "domain", - "source", - "subject", - "event" - ] - }, - "specificationId": { - "title": "Specification ID", - "description": "Reference to the letter specification which was used to produce a letter pack for this request.", - "examples": [ - "1y3q9v1zzzz" - ], - "type": "string" - }, - "groupId": { - "title": "Group ID", - "description": "Identifier for the group which this letter assigned to for reporting purposes.", - "examples": [ - "client_template", - "00f3b388-bbe9-41c9-9e76-052d37ee8988_20a1ab22-6136-47ae-ac0f-989f382be8df" - ], - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "PENDING" - ] - }, - "reasonCode": { - "title": "Reason Code", - "description": "Optional reason code for the status change, if applicable.", - "examples": [ - "R01", - "R08" - ], - "type": "string" - }, - "reasonText": { - "title": "Reason Text", - "description": "Optional human-readable reason for the status change, if applicable.", - "examples": [ - "Undeliverable", - "Recipient moved" - ], - "type": "string" - } - }, - "required": [ - "domainId", - "origin", - "specificationId", - "groupId", - "status" - ] - }, - "traceparent": { - "title": "Traceparent", - "description": "W3C Trace Context traceparent header value.", - "examples": [ - "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" - ], - "type": "string", - "minLength": 1, - "pattern": "^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$" - }, - "tracestate": { - "title": "Tracestate", - "description": "Optional W3C Trace Context tracestate header value.", - "examples": [ - "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE" - ], - "type": "string" - }, - "partitionkey": { - "title": "Partition Key", - "description": "Partition / ordering key (lowercase alphanumerics and hyphen, 1-64 chars).", - "examples": [ - "customer-920fca11" - ], - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^[a-z0-9-]+$" - }, - "recordedtime": { - "title": "Recorded Time", - "description": "Timestamp when the event was recorded/persisted (should be >= time).", - "examples": [ - "2025-10-01T10:15:30.250Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "sampledrate": { - "title": "Sampled Rate", - "description": "Sampling factor: number of similar occurrences this event represents.", - "examples": [ - 5 - ], - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "sequence": { - "title": "Sequence", - "description": "Zero-padded 20 digit numeric sequence (lexicographically sortable).", - "examples": [ - "00000000000000000042" - ], - "type": "string", - "pattern": "^\\d{20}$" - }, - "severitytext": { - "title": "Severity Text", - "description": "Log severity level name.", - "examples": [ - "DEBUG" - ], - "type": "string", - "enum": [ - "TRACE", - "DEBUG", - "INFO", - "WARN", - "ERROR", - "FATAL" - ] - }, - "severitynumber": { - "title": "Severity Number", - "description": "Numeric severity (TRACE=0, DEBUG=1, INFO=2, WARN=3, ERROR=4, FATAL=5).", - "examples": [ - 1 - ], - "type": "integer", - "minimum": 0, - "maximum": 5 - }, - "dataclassification": { - "title": "Data Classification", - "description": "Data sensitivity classification.", - "examples": [ - "restricted" - ], - "type": "string", - "enum": [ - "public", - "internal", - "confidential", - "restricted" - ] - }, - "dataregulation": { - "title": "Data Regulation", - "description": "Regulatory regime tag applied to this data.", - "examples": [ - "ISO-27001" - ], - "type": "string", - "enum": [ - "GDPR", - "HIPAA", - "PCI-DSS", - "ISO-27001", - "NIST-800-53", - "CCPA" - ] - }, - "datacategory": { - "title": "Data Category", - "description": "Data category classification (e.g. standard, special-category).", - "examples": [ - "sensitive" - ], - "type": "string", - "enum": [ - "non-sensitive", - "standard", - "sensitive", - "special-category" - ] - }, - "dataschemaversion": { - "title": "Data Schema Version", - "description": "Matches semantic versioning format with fixed major version (Not part of cloudevents spec?)", - "type": "string", - "pattern": "^1\\.\\d+\\.\\d+$" - } - }, - "required": [ - "specversion", - "id", - "source", - "subject", - "type", - "time", - "dataschema", - "data", - "traceparent", - "recordedtime", - "severitynumber", - "dataschemaversion" - ] -} \ No newline at end of file diff --git a/internal/events/schemas/events/letter.PRINTED.schema.json b/internal/events/schemas/events/letter.PRINTED.schema.json deleted file mode 100644 index cb013e4be..000000000 --- a/internal/events/schemas/events/letter.PRINTED.schema.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "title": "letter.PRINTED Event", - "description": "Event schema for letter status change to PRINTED", - "type": "object", - "properties": { - "specversion": { - "title": "CloudEvents spec version", - "description": "CloudEvents specification version (fixed to 1.0).", - "examples": [ - "1.0" - ], - "type": "string", - "enum": [ - "1.0" - ] - }, - "id": { - "title": "Event ID", - "description": "Unique identifier for this event instance (UUID).", - "examples": [ - "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111" - ], - "type": "string", - "minLength": 1, - "format": "uuid", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" - }, - "source": { - "title": "Event Source", - "description": "Logical event producer path within the supplier-api domain", - "type": "string", - "pattern": "^\\/data-plane\\/supplier-api(?:\\/.*)?$" - }, - "subject": { - "title": "Event Subject", - "description": "Resource path (no leading slash) within the source made of segments separated by '/'.", - "examples": [ - "letter-origin/letter-rendering/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479" - ], - "type": "string", - "pattern": "^letter-origin\\/[a-z0-9-]+\\/letter\\/[^/]+(?:\\/.*)?" - }, - "type": { - "title": "Letter PRINTED event type", - "description": "Event type using reverse-DNS style", - "examples": [ - "uk.nhs.notify.supplier-api.letter.PRINTED.v1" - ], - "type": "string", - "enum": [ - "uk.nhs.notify.supplier-api.letter.PRINTED.v1" - ] - }, - "time": { - "title": "Event Time", - "description": "Timestamp when the event occurred (RFC 3339).", - "examples": [ - "2025-10-01T10:15:30.000Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "datacontenttype": { - "title": "Data Content Type", - "description": "Media type for the data field (fixed to application/json).", - "examples": [ - "application/json" - ], - "type": "string", - "enum": [ - "application/json" - ] - }, - "dataschema": { - "title": "Data Schema URI", - "description": "URI of a schema that describes the event data\n\nData schema version must match the major version indicated by the type", - "examples": [ - "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json" - ], - "type": "string", - "pattern": "^https:\\/\\/notify\\.nhs\\.uk\\/cloudevents\\/schemas\\/supplier-api\\/letter.PRINTED.1\\.\\d+\\.\\d+\\.schema.json$" - }, - "data": { - "type": "object", - "properties": { - "domainId": { - "type": "string" - }, - "origin": { - "title": "Letter origin", - "description": "Identifiers captured from the original event that introduced the letter to the supplier-api domain.\n\nThe identifier will be included as the origin domain in the subject of any corresponding events emitted by the supplier-api domain.", - "examples": [ - { - "domain": "letter-rendering", - "subject": "customer/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-rendering/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", - "event": "00f3b388-bbe9-41c9-9e76-052d37ee8988" - } - ], - "type": "object", - "properties": { - "domain": { - "title": "Domain ID", - "description": "The domain which requested this letter", - "type": "string" - }, - "source": { - "title": "Event source", - "description": "The source of the event which created this letter", - "type": "string" - }, - "subject": { - "title": "Event subject", - "description": "The subject of the event which created this letter, scoped to source", - "type": "string" - }, - "event": { - "title": "Event ID", - "description": "The ID of the event which created this letter", - "type": "string" - } - }, - "required": [ - "domain", - "source", - "subject", - "event" - ] - }, - "specificationId": { - "title": "Specification ID", - "description": "Reference to the letter specification which was used to produce a letter pack for this request.", - "examples": [ - "1y3q9v1zzzz" - ], - "type": "string" - }, - "groupId": { - "title": "Group ID", - "description": "Identifier for the group which this letter assigned to for reporting purposes.", - "examples": [ - "client_template", - "00f3b388-bbe9-41c9-9e76-052d37ee8988_20a1ab22-6136-47ae-ac0f-989f382be8df" - ], - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "PRINTED" - ] - }, - "reasonCode": { - "title": "Reason Code", - "description": "Optional reason code for the status change, if applicable.", - "examples": [ - "R01", - "R08" - ], - "type": "string" - }, - "reasonText": { - "title": "Reason Text", - "description": "Optional human-readable reason for the status change, if applicable.", - "examples": [ - "Undeliverable", - "Recipient moved" - ], - "type": "string" - } - }, - "required": [ - "domainId", - "origin", - "specificationId", - "groupId", - "status" - ] - }, - "traceparent": { - "title": "Traceparent", - "description": "W3C Trace Context traceparent header value.", - "examples": [ - "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" - ], - "type": "string", - "minLength": 1, - "pattern": "^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$" - }, - "tracestate": { - "title": "Tracestate", - "description": "Optional W3C Trace Context tracestate header value.", - "examples": [ - "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE" - ], - "type": "string" - }, - "partitionkey": { - "title": "Partition Key", - "description": "Partition / ordering key (lowercase alphanumerics and hyphen, 1-64 chars).", - "examples": [ - "customer-920fca11" - ], - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^[a-z0-9-]+$" - }, - "recordedtime": { - "title": "Recorded Time", - "description": "Timestamp when the event was recorded/persisted (should be >= time).", - "examples": [ - "2025-10-01T10:15:30.250Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "sampledrate": { - "title": "Sampled Rate", - "description": "Sampling factor: number of similar occurrences this event represents.", - "examples": [ - 5 - ], - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "sequence": { - "title": "Sequence", - "description": "Zero-padded 20 digit numeric sequence (lexicographically sortable).", - "examples": [ - "00000000000000000042" - ], - "type": "string", - "pattern": "^\\d{20}$" - }, - "severitytext": { - "title": "Severity Text", - "description": "Log severity level name.", - "examples": [ - "DEBUG" - ], - "type": "string", - "enum": [ - "TRACE", - "DEBUG", - "INFO", - "WARN", - "ERROR", - "FATAL" - ] - }, - "severitynumber": { - "title": "Severity Number", - "description": "Numeric severity (TRACE=0, DEBUG=1, INFO=2, WARN=3, ERROR=4, FATAL=5).", - "examples": [ - 1 - ], - "type": "integer", - "minimum": 0, - "maximum": 5 - }, - "dataclassification": { - "title": "Data Classification", - "description": "Data sensitivity classification.", - "examples": [ - "restricted" - ], - "type": "string", - "enum": [ - "public", - "internal", - "confidential", - "restricted" - ] - }, - "dataregulation": { - "title": "Data Regulation", - "description": "Regulatory regime tag applied to this data.", - "examples": [ - "ISO-27001" - ], - "type": "string", - "enum": [ - "GDPR", - "HIPAA", - "PCI-DSS", - "ISO-27001", - "NIST-800-53", - "CCPA" - ] - }, - "datacategory": { - "title": "Data Category", - "description": "Data category classification (e.g. standard, special-category).", - "examples": [ - "sensitive" - ], - "type": "string", - "enum": [ - "non-sensitive", - "standard", - "sensitive", - "special-category" - ] - }, - "dataschemaversion": { - "title": "Data Schema Version", - "description": "Matches semantic versioning format with fixed major version (Not part of cloudevents spec?)", - "type": "string", - "pattern": "^1\\.\\d+\\.\\d+$" - } - }, - "required": [ - "specversion", - "id", - "source", - "subject", - "type", - "time", - "dataschema", - "data", - "traceparent", - "recordedtime", - "severitynumber", - "dataschemaversion" - ] -} \ No newline at end of file diff --git a/internal/events/schemas/events/letter.REJECTED.schema.json b/internal/events/schemas/events/letter.REJECTED.schema.json deleted file mode 100644 index ae24a5823..000000000 --- a/internal/events/schemas/events/letter.REJECTED.schema.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "title": "letter.REJECTED Event", - "description": "Event schema for letter status change to REJECTED", - "type": "object", - "properties": { - "specversion": { - "title": "CloudEvents spec version", - "description": "CloudEvents specification version (fixed to 1.0).", - "examples": [ - "1.0" - ], - "type": "string", - "enum": [ - "1.0" - ] - }, - "id": { - "title": "Event ID", - "description": "Unique identifier for this event instance (UUID).", - "examples": [ - "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111" - ], - "type": "string", - "minLength": 1, - "format": "uuid", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" - }, - "source": { - "title": "Event Source", - "description": "Logical event producer path within the supplier-api domain", - "type": "string", - "pattern": "^\\/data-plane\\/supplier-api(?:\\/.*)?$" - }, - "subject": { - "title": "Event Subject", - "description": "Resource path (no leading slash) within the source made of segments separated by '/'.", - "examples": [ - "letter-origin/letter-rendering/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479" - ], - "type": "string", - "pattern": "^letter-origin\\/[a-z0-9-]+\\/letter\\/[^/]+(?:\\/.*)?" - }, - "type": { - "title": "Letter REJECTED event type", - "description": "Event type using reverse-DNS style", - "examples": [ - "uk.nhs.notify.supplier-api.letter.REJECTED.v1" - ], - "type": "string", - "enum": [ - "uk.nhs.notify.supplier-api.letter.REJECTED.v1" - ] - }, - "time": { - "title": "Event Time", - "description": "Timestamp when the event occurred (RFC 3339).", - "examples": [ - "2025-10-01T10:15:30.000Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "datacontenttype": { - "title": "Data Content Type", - "description": "Media type for the data field (fixed to application/json).", - "examples": [ - "application/json" - ], - "type": "string", - "enum": [ - "application/json" - ] - }, - "dataschema": { - "title": "Data Schema URI", - "description": "URI of a schema that describes the event data\n\nData schema version must match the major version indicated by the type", - "examples": [ - "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json" - ], - "type": "string", - "pattern": "^https:\\/\\/notify\\.nhs\\.uk\\/cloudevents\\/schemas\\/supplier-api\\/letter.REJECTED.1\\.\\d+\\.\\d+\\.schema.json$" - }, - "data": { - "type": "object", - "properties": { - "domainId": { - "type": "string" - }, - "origin": { - "title": "Letter origin", - "description": "Identifiers captured from the original event that introduced the letter to the supplier-api domain.\n\nThe identifier will be included as the origin domain in the subject of any corresponding events emitted by the supplier-api domain.", - "examples": [ - { - "domain": "letter-rendering", - "subject": "customer/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-rendering/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", - "event": "00f3b388-bbe9-41c9-9e76-052d37ee8988" - } - ], - "type": "object", - "properties": { - "domain": { - "title": "Domain ID", - "description": "The domain which requested this letter", - "type": "string" - }, - "source": { - "title": "Event source", - "description": "The source of the event which created this letter", - "type": "string" - }, - "subject": { - "title": "Event subject", - "description": "The subject of the event which created this letter, scoped to source", - "type": "string" - }, - "event": { - "title": "Event ID", - "description": "The ID of the event which created this letter", - "type": "string" - } - }, - "required": [ - "domain", - "source", - "subject", - "event" - ] - }, - "specificationId": { - "title": "Specification ID", - "description": "Reference to the letter specification which was used to produce a letter pack for this request.", - "examples": [ - "1y3q9v1zzzz" - ], - "type": "string" - }, - "groupId": { - "title": "Group ID", - "description": "Identifier for the group which this letter assigned to for reporting purposes.", - "examples": [ - "client_template", - "00f3b388-bbe9-41c9-9e76-052d37ee8988_20a1ab22-6136-47ae-ac0f-989f382be8df" - ], - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "REJECTED" - ] - }, - "reasonCode": { - "title": "Reason Code", - "description": "Optional reason code for the status change, if applicable.", - "examples": [ - "R01", - "R08" - ], - "type": "string" - }, - "reasonText": { - "title": "Reason Text", - "description": "Optional human-readable reason for the status change, if applicable.", - "examples": [ - "Undeliverable", - "Recipient moved" - ], - "type": "string" - } - }, - "required": [ - "domainId", - "origin", - "specificationId", - "groupId", - "status" - ] - }, - "traceparent": { - "title": "Traceparent", - "description": "W3C Trace Context traceparent header value.", - "examples": [ - "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" - ], - "type": "string", - "minLength": 1, - "pattern": "^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$" - }, - "tracestate": { - "title": "Tracestate", - "description": "Optional W3C Trace Context tracestate header value.", - "examples": [ - "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE" - ], - "type": "string" - }, - "partitionkey": { - "title": "Partition Key", - "description": "Partition / ordering key (lowercase alphanumerics and hyphen, 1-64 chars).", - "examples": [ - "customer-920fca11" - ], - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^[a-z0-9-]+$" - }, - "recordedtime": { - "title": "Recorded Time", - "description": "Timestamp when the event was recorded/persisted (should be >= time).", - "examples": [ - "2025-10-01T10:15:30.250Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "sampledrate": { - "title": "Sampled Rate", - "description": "Sampling factor: number of similar occurrences this event represents.", - "examples": [ - 5 - ], - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "sequence": { - "title": "Sequence", - "description": "Zero-padded 20 digit numeric sequence (lexicographically sortable).", - "examples": [ - "00000000000000000042" - ], - "type": "string", - "pattern": "^\\d{20}$" - }, - "severitytext": { - "title": "Severity Text", - "description": "Log severity level name.", - "examples": [ - "DEBUG" - ], - "type": "string", - "enum": [ - "TRACE", - "DEBUG", - "INFO", - "WARN", - "ERROR", - "FATAL" - ] - }, - "severitynumber": { - "title": "Severity Number", - "description": "Numeric severity (TRACE=0, DEBUG=1, INFO=2, WARN=3, ERROR=4, FATAL=5).", - "examples": [ - 1 - ], - "type": "integer", - "minimum": 0, - "maximum": 5 - }, - "dataclassification": { - "title": "Data Classification", - "description": "Data sensitivity classification.", - "examples": [ - "restricted" - ], - "type": "string", - "enum": [ - "public", - "internal", - "confidential", - "restricted" - ] - }, - "dataregulation": { - "title": "Data Regulation", - "description": "Regulatory regime tag applied to this data.", - "examples": [ - "ISO-27001" - ], - "type": "string", - "enum": [ - "GDPR", - "HIPAA", - "PCI-DSS", - "ISO-27001", - "NIST-800-53", - "CCPA" - ] - }, - "datacategory": { - "title": "Data Category", - "description": "Data category classification (e.g. standard, special-category).", - "examples": [ - "sensitive" - ], - "type": "string", - "enum": [ - "non-sensitive", - "standard", - "sensitive", - "special-category" - ] - }, - "dataschemaversion": { - "title": "Data Schema Version", - "description": "Matches semantic versioning format with fixed major version (Not part of cloudevents spec?)", - "type": "string", - "pattern": "^1\\.\\d+\\.\\d+$" - } - }, - "required": [ - "specversion", - "id", - "source", - "subject", - "type", - "time", - "dataschema", - "data", - "traceparent", - "recordedtime", - "severitynumber", - "dataschemaversion" - ] -} \ No newline at end of file diff --git a/internal/events/schemas/events/letter.RETURNED.schema.json b/internal/events/schemas/events/letter.RETURNED.schema.json deleted file mode 100644 index ca0f8fbf9..000000000 --- a/internal/events/schemas/events/letter.RETURNED.schema.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "title": "letter.RETURNED Event", - "description": "Event schema for letter status change to RETURNED", - "type": "object", - "properties": { - "specversion": { - "title": "CloudEvents spec version", - "description": "CloudEvents specification version (fixed to 1.0).", - "examples": [ - "1.0" - ], - "type": "string", - "enum": [ - "1.0" - ] - }, - "id": { - "title": "Event ID", - "description": "Unique identifier for this event instance (UUID).", - "examples": [ - "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111" - ], - "type": "string", - "minLength": 1, - "format": "uuid", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" - }, - "source": { - "title": "Event Source", - "description": "Logical event producer path within the supplier-api domain", - "type": "string", - "pattern": "^\\/data-plane\\/supplier-api(?:\\/.*)?$" - }, - "subject": { - "title": "Event Subject", - "description": "Resource path (no leading slash) within the source made of segments separated by '/'.", - "examples": [ - "letter-origin/letter-rendering/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479" - ], - "type": "string", - "pattern": "^letter-origin\\/[a-z0-9-]+\\/letter\\/[^/]+(?:\\/.*)?" - }, - "type": { - "title": "Letter RETURNED event type", - "description": "Event type using reverse-DNS style", - "examples": [ - "uk.nhs.notify.supplier-api.letter.RETURNED.v1" - ], - "type": "string", - "enum": [ - "uk.nhs.notify.supplier-api.letter.RETURNED.v1" - ] - }, - "time": { - "title": "Event Time", - "description": "Timestamp when the event occurred (RFC 3339).", - "examples": [ - "2025-10-01T10:15:30.000Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "datacontenttype": { - "title": "Data Content Type", - "description": "Media type for the data field (fixed to application/json).", - "examples": [ - "application/json" - ], - "type": "string", - "enum": [ - "application/json" - ] - }, - "dataschema": { - "title": "Data Schema URI", - "description": "URI of a schema that describes the event data\n\nData schema version must match the major version indicated by the type", - "examples": [ - "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json" - ], - "type": "string", - "pattern": "^https:\\/\\/notify\\.nhs\\.uk\\/cloudevents\\/schemas\\/supplier-api\\/letter.RETURNED.1\\.\\d+\\.\\d+\\.schema.json$" - }, - "data": { - "type": "object", - "properties": { - "domainId": { - "type": "string" - }, - "origin": { - "title": "Letter origin", - "description": "Identifiers captured from the original event that introduced the letter to the supplier-api domain.\n\nThe identifier will be included as the origin domain in the subject of any corresponding events emitted by the supplier-api domain.", - "examples": [ - { - "domain": "letter-rendering", - "subject": "customer/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-rendering/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", - "event": "00f3b388-bbe9-41c9-9e76-052d37ee8988" - } - ], - "type": "object", - "properties": { - "domain": { - "title": "Domain ID", - "description": "The domain which requested this letter", - "type": "string" - }, - "source": { - "title": "Event source", - "description": "The source of the event which created this letter", - "type": "string" - }, - "subject": { - "title": "Event subject", - "description": "The subject of the event which created this letter, scoped to source", - "type": "string" - }, - "event": { - "title": "Event ID", - "description": "The ID of the event which created this letter", - "type": "string" - } - }, - "required": [ - "domain", - "source", - "subject", - "event" - ] - }, - "specificationId": { - "title": "Specification ID", - "description": "Reference to the letter specification which was used to produce a letter pack for this request.", - "examples": [ - "1y3q9v1zzzz" - ], - "type": "string" - }, - "groupId": { - "title": "Group ID", - "description": "Identifier for the group which this letter assigned to for reporting purposes.", - "examples": [ - "client_template", - "00f3b388-bbe9-41c9-9e76-052d37ee8988_20a1ab22-6136-47ae-ac0f-989f382be8df" - ], - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "RETURNED" - ] - }, - "reasonCode": { - "title": "Reason Code", - "description": "Optional reason code for the status change, if applicable.", - "examples": [ - "R01", - "R08" - ], - "type": "string" - }, - "reasonText": { - "title": "Reason Text", - "description": "Optional human-readable reason for the status change, if applicable.", - "examples": [ - "Undeliverable", - "Recipient moved" - ], - "type": "string" - } - }, - "required": [ - "domainId", - "origin", - "specificationId", - "groupId", - "status" - ] - }, - "traceparent": { - "title": "Traceparent", - "description": "W3C Trace Context traceparent header value.", - "examples": [ - "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" - ], - "type": "string", - "minLength": 1, - "pattern": "^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$" - }, - "tracestate": { - "title": "Tracestate", - "description": "Optional W3C Trace Context tracestate header value.", - "examples": [ - "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE" - ], - "type": "string" - }, - "partitionkey": { - "title": "Partition Key", - "description": "Partition / ordering key (lowercase alphanumerics and hyphen, 1-64 chars).", - "examples": [ - "customer-920fca11" - ], - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^[a-z0-9-]+$" - }, - "recordedtime": { - "title": "Recorded Time", - "description": "Timestamp when the event was recorded/persisted (should be >= time).", - "examples": [ - "2025-10-01T10:15:30.250Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "sampledrate": { - "title": "Sampled Rate", - "description": "Sampling factor: number of similar occurrences this event represents.", - "examples": [ - 5 - ], - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "sequence": { - "title": "Sequence", - "description": "Zero-padded 20 digit numeric sequence (lexicographically sortable).", - "examples": [ - "00000000000000000042" - ], - "type": "string", - "pattern": "^\\d{20}$" - }, - "severitytext": { - "title": "Severity Text", - "description": "Log severity level name.", - "examples": [ - "DEBUG" - ], - "type": "string", - "enum": [ - "TRACE", - "DEBUG", - "INFO", - "WARN", - "ERROR", - "FATAL" - ] - }, - "severitynumber": { - "title": "Severity Number", - "description": "Numeric severity (TRACE=0, DEBUG=1, INFO=2, WARN=3, ERROR=4, FATAL=5).", - "examples": [ - 1 - ], - "type": "integer", - "minimum": 0, - "maximum": 5 - }, - "dataclassification": { - "title": "Data Classification", - "description": "Data sensitivity classification.", - "examples": [ - "restricted" - ], - "type": "string", - "enum": [ - "public", - "internal", - "confidential", - "restricted" - ] - }, - "dataregulation": { - "title": "Data Regulation", - "description": "Regulatory regime tag applied to this data.", - "examples": [ - "ISO-27001" - ], - "type": "string", - "enum": [ - "GDPR", - "HIPAA", - "PCI-DSS", - "ISO-27001", - "NIST-800-53", - "CCPA" - ] - }, - "datacategory": { - "title": "Data Category", - "description": "Data category classification (e.g. standard, special-category).", - "examples": [ - "sensitive" - ], - "type": "string", - "enum": [ - "non-sensitive", - "standard", - "sensitive", - "special-category" - ] - }, - "dataschemaversion": { - "title": "Data Schema Version", - "description": "Matches semantic versioning format with fixed major version (Not part of cloudevents spec?)", - "type": "string", - "pattern": "^1\\.\\d+\\.\\d+$" - } - }, - "required": [ - "specversion", - "id", - "source", - "subject", - "type", - "time", - "dataschema", - "data", - "traceparent", - "recordedtime", - "severitynumber", - "dataschemaversion" - ] -} \ No newline at end of file diff --git a/internal/events/schemas/events/letter.any.schema.json b/internal/events/schemas/events/letter.any.schema.json deleted file mode 100644 index b5771b7aa..000000000 --- a/internal/events/schemas/events/letter.any.schema.json +++ /dev/null @@ -1,358 +0,0 @@ -{ - "title": "letter.* Event", - "description": "Event schema for generic letter status change", - "type": "object", - "properties": { - "specversion": { - "title": "CloudEvents spec version", - "description": "CloudEvents specification version (fixed to 1.0).", - "examples": [ - "1.0" - ], - "type": "string", - "enum": [ - "1.0" - ] - }, - "id": { - "title": "Event ID", - "description": "Unique identifier for this event instance (UUID).", - "examples": [ - "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111" - ], - "type": "string", - "minLength": 1, - "format": "uuid", - "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$" - }, - "source": { - "title": "Event Source", - "description": "Logical event producer path within the supplier-api domain", - "type": "string", - "pattern": "^\\/data-plane\\/supplier-api(?:\\/.*)?$" - }, - "subject": { - "title": "Event Subject", - "description": "Resource path (no leading slash) within the source made of segments separated by '/'.", - "examples": [ - "letter-origin/letter-rendering/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479" - ], - "type": "string", - "pattern": "^letter-origin\\/[a-z0-9-]+\\/letter\\/[^/]+(?:\\/.*)?" - }, - "type": { - "title": "Letter event type", - "description": "Event type using reverse-DNS style", - "examples": [ - "uk.nhs.notify.supplier-api.letter.PENDING.v1", - "uk.nhs.notify.supplier-api.letter.ACCEPTED.v1", - "uk.nhs.notify.supplier-api.letter.DISPATCHED.v1" - ], - "type": "string", - "enum": [ - "uk.nhs.notify.supplier-api.letter.PENDING.v1", - "uk.nhs.notify.supplier-api.letter.ACCEPTED.v1", - "uk.nhs.notify.supplier-api.letter.REJECTED.v1", - "uk.nhs.notify.supplier-api.letter.PRINTED.v1", - "uk.nhs.notify.supplier-api.letter.ENCLOSED.v1", - "uk.nhs.notify.supplier-api.letter.CANCELLED.v1", - "uk.nhs.notify.supplier-api.letter.DISPATCHED.v1", - "uk.nhs.notify.supplier-api.letter.FAILED.v1", - "uk.nhs.notify.supplier-api.letter.RETURNED.v1", - "uk.nhs.notify.supplier-api.letter.FORWARDED.v1", - "uk.nhs.notify.supplier-api.letter.DELIVERED.v1" - ] - }, - "time": { - "title": "Event Time", - "description": "Timestamp when the event occurred (RFC 3339).", - "examples": [ - "2025-10-01T10:15:30.000Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "datacontenttype": { - "title": "Data Content Type", - "description": "Media type for the data field (fixed to application/json).", - "examples": [ - "application/json" - ], - "type": "string", - "enum": [ - "application/json" - ] - }, - "dataschema": { - "title": "Data Schema URI", - "description": "URI of a schema that describes the event data\n\nData schema version must match the major version indicated by the type", - "examples": [ - "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json" - ], - "type": "string", - "pattern": "^https:\\/\\/notify\\.nhs\\.uk\\/cloudevents\\/schemas\\/supplier-api\\/letter\\.(?PENDING|ACCEPTED|REJECTED|PRINTED|ENCLOSED|CANCELLED|DISPATCHED|FAILED|RETURNED|FORWARDED|DELIVERED)\\.1\\.\\d+\\.\\d+\\.schema.json$" - }, - "data": { - "type": "object", - "properties": { - "domainId": { - "type": "string" - }, - "origin": { - "title": "Letter origin", - "description": "Identifiers captured from the original event that introduced the letter to the supplier-api domain.\n\nThe identifier will be included as the origin domain in the subject of any corresponding events emitted by the supplier-api domain.", - "examples": [ - { - "domain": "letter-rendering", - "subject": "customer/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-rendering/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", - "event": "00f3b388-bbe9-41c9-9e76-052d37ee8988" - } - ], - "type": "object", - "properties": { - "domain": { - "title": "Domain ID", - "description": "The domain which requested this letter", - "type": "string" - }, - "source": { - "title": "Event source", - "description": "The source of the event which created this letter", - "type": "string" - }, - "subject": { - "title": "Event subject", - "description": "The subject of the event which created this letter, scoped to source", - "type": "string" - }, - "event": { - "title": "Event ID", - "description": "The ID of the event which created this letter", - "type": "string" - } - }, - "required": [ - "domain", - "source", - "subject", - "event" - ] - }, - "specificationId": { - "title": "Specification ID", - "description": "Reference to the letter specification which was used to produce a letter pack for this request.", - "examples": [ - "1y3q9v1zzzz" - ], - "type": "string" - }, - "groupId": { - "title": "Group ID", - "description": "Identifier for the group which this letter assigned to for reporting purposes.", - "examples": [ - "client_template", - "00f3b388-bbe9-41c9-9e76-052d37ee8988_20a1ab22-6136-47ae-ac0f-989f382be8df" - ], - "type": "string" - }, - "status": { - "title": "Letter Status", - "description": "The status of a letter in the supplier-api domain.", - "examples": [ - "ACCEPTED", - "REJECTED", - "PRINTED" - ], - "type": "string", - "enum": [ - "PENDING", - "ACCEPTED", - "REJECTED", - "PRINTED", - "ENCLOSED", - "CANCELLED", - "DISPATCHED", - "FAILED", - "RETURNED", - "FORWARDED", - "DELIVERED" - ] - }, - "reasonCode": { - "title": "Reason Code", - "description": "Optional reason code for the status change, if applicable.", - "examples": [ - "R01", - "R08" - ], - "type": "string" - }, - "reasonText": { - "title": "Reason Text", - "description": "Optional human-readable reason for the status change, if applicable.", - "examples": [ - "Undeliverable", - "Recipient moved" - ], - "type": "string" - } - }, - "required": [ - "domainId", - "origin", - "specificationId", - "groupId", - "status" - ] - }, - "traceparent": { - "title": "Traceparent", - "description": "W3C Trace Context traceparent header value.", - "examples": [ - "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" - ], - "type": "string", - "minLength": 1, - "pattern": "^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$" - }, - "tracestate": { - "title": "Tracestate", - "description": "Optional W3C Trace Context tracestate header value.", - "examples": [ - "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE" - ], - "type": "string" - }, - "partitionkey": { - "title": "Partition Key", - "description": "Partition / ordering key (lowercase alphanumerics and hyphen, 1-64 chars).", - "examples": [ - "customer-920fca11" - ], - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^[a-z0-9-]+$" - }, - "recordedtime": { - "title": "Recorded Time", - "description": "Timestamp when the event was recorded/persisted (should be >= time).", - "examples": [ - "2025-10-01T10:15:30.250Z" - ], - "type": "string", - "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" - }, - "sampledrate": { - "title": "Sampled Rate", - "description": "Sampling factor: number of similar occurrences this event represents.", - "examples": [ - 5 - ], - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, - "sequence": { - "title": "Sequence", - "description": "Zero-padded 20 digit numeric sequence (lexicographically sortable).", - "examples": [ - "00000000000000000042" - ], - "type": "string", - "pattern": "^\\d{20}$" - }, - "severitytext": { - "title": "Severity Text", - "description": "Log severity level name.", - "examples": [ - "DEBUG" - ], - "type": "string", - "enum": [ - "TRACE", - "DEBUG", - "INFO", - "WARN", - "ERROR", - "FATAL" - ] - }, - "severitynumber": { - "title": "Severity Number", - "description": "Numeric severity (TRACE=0, DEBUG=1, INFO=2, WARN=3, ERROR=4, FATAL=5).", - "examples": [ - 1 - ], - "type": "integer", - "minimum": 0, - "maximum": 5 - }, - "dataclassification": { - "title": "Data Classification", - "description": "Data sensitivity classification.", - "examples": [ - "restricted" - ], - "type": "string", - "enum": [ - "public", - "internal", - "confidential", - "restricted" - ] - }, - "dataregulation": { - "title": "Data Regulation", - "description": "Regulatory regime tag applied to this data.", - "examples": [ - "ISO-27001" - ], - "type": "string", - "enum": [ - "GDPR", - "HIPAA", - "PCI-DSS", - "ISO-27001", - "NIST-800-53", - "CCPA" - ] - }, - "datacategory": { - "title": "Data Category", - "description": "Data category classification (e.g. standard, special-category).", - "examples": [ - "sensitive" - ], - "type": "string", - "enum": [ - "non-sensitive", - "standard", - "sensitive", - "special-category" - ] - }, - "dataschemaversion": { - "title": "Data Schema Version", - "description": "Matches semantic versioning format with fixed major version (Not part of cloudevents spec?)", - "type": "string", - "pattern": "^1\\.\\d+\\.\\d+$" - } - }, - "required": [ - "specversion", - "id", - "source", - "subject", - "type", - "time", - "dataschema", - "data", - "traceparent", - "recordedtime", - "severitynumber", - "dataschemaversion" - ] -} \ No newline at end of file diff --git a/internal/events/schemas/examples/letter.ACCEPTED.json b/internal/events/schemas/examples/letter.ACCEPTED.json index 7d22e4132..f9a1177ce 100644 --- a/internal/events/schemas/examples/letter.ACCEPTED.json +++ b/internal/events/schemas/examples/letter.ACCEPTED.json @@ -13,7 +13,6 @@ }, "datacontenttype": "application/json", "dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json", - "dataschemaversion": "1.0.0", "id": "23f1f09c-a555-4d9b-8405-0b33490bc920", "recordedtime": "2025-08-28T08:45:00.000Z", "severitynumber": 2, diff --git a/internal/events/schemas/examples/letter.FORWARDED.json b/internal/events/schemas/examples/letter.FORWARDED.json index 0f21ca064..bf12ed69f 100644 --- a/internal/events/schemas/examples/letter.FORWARDED.json +++ b/internal/events/schemas/examples/letter.FORWARDED.json @@ -15,7 +15,6 @@ }, "datacontenttype": "application/json", "dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.FORWARDED.1.0.0.schema.json", - "dataschemaversion": "1.0.0", "id": "23f1f09c-a555-4d9b-8405-0b33490bc920", "recordedtime": "2025-08-28T08:45:00.000Z", "severitynumber": 2, diff --git a/internal/events/schemas/examples/letter.RETURNED.json b/internal/events/schemas/examples/letter.RETURNED.json index ba818ef6c..e273029d1 100644 --- a/internal/events/schemas/examples/letter.RETURNED.json +++ b/internal/events/schemas/examples/letter.RETURNED.json @@ -15,7 +15,6 @@ }, "datacontenttype": "application/json", "dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.RETURNED.1.0.0.schema.json", - "dataschemaversion": "1.0.0", "id": "23f1f09c-a555-4d9b-8405-0b33490bc920", "recordedtime": "2025-08-28T08:45:00.000Z", "severitynumber": 2, diff --git a/internal/events/schemas/supplier-api.yaml b/internal/events/schemas/supplier-api.yaml index d9d660dae..a87277dc1 100644 --- a/internal/events/schemas/supplier-api.yaml +++ b/internal/events/schemas/supplier-api.yaml @@ -35,6 +35,8 @@ channels: $ref: '#/components/messages/letter-returned' letter-any: $ref: '#/components/messages/letter-any' + mi-submitted: + $ref: '#/components/messages/mi-submitted' components: messages: @@ -179,6 +181,17 @@ components: payload: $ref: './events/letter.any.schema.json' + mi-submitted: + name: mi-submitted + title: MI Submitted + summary: | + This indicates that MI data has been submitted to the NHS Notify Supplier API. + contentType: application/json + payload: + $ref: './events/mi.SUBMITTED.schema.json' + schemas: letter: $ref: './domain/letter.schema.json' + mi: + $ref: './domain/mi.schema.json' diff --git a/internal/events/src/cli/generate-json.ts b/internal/events/src/cli/generate-json.ts index 42d4edf78..a6d35d68f 100644 --- a/internal/events/src/cli/generate-json.ts +++ b/internal/events/src/cli/generate-json.ts @@ -5,9 +5,12 @@ import { $LetterEvent, letterEventMap, } from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src/events/letter-events"; +import { $MISubmittedEvent } from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src/events/mi-events"; +import { $MI } from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src/domain/mi"; for (const [key, schema] of Object.entries({ letter: $Letter, + mi: $MI, })) { const json = z.toJSONSchema(schema, { io: "input", @@ -39,6 +42,16 @@ const json = z.toJSONSchema($LetterEvent, { reused: "ref", }); fs.mkdirSync("schemas/events", { recursive: true }); -const file = `schemas/events/letter.any.schema.json`; -fs.writeFileSync(file, JSON.stringify(json, null, 2)); -console.info(`Wrote JSON schema for letter.any to ${file}`); +const letterJson = `schemas/events/letter.any.schema.json`; +fs.writeFileSync(letterJson, JSON.stringify(json, null, 2)); +console.info(`Wrote JSON schema for letter.any to ${letterJson}`); + +// MI Submitted Event +const miJson = z.toJSONSchema($MISubmittedEvent, { + io: "input", + target: "openapi-3.0", + reused: "ref", +}); +const miFile = `schemas/events/mi.SUBMITTED.schema.json`; +fs.writeFileSync(miFile, JSON.stringify(miJson, null, 2)); +console.info(`Wrote JSON schema for letter.any to ${miFile}`); diff --git a/internal/events/src/domain/__tests__/mi.test.ts b/internal/events/src/domain/__tests__/mi.test.ts new file mode 100644 index 000000000..111e3cf0d --- /dev/null +++ b/internal/events/src/domain/__tests__/mi.test.ts @@ -0,0 +1,165 @@ +import { + $MI, + MI, +} from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src/domain/mi"; + +describe("MI schema validation", () => { + const validMIEvent: MI = { + id: "mi-001", + lineItem: "LETTER_PRINT_A4", + timestamp: "2025-11-16T10:30:00.000Z", + quantity: 150, + specificationId: "spec-123", + groupId: "group-456", + stockRemaining: 1000, + supplierId: "supplier-789", + createdAt: "2025-11-16T10:30:00.000Z", + updatedAt: "2025-11-16T10:30:00.000Z", + }; + + describe("basic validation", () => { + it("should validate a valid MI event with all fields", () => { + const result = $MI.safeParse(validMIEvent); + expect(result.success).toBe(true); + expect(result.error).toBeUndefined(); + expect(result.data).toEqual(validMIEvent); + }); + + it("should validate an MI event without optional fields", () => { + const minimalMI = { + id: "mi-002", + lineItem: "LETTER_PRINT_A5", + timestamp: "2025-11-16T11:00:00.000Z", + quantity: 75, + supplierId: "supplier-101", + createdAt: "2025-11-16T11:00:00.000Z", + updatedAt: "2025-11-16T11:00:00.000Z", + }; + + const result = $MI.safeParse(minimalMI); + expect(result.success).toBe(true); + const data = result.data as MI; + expect(data.specificationId).toBeUndefined(); + expect(data.groupId).toBeUndefined(); + expect(data.stockRemaining).toBeUndefined(); + }); + }); + + describe("field validation", () => { + it("should reject MI event missing required field 'id'", () => { + const invalidMI = { ...validMIEvent }; + delete (invalidMI as any).id; + + const result = $MI.safeParse(invalidMI); + expect(result.success).toBe(false); + }); + + it("should reject MI event missing required field 'lineItem'", () => { + const invalidMI = { ...validMIEvent }; + delete (invalidMI as any).lineItem; + + const result = $MI.safeParse(invalidMI); + expect(result.success).toBe(false); + }); + + it("should reject MI event missing required field 'timestamp'", () => { + const invalidMI = { ...validMIEvent }; + delete (invalidMI as any).timestamp; + + const result = $MI.safeParse(invalidMI); + expect(result.success).toBe(false); + }); + + it("should reject MI event missing required field 'quantity'", () => { + const invalidMI = { ...validMIEvent }; + delete (invalidMI as any).quantity; + + const result = $MI.safeParse(invalidMI); + expect(result.success).toBe(false); + }); + + it("should reject MI event missing required field 'supplierId'", () => { + const invalidMI = { ...validMIEvent }; + delete (invalidMI as any).supplierId; + + const result = $MI.safeParse(invalidMI); + expect(result.success).toBe(false); + }); + + it("should reject MI event with invalid quantity type", () => { + const invalidMI = { + ...validMIEvent, + quantity: "not-a-number", + }; + + const result = $MI.safeParse(invalidMI); + expect(result.success).toBe(false); + }); + + it("should reject MI event with invalid stockRemaining type", () => { + const invalidMI = { + ...validMIEvent, + stockRemaining: "not-a-number", + }; + + const result = $MI.safeParse(invalidMI); + expect(result.success).toBe(false); + }); + }); + + describe("testData examples", () => { + it("should parse a letter print MI event", () => { + const letterPrintMI = { + id: "mi-letter-001", + lineItem: "LETTER_PRINT_A4_COLOR", + timestamp: "2025-11-16T14:00:00.000Z", + quantity: 250, + specificationId: "letter-spec-001", + groupId: "batch-001", + supplierId: "supplier-abc", + createdAt: "2025-11-16T14:00:00.000Z", + updatedAt: "2025-11-16T14:00:00.000Z", + }; + + const result = $MI.safeParse(letterPrintMI); + expect(result.success).toBe(true); + }); + + it("should parse an envelope usage MI event", () => { + const envelopeMI = { + id: "mi-envelope-001", + lineItem: "ENVELOPE_DL", + timestamp: "2025-11-16T15:00:00.000Z", + quantity: 300, + stockRemaining: 2500, + supplierId: "supplier-xyz", + createdAt: "2025-11-16T15:00:00.000Z", + updatedAt: "2025-11-16T15:00:00.000Z", + }; + + const result = $MI.safeParse(envelopeMI); + expect(result.success).toBe(true); + const data = result.data as MI; + expect(data.stockRemaining).toBe(2500); + }); + + it("should parse a postage MI event", () => { + const postageMI = { + id: "mi-postage-001", + lineItem: "POSTAGE_FIRST_CLASS", + timestamp: "2025-11-16T16:00:00.000Z", + quantity: 500, + groupId: "daily-batch-16-11-2025", + supplierId: "supplier-123", + createdAt: "2025-11-16T16:00:00.000Z", + updatedAt: "2025-11-16T16:00:00.000Z", + }; + + const result = $MI.safeParse(postageMI); + expect(result.success).toBe(true); + const data = result.data as MI; + expect(data.lineItem).toBe("POSTAGE_FIRST_CLASS"); + expect(data.groupId).toBe("daily-batch-16-11-2025"); + }); + }); +}); diff --git a/internal/events/src/domain/letter.ts b/internal/events/src/domain/letter.ts index 46590ce2b..e06aa3389 100644 --- a/internal/events/src/domain/letter.ts +++ b/internal/events/src/domain/letter.ts @@ -37,15 +37,18 @@ export const $Letter = DomainBase("Letter") title: "Domain ID", description: "The domain which requested this letter", }), + source: z.string().meta({ title: "Event source", description: "The source of the event which created this letter", }), + subject: z.string().meta({ title: "Event subject", description: "The subject of the event which created this letter, scoped to source", }), + event: z.string().meta({ title: "Event ID", description: "The ID of the event which created this letter", @@ -65,12 +68,14 @@ The identifier will be included as the origin domain in the subject of any corre }, ], }), + specificationId: z.string().meta({ title: "Specification ID", description: "Reference to the letter specification which was used to produce a letter pack for this request.", examples: ["1y3q9v1zzzz"], }), + groupId: z.string().meta({ title: "Group ID", description: @@ -80,7 +85,9 @@ The identifier will be included as the origin domain in the subject of any corre "00f3b388-bbe9-41c9-9e76-052d37ee8988_20a1ab22-6136-47ae-ac0f-989f382be8df", ], }), + status: $LetterStatus, + reasonCode: z .string() .optional() @@ -90,6 +97,7 @@ The identifier will be included as the origin domain in the subject of any corre "Optional reason code for the status change, if applicable.", examples: ["R01", "R08"], }), + reasonText: z .string() .optional() diff --git a/internal/events/src/domain/mi.ts b/internal/events/src/domain/mi.ts new file mode 100644 index 000000000..63954267e --- /dev/null +++ b/internal/events/src/domain/mi.ts @@ -0,0 +1,18 @@ +import { z } from "zod"; + +export const $MI = z + .object({ + id: z.string(), + lineItem: z.string(), + timestamp: z.string(), + quantity: z.number(), + specificationId: z.string().optional(), + groupId: z.string().optional(), + stockRemaining: z.number().optional(), + supplierId: z.string(), + createdAt: z.string(), + updatedAt: z.string(), + }) + .describe("MI"); + +export type MI = z.infer; diff --git a/internal/events/src/events/__tests__/event-envelope.test.ts b/internal/events/src/events/__tests__/event-envelope.test.ts new file mode 100644 index 000000000..b3cd8e4b0 --- /dev/null +++ b/internal/events/src/events/__tests__/event-envelope.test.ts @@ -0,0 +1,360 @@ +import { z } from "zod"; +import { EventEnvelope } from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src/events/event-envelope"; + +describe("EventEnvelope schema validation", () => { + const $Envelope = EventEnvelope("order.read", "order", z.any(), ["READ"]); + type Envelope = z.infer; + + const baseValidEnvelope: Envelope = { + dataschema: + "https://notify.nhs.uk/cloudevents/schemas/supplier-api/order.READ.1.0.0.schema.json", + specversion: "1.0", + id: "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111", + source: "/data-plane/supplier-api/ordering", + subject: "order/769acdd4", + type: "uk.nhs.notify.supplier-api.order.READ.v1", + time: "2025-10-01T10:15:30.000Z", + data: { + "notify-payload": { + "notify-data": { nhsNumber: "9434765919" }, + "notify-metadata": { + teamResponsible: "Team 1", + notifyDomain: "Ordering", + version: "1.3.0", + }, + }, + }, + traceparent: "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + recordedtime: "2025-10-01T10:15:30.250Z", + severitynumber: 2, + severitytext: "INFO", + }; + + describe("basic validation", () => { + it("should validate a valid envelope", () => { + const result = $Envelope.safeParse(baseValidEnvelope); + expect(result.error).toBeUndefined(); + expect(result.success).toBe(true); + }); + }); + + describe("superRefine: severity text and number validation", () => { + it("should accept TRACE with severitynumber 0", () => { + const envelope = { + ...baseValidEnvelope, + severitytext: "TRACE", + severitynumber: 0, + }; + + const result = $Envelope.safeParse(envelope); + expect(result.success).toBe(true); + }); + + it("should accept DEBUG with severitynumber 1", () => { + const envelope = { + ...baseValidEnvelope, + severitytext: "DEBUG", + severitynumber: 1, + }; + + const result = $Envelope.safeParse(envelope); + expect(result.success).toBe(true); + }); + + it("should accept INFO with severitynumber 2", () => { + const envelope = { + ...baseValidEnvelope, + severitytext: "INFO", + severitynumber: 2, + }; + + const result = $Envelope.safeParse(envelope); + expect(result.success).toBe(true); + }); + + it("should accept WARN with severitynumber 3", () => { + const envelope = { + ...baseValidEnvelope, + severitytext: "WARN", + severitynumber: 3, + }; + + const result = $Envelope.safeParse(envelope); + expect(result.success).toBe(true); + }); + + it("should accept ERROR with severitynumber 4", () => { + const envelope = { + ...baseValidEnvelope, + severitytext: "ERROR", + severitynumber: 4, + }; + + const result = $Envelope.safeParse(envelope); + expect(result.success).toBe(true); + }); + + it("should accept FATAL with severitynumber 5", () => { + const envelope = { + ...baseValidEnvelope, + severitytext: "FATAL", + severitynumber: 5, + }; + + const result = $Envelope.safeParse(envelope); + expect(result.success).toBe(true); + }); + + it("should reject TRACE with incorrect severitynumber", () => { + const envelope = { + ...baseValidEnvelope, + severitytext: "TRACE", + severitynumber: 1, + }; + + const result = $Envelope.safeParse(envelope); + expect(result.success).toBe(false); + }); + + it("should reject DEBUG with incorrect severitynumber", () => { + const envelope = { + ...baseValidEnvelope, + severitytext: "DEBUG", + severitynumber: 2, + }; + + const result = $Envelope.safeParse(envelope); + expect(result.success).toBe(false); + }); + + it("should reject INFO with incorrect severitynumber", () => { + const envelope = { + ...baseValidEnvelope, + severitytext: "INFO", + severitynumber: 1, + }; + + const result = $Envelope.safeParse(envelope); + expect(result.success).toBe(false); + }); + + it("should reject WARN with incorrect severitynumber", () => { + const envelope = { + ...baseValidEnvelope, + severitytext: "WARN", + severitynumber: 2, + }; + + const result = $Envelope.safeParse(envelope); + expect(result.success).toBe(false); + }); + + it("should reject ERROR with incorrect severitynumber", () => { + const envelope = { + ...baseValidEnvelope, + severitytext: "ERROR", + severitynumber: 3, + }; + + const result = $Envelope.safeParse(envelope); + expect(result.success).toBe(false); + }); + + it("should reject FATAL with incorrect severitynumber", () => { + const envelope = { + ...baseValidEnvelope, + severitytext: "FATAL", + severitynumber: 4, + }; + + const result = $Envelope.safeParse(envelope); + expect(result.success).toBe(false); + }); + + it("should reject severitynumber without severitytext", () => { + const envelope = { + ...baseValidEnvelope, + severitytext: undefined, + severitynumber: 2, + }; + + const result = $Envelope.safeParse(envelope); + expect(result.success).toBe(false); + }); + + it("should accept severitytext without severitynumber (optional)", () => { + const envelope = { + ...baseValidEnvelope, + severitytext: "INFO", + severitynumber: 2, + }; + + const result = $Envelope.safeParse(envelope); + expect(result.success).toBe(true); + }); + }); + + describe("optional fields validation", () => { + it("should accept envelope with all optional fields", () => { + const envelope = { + ...baseValidEnvelope, + datacontenttype: "application/json", + tracestate: "rojo=00f067aa0ba902b7", + partitionkey: "customer-920fca11", + sampledrate: 5, + sequence: "00000000000000000042", + severitytext: "DEBUG", + severitynumber: 1, + dataclassification: "restricted", + dataregulation: "GDPR", + datacategory: "sensitive", + }; + + const result = $Envelope.safeParse(envelope); + expect(result.error).toBeUndefined(); + expect(result.success).toBe(true); + }); + }); + + describe("edge cases", () => { + it("should reject invalid source pattern", () => { + const envelope = { + ...baseValidEnvelope, + source: "/invalid-plane/test", + }; + + const result = $Envelope.safeParse(envelope); + expect(result.success).toBe(false); + }); + }); + + describe("subject prefix validation", () => { + const $EnvelopeWithPrefix = EventEnvelope( + "letter.CREATED", + "letter", + z.any(), + ["CREATED"], + "letter-origin", + ); + + const baseLetterEnvelope = { + specversion: "1.0" as const, + id: "6f1c2a53-3d54-4a0a-9a0b-0e02b2c3d479", + type: "uk.nhs.notify.supplier-api.letter.CREATED.v1" as const, + dataschema: + "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.CREATED.1.0.0.schema.json", + source: "/data-plane/supplier-api/letters", + time: "2025-10-01T10:15:30.000Z", + data: { status: "CREATED" }, + traceparent: "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + recordedtime: "2025-10-01T10:15:30.250Z", + severitynumber: 2, + severitytext: "INFO" as const, + }; + + it("should accept subject with valid prefix when prefix is required", () => { + const envelope = { + ...baseLetterEnvelope, + subject: + "letter-origin/letter-rendering/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479", + }; + + const result = $EnvelopeWithPrefix.safeParse(envelope); + expect(result.error).toBeUndefined(); + expect(result.success).toBe(true); + }); + + it("should reject subject without prefix when prefix is required", () => { + const envelope = { + ...baseLetterEnvelope, + subject: "letter/f47ac10b-58cc-4372-a567-0e02b2c3d479", + }; + + const result = $EnvelopeWithPrefix.safeParse(envelope); + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.issues[0].path).toContain("subject"); + } + }); + + it("should reject subject with incomplete prefix", () => { + const envelope = { + ...baseLetterEnvelope, + subject: "letter-origin/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479", + }; + + const result = $EnvelopeWithPrefix.safeParse(envelope); + expect(result.success).toBe(false); + }); + + it("should accept subject without prefix when no prefix is specified", () => { + const $EnvelopeNoPrefix = EventEnvelope("order.READ", "order", z.any(), [ + "READ", + ]); + + const envelope = { + specversion: "1.0" as const, + id: "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111", + type: "uk.nhs.notify.supplier-api.order.READ.v1" as const, + dataschema: + "https://notify.nhs.uk/cloudevents/schemas/supplier-api/order.READ.1.0.0.schema.json", + source: "/data-plane/supplier-api/ordering", + subject: "order/769acdd4", + time: "2025-10-01T10:15:30.000Z", + data: { status: "READ" }, + traceparent: "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + recordedtime: "2025-10-01T10:15:30.250Z", + severitynumber: 2, + severitytext: "INFO" as const, + }; + + const result = $EnvelopeNoPrefix.safeParse(envelope); + expect(result.error).toBeUndefined(); + expect(result.success).toBe(true); + }); + + it("should accept various prefix formats", () => { + const $EnvelopeMultiSegmentPrefix = EventEnvelope( + "letter.CREATED", + "letter", + z.any(), + ["CREATED"], + "a/b", + ); + + const envelope = { + ...baseLetterEnvelope, + subject: "a/b/c/letter/test-id-123", + }; + + const result = $EnvelopeMultiSegmentPrefix.safeParse(envelope); + expect(result.error).toBeUndefined(); + expect(result.success).toBe(true); + }); + + it("should reject subject with prefix when no prefix is required", () => { + const $EnvelopeNoPrefix = EventEnvelope("order.read", "order", z.any(), [ + "READ", + ]); + + const envelope = { + specversion: "1.0" as const, + id: "6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111", + type: "uk.nhs.notify.supplier-api.order.read.v1" as const, + dataschema: + "https://notify.nhs.uk/cloudevents/schemas/supplier-api/order.read.1.0.0.schema.json", + source: "/data-plane/supplier-api/ordering", + subject: "prefix/letter-rendering/order/769acdd4", + time: "2025-10-01T10:15:30.000Z", + data: { status: "READ" }, + traceparent: "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + recordedtime: "2025-10-01T10:15:30.250Z", + severitynumber: 2, + severitytext: "INFO" as const, + }; + + const result = $EnvelopeNoPrefix.safeParse(envelope); + expect(result.success).toBe(false); + }); + }); +}); diff --git a/internal/events/src/events/__tests__/letter-status-change-events.test.ts b/internal/events/src/events/__tests__/letter-status-change-events.test.ts index 37a781e04..25541fc5a 100644 --- a/internal/events/src/events/__tests__/letter-status-change-events.test.ts +++ b/internal/events/src/events/__tests__/letter-status-change-events.test.ts @@ -14,7 +14,9 @@ describe("LetterStatus event validations", () => { (status) => { const json = readJson(`letter.${status}.json`); - const event = letterEventMap[`letter.${status}`].parse(json); + const { data: event, error } = + letterEventMap[`letter.${status}`].safeParse(json); + expect(error).toBeUndefined(); expect(event).toBeDefined(); expect(event).toEqual( expect.objectContaining({ @@ -25,7 +27,6 @@ describe("LetterStatus event validations", () => { time: "2025-08-28T08:45:00.000Z", datacontenttype: "application/json", dataschema: `https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.${status}.1.0.0.schema.json`, - dataschemaversion: "1.0.0", subject: "letter-origin/letter-rendering/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479", data: expect.objectContaining({ @@ -82,7 +83,7 @@ describe("LetterStatus event validations", () => { const json = readJson("letter.ACCEPTED-with-invalid-major-version.json"); expect(() => letterEventMap["letter.ACCEPTED"].parse(json)).toThrow( - "dataschemaversion", + "dataschema", ); }); }); diff --git a/internal/events/src/events/__tests__/mi-events.test.ts b/internal/events/src/events/__tests__/mi-events.test.ts new file mode 100644 index 000000000..565ab7dc2 --- /dev/null +++ b/internal/events/src/events/__tests__/mi-events.test.ts @@ -0,0 +1,86 @@ +import fs from "node:fs"; +import path from "node:path"; +import { $MISubmittedEvent } from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src/events/mi-events"; + +function readJson(filename: string): unknown { + const filePath = path.resolve(__dirname, "./testData/", filename); + + return JSON.parse(fs.readFileSync(filePath, "utf8")); +} + +describe("MI event validations", () => { + it("should parse mi.SUBMITTED event successfully", () => { + const json = readJson("mi.SUBMITTED.json"); + + const { data: event, error } = $MISubmittedEvent.safeParse(json); + expect(error).toBeUndefined(); + expect(event).toBeDefined(); + expect(event).toEqual( + expect.objectContaining({ + type: "uk.nhs.notify.supplier-api.mi.SUBMITTED.v1", + specversion: "1.0", + source: "/data-plane/supplier-api/prod/submit-mi", + id: "8f2c3b44-4e65-5b1b-a678-1f0bf3d4d222", + time: "2025-11-16T10:30:00.000Z", + datacontenttype: "application/json", + dataschema: + "https://notify.nhs.uk/cloudevents/schemas/supplier-api/mi.SUBMITTED.1.0.0.schema.json", + subject: "mi/mi-test-001", + data: expect.objectContaining({ + id: "mi-test-001", + lineItem: "LETTER_PRINT_A4", + timestamp: "2025-11-16T10:30:00.000Z", + quantity: 150, + specificationId: "spec-123", + groupId: "group-456", + stockRemaining: 1000, + supplierId: "supplier-789", + }), + }), + ); + }); + + it("should parse minimal mi.SUBMITTED event successfully", () => { + const json = readJson("mi.SUBMITTED-minimal.json"); + + const event = $MISubmittedEvent.parse(json); + expect(event).toBeDefined(); + expect(event.data).toEqual( + expect.objectContaining({ + id: "mi-envelope-001", + lineItem: "ENVELOPE_DL", + quantity: 300, + stockRemaining: 2500, + supplierId: "supplier-xyz", + }), + ); + expect(event.data.specificationId).toBeUndefined(); + expect(event.data.groupId).toBeUndefined(); + }); + + it("should parse MI data fields correctly", () => { + const json = readJson("mi.SUBMITTED.json"); + + const event = $MISubmittedEvent.parse(json); + expect(event).toBeDefined(); + expect(event.data.id).toBe("mi-test-001"); + expect(event.data.lineItem).toBe("LETTER_PRINT_A4"); + expect(event.data.quantity).toBe(150); + expect(event.data.stockRemaining).toBe(1000); + expect(event.data.supplierId).toBe("supplier-789"); + expect(event.data.specificationId).toBe("spec-123"); + expect(event.data.groupId).toBe("group-456"); + }); + + it("should throw error for mi.SUBMITTED event with missing subject", () => { + const json = readJson("mi.SUBMITTED-with-missing-subject.json"); + + expect(() => $MISubmittedEvent.parse(json)).toThrow("subject"); + }); + + it("should throw error for mi.SUBMITTED event with invalid major schema version", () => { + const json = readJson("mi.SUBMITTED-with-invalid-major-version.json"); + + expect(() => $MISubmittedEvent.parse(json)).toThrow("dataschema"); + }); +}); diff --git a/internal/events/src/events/__tests__/testData/letter.ACCEPTED-with-invalid-major-version.json b/internal/events/src/events/__tests__/testData/letter.ACCEPTED-with-invalid-major-version.json index 137734a2d..95e95651b 100644 --- a/internal/events/src/events/__tests__/testData/letter.ACCEPTED-with-invalid-major-version.json +++ b/internal/events/src/events/__tests__/testData/letter.ACCEPTED-with-invalid-major-version.json @@ -12,8 +12,7 @@ "status": "ACCEPTED" }, "datacontenttype": "application/json", - "dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json", - "dataschemaversion": "0.1.0", + "dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.0.1.0.schema.json", "id": "23f1f09c-a555-4d9b-8405-0b33490bc920", "recordedtime": "2025-08-28T08:45:00.000Z", "severitynumber": 2, diff --git a/internal/events/src/events/__tests__/testData/letter.ACCEPTED-with-missing-fields.json b/internal/events/src/events/__tests__/testData/letter.ACCEPTED-with-missing-fields.json index 618724682..bb5d8021c 100644 --- a/internal/events/src/events/__tests__/testData/letter.ACCEPTED-with-missing-fields.json +++ b/internal/events/src/events/__tests__/testData/letter.ACCEPTED-with-missing-fields.json @@ -12,7 +12,6 @@ }, "datacontenttype": "application/json", "dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json", - "dataschemaversion": "1.0.0", "id": "23f1f09c-a555-4d9b-8405-0b33490bc920", "recordedtime": "2025-08-28T08:45:00.000Z", "severitynumber": 2, diff --git a/internal/events/src/events/__tests__/testData/letter.ACCEPTED.json b/internal/events/src/events/__tests__/testData/letter.ACCEPTED.json index 7d22e4132..f9a1177ce 100644 --- a/internal/events/src/events/__tests__/testData/letter.ACCEPTED.json +++ b/internal/events/src/events/__tests__/testData/letter.ACCEPTED.json @@ -13,7 +13,6 @@ }, "datacontenttype": "application/json", "dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json", - "dataschemaversion": "1.0.0", "id": "23f1f09c-a555-4d9b-8405-0b33490bc920", "recordedtime": "2025-08-28T08:45:00.000Z", "severitynumber": 2, diff --git a/internal/events/src/events/__tests__/testData/letter.FORWARDED.json b/internal/events/src/events/__tests__/testData/letter.FORWARDED.json index 0f21ca064..bf12ed69f 100644 --- a/internal/events/src/events/__tests__/testData/letter.FORWARDED.json +++ b/internal/events/src/events/__tests__/testData/letter.FORWARDED.json @@ -15,7 +15,6 @@ }, "datacontenttype": "application/json", "dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.FORWARDED.1.0.0.schema.json", - "dataschemaversion": "1.0.0", "id": "23f1f09c-a555-4d9b-8405-0b33490bc920", "recordedtime": "2025-08-28T08:45:00.000Z", "severitynumber": 2, diff --git a/internal/events/src/events/__tests__/testData/letter.RETURNED.json b/internal/events/src/events/__tests__/testData/letter.RETURNED.json index ba818ef6c..e273029d1 100644 --- a/internal/events/src/events/__tests__/testData/letter.RETURNED.json +++ b/internal/events/src/events/__tests__/testData/letter.RETURNED.json @@ -15,7 +15,6 @@ }, "datacontenttype": "application/json", "dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.RETURNED.1.0.0.schema.json", - "dataschemaversion": "1.0.0", "id": "23f1f09c-a555-4d9b-8405-0b33490bc920", "recordedtime": "2025-08-28T08:45:00.000Z", "severitynumber": 2, diff --git a/internal/events/src/events/__tests__/testData/mi.SUBMITTED-minimal.json b/internal/events/src/events/__tests__/testData/mi.SUBMITTED-minimal.json new file mode 100644 index 000000000..fd1cd1be1 --- /dev/null +++ b/internal/events/src/events/__tests__/testData/mi.SUBMITTED-minimal.json @@ -0,0 +1,24 @@ +{ + "data": { + "createdAt": "2025-11-16T15:00:00.000Z", + "id": "mi-envelope-001", + "lineItem": "ENVELOPE_DL", + "quantity": 300, + "stockRemaining": 2500, + "supplierId": "supplier-xyz", + "timestamp": "2025-11-16T15:00:00.000Z", + "updatedAt": "2025-11-16T15:00:00.000Z" + }, + "datacontenttype": "application/json", + "dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/mi.SUBMITTED.1.0.0.schema.json", + "id": "9a3d4c55-5f76-6c2c-b789-2f1cf4e5e333", + "recordedtime": "2025-11-16T15:00:00.250Z", + "severitynumber": 2, + "severitytext": "INFO", + "source": "/data-plane/supplier-api/prod/submit-mi", + "specversion": "1.0", + "subject": "mi/mi-envelope-001", + "time": "2025-11-16T15:00:00.000Z", + "traceparent": "00-2cf9873938ef65ff0660fd433e02541e-d9cf8d9380415553-01", + "type": "uk.nhs.notify.supplier-api.mi.SUBMITTED.v1" +} diff --git a/internal/events/src/events/__tests__/testData/mi.SUBMITTED-with-invalid-major-version.json b/internal/events/src/events/__tests__/testData/mi.SUBMITTED-with-invalid-major-version.json new file mode 100644 index 000000000..5c51190a7 --- /dev/null +++ b/internal/events/src/events/__tests__/testData/mi.SUBMITTED-with-invalid-major-version.json @@ -0,0 +1,26 @@ +{ + "data": { + "createdAt": "2025-11-16T10:30:00.000Z", + "groupId": "group-456", + "id": "mi-test-001", + "lineItem": "LETTER_PRINT_A4", + "quantity": 150, + "specificationId": "spec-123", + "stockRemaining": 1000, + "supplierId": "supplier-789", + "timestamp": "2025-11-16T10:30:00.000Z", + "updatedAt": "2025-11-16T10:30:00.000Z" + }, + "datacontenttype": "application/json", + "dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/mi.SUBMITTED.2.0.0.schema.json", + "id": "8f2c3b44-4e65-5b1b-a678-1f0bf3d4d222", + "recordedtime": "2025-11-16T10:30:00.250Z", + "severitynumber": 2, + "severitytext": "INFO", + "source": "/data-plane/supplier-api/prod/submit-mi", + "specversion": "1.0", + "subject": "mi/mi-test-001", + "time": "2025-11-16T10:30:00.000Z", + "traceparent": "00-1bf8762827de54ee9559fc322d91430d-c8be7c8279304442-01", + "type": "uk.nhs.notify.supplier-api.mi.SUBMITTED.v1" +} diff --git a/internal/events/src/events/__tests__/testData/mi.SUBMITTED-with-missing-subject.json b/internal/events/src/events/__tests__/testData/mi.SUBMITTED-with-missing-subject.json new file mode 100644 index 000000000..e0be7a8e0 --- /dev/null +++ b/internal/events/src/events/__tests__/testData/mi.SUBMITTED-with-missing-subject.json @@ -0,0 +1,22 @@ +{ + "data": { + "createdAt": "2025-11-16T10:30:00.000Z", + "id": "mi-invalid-001", + "lineItem": "LETTER_PRINT_A4", + "quantity": 150, + "supplierId": "supplier-789", + "timestamp": "2025-11-16T10:30:00.000Z", + "updatedAt": "2025-11-16T10:30:00.000Z" + }, + "datacontenttype": "application/json", + "dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/mi.SUBMITTED.1.0.0.schema.json", + "id": "8f2c3b44-4e65-5b1b-a678-1f0bf3d4d222", + "recordedtime": "2025-11-16T10:30:00.250Z", + "severitynumber": 2, + "severitytext": "INFO", + "source": "/data-plane/supplier-api/prod/submit-mi", + "specversion": "1.0", + "time": "2025-11-16T10:30:00.000Z", + "traceparent": "00-1bf8762827de54ee9559fc322d91430d-c8be7c8279304442-01", + "type": "uk.nhs.notify.supplier-api.mi.SUBMITTED.v1" +} diff --git a/internal/events/src/events/__tests__/testData/mi.SUBMITTED.json b/internal/events/src/events/__tests__/testData/mi.SUBMITTED.json new file mode 100644 index 000000000..6e2c031da --- /dev/null +++ b/internal/events/src/events/__tests__/testData/mi.SUBMITTED.json @@ -0,0 +1,26 @@ +{ + "data": { + "createdAt": "2025-11-16T10:30:00.000Z", + "groupId": "group-456", + "id": "mi-test-001", + "lineItem": "LETTER_PRINT_A4", + "quantity": 150, + "specificationId": "spec-123", + "stockRemaining": 1000, + "supplierId": "supplier-789", + "timestamp": "2025-11-16T10:30:00.000Z", + "updatedAt": "2025-11-16T10:30:00.000Z" + }, + "datacontenttype": "application/json", + "dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/mi.SUBMITTED.1.0.0.schema.json", + "id": "8f2c3b44-4e65-5b1b-a678-1f0bf3d4d222", + "recordedtime": "2025-11-16T10:30:00.250Z", + "severitynumber": 2, + "severitytext": "INFO", + "source": "/data-plane/supplier-api/prod/submit-mi", + "specversion": "1.0", + "subject": "mi/mi-test-001", + "time": "2025-11-16T10:30:00.000Z", + "traceparent": "00-1bf8762827de54ee9559fc322d91430d-c8be7c8279304442-01", + "type": "uk.nhs.notify.supplier-api.mi.SUBMITTED.v1" +} diff --git a/internal/events/src/events/envelope-profile.ts b/internal/events/src/events/envelope-profile.ts deleted file mode 100644 index 06535b905..000000000 --- a/internal/events/src/events/envelope-profile.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { z } from "zod"; - -export const $EnvelopeProfile = z - .object({ - specversion: z.literal("1.0").meta({ - title: "CloudEvents spec version", - description: "CloudEvents specification version (fixed to 1.0).", - examples: ["1.0"], - }), - id: z - .uuid() - .min(1) - .meta({ - title: "Event ID", - description: "Unique identifier for this event instance (UUID).", - examples: ["6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111"], - }), - source: z - .string() - .min(12) - .regex(/^\/(data-plane|control-plane)(?:\/[a-z0-9-]+)*$/) - .meta({ - title: "Event Source", - description: - "Logical event producer path starting /data-plane or /control-plane followed by lowercase segments.", - examples: ["/data-plane/ordering", "/control-plane/audit"], - }), - subject: z - .string() - .min(5) - .regex(/^[^/]+(\/[^/]+)*$/) - .meta({ - title: "Event Subject", - description: - "Resource path (no leading slash) within the source made of segments separated by '/'.", - examples: [ - "origin/920fca11-596a-4eca-9c47-99f624614658/order/769acdd4-6a47-496f-999f-76a6fd2c3959/item/4f5e17c0-ec57-4cee-9a86-14580cf5af7d", - ], - }), - type: z - .string() - .min(1) - .regex( - // eslint-disable-next-line sonarjs/regex-complexity - /^(?!.*(?:^|\.|\/)(completed|finished|updated|changed|processed|handled|status|started|failed)(?:\.|\/|$))uk\.nhs\.notify\.[a-z0-9]+(\.[a-z0-9]+)*$/, - { - message: - "Event type must match uk.nhs.notify.* and must not contain any of: completed, finished, updated, changed, processed, handled, status, started, failed.", - }, - ) - .meta({ - title: "Event Type", - description: - "Event type (uk.nhs.notify.*) using reverse-DNS style; ambiguous verbs (completed, finished, updated, changed, processed, handled, status, started, failed) disallowed.", - examples: ["uk.nhs.notify.ordering.order.read"], - }), - time: z.iso.datetime().meta({ - title: "Event Time", - description: "Timestamp when the event occurred (RFC 3339).", - examples: ["2025-10-01T10:15:30.000Z"], - }), - datacontenttype: z.optional( - z.literal("application/json").meta({ - title: "Data Content Type", - description: - "Media type for the data field (fixed to application/json).", - examples: ["application/json"], - }), - ), - dataschema: z.optional( - z.string().meta({ - title: "Data Schema URI", - description: - "URI of a schema that describes the event payload (notify-payload).", - examples: [ - "https://nhsdigital.github.io/nhs-notify-standards/cloudevents/nhs-notify-example-event-data.schema.json", - ], - }), - ), - data: z.record(z.string(), z.unknown()).meta({ - title: "Event Data", - description: "Container object wrapping the structured Notify payload.", - examples: [ - { - "notify-payload": { - "notify-data": { nhsNumber: "9434765919" }, - "notify-metadata": { - teamResponsible: "Team 1", - notifyDomain: "Ordering", - version: "1.3.0", - }, - }, - }, - ], - }), - traceparent: z - .string() - .min(1) - .regex(/^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$/) - .meta({ - title: "Traceparent", - description: "W3C Trace Context traceparent header value.", - examples: ["00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"], - }), - tracestate: z.optional( - z.string().meta({ - title: "Tracestate", - description: "Optional W3C Trace Context tracestate header value.", - examples: ["rojo=00f067aa0ba902b7,congo=t61rcWkgMzE"], - }), - ), - partitionkey: z.optional( - z - .string() - .min(1) - .max(64) - .regex(/^[a-z0-9-]+$/) - .meta({ - title: "Partition Key", - description: - "Partition / ordering key (lowercase alphanumerics and hyphen, 1-64 chars).", - examples: ["customer-920fca11"], - }), - ), - recordedtime: z.iso.datetime().meta({ - title: "Recorded Time", - description: - "Timestamp when the event was recorded/persisted (should be >= time).", - examples: ["2025-10-01T10:15:30.250Z"], - }), - sampledrate: z.optional( - z - .number() - .int() - .min(1) - .meta({ - title: "Sampled Rate", - description: - "Sampling factor: number of similar occurrences this event represents.", - examples: [5], - }), - ), - sequence: z.optional( - z - .string() - .regex(/^\d{20}$/) - .meta({ - title: "Sequence", - description: - "Zero-padded 20 digit numeric sequence (lexicographically sortable).", - examples: ["00000000000000000042"], - }), - ), - severitytext: z.optional( - z.enum(["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"]).meta({ - title: "Severity Text", - description: "Log severity level name.", - examples: ["DEBUG"], - }), - ), - severitynumber: z - .number() - .int() - .min(0) - .max(5) - .meta({ - title: "Severity Number", - description: - "Numeric severity (TRACE=0, DEBUG=1, INFO=2, WARN=3, ERROR=4, FATAL=5).", - examples: [1], - }), - dataclassification: z.optional( - z.enum(["public", "internal", "confidential", "restricted"]).meta({ - title: "Data Classification", - description: "Data sensitivity classification.", - examples: ["restricted"], - }), - ), - dataregulation: z.optional( - z - .enum(["GDPR", "HIPAA", "PCI-DSS", "ISO-27001", "NIST-800-53", "CCPA"]) - .meta({ - title: "Data Regulation", - description: "Regulatory regime tag applied to this data.", - examples: ["ISO-27001"], - }), - ), - datacategory: z.optional( - z - .enum(["non-sensitive", "standard", "sensitive", "special-category"]) - .meta({ - title: "Data Category", - description: - "Data category classification (e.g. standard, special-category).", - examples: ["sensitive"], - }), - ), - }) - .superRefine((obj, ctx) => { - if ( - /^\/data-plane/.test(obj.source) && - !/^[a-z0-9-]+(\/[^/]+)+$/.test(obj.subject) - ) { - ctx.addIssue({ - code: "custom", - message: - "For /data-plane sources, subject must start with a {namespace}/{id} and may have further segments separated by '/'.", - path: ["subject"], - }); - } - if (obj.severitytext !== undefined) { - const mapping = { - TRACE: 0, - DEBUG: 1, - INFO: 2, - WARN: 3, - ERROR: 4, - FATAL: 5, - }; - if (obj.severitynumber !== mapping[obj.severitytext]) { - ctx.addIssue({ - code: "custom", - message: `severitynumber must be ${mapping[obj.severitytext]} when severitytext is ${obj.severitytext}`, - path: ["severitynumber"], - }); - } - } - if (obj.severitynumber && obj.severitytext === undefined) { - ctx.addIssue({ - code: "custom", - message: "severitytext is required when severitynumber is present", - path: ["severitytext"], - }); - } - }); - -export type EnvelopeProfile = z.infer; diff --git a/internal/events/src/events/event-envelope.ts b/internal/events/src/events/event-envelope.ts new file mode 100644 index 000000000..aeccd2d60 --- /dev/null +++ b/internal/events/src/events/event-envelope.ts @@ -0,0 +1,256 @@ +import { z } from "zod"; + +// eslint-disable-next-line import-x/prefer-default-export +export function EventEnvelope( + eventName: string, + resourceName: string, + data: TData, + statuses: readonly string[], + subjectPrefix?: string, +) { + const statusRegex = statuses.join("|"); + const subjectPrefixRegex = subjectPrefix + ? `${subjectPrefix}/[a-z0-9-]+/` + : ""; + const subjectExamplePrefix = subjectPrefix + ? `${subjectPrefix}/letter-rendering/` + : ""; + + // Pre-compute type strings to avoid repeated inference + const typeStrings = statuses.map( + (status) => + `uk.nhs.notify.supplier-api.${resourceName}.${status}.v1` as const, + ); + + const schemaExamples = statuses.map( + (status) => + `https://notify.nhs.uk/cloudevents/schemas/supplier-api/${resourceName}.${status}.1.0.0.schema.json`, + ); + + return z + .object({ + specversion: z.literal("1.0").meta({ + title: "CloudEvents spec version", + description: "CloudEvents specification version (fixed to 1.0).", + examples: ["1.0"], + }), + + id: z + .uuid() + .min(1) + .meta({ + title: "Event ID", + description: "Unique identifier for this event instance (UUID).", + examples: ["6f1c2a53-3d54-4a0a-9a0b-0e9ae2d4c111"], + }), + + type: z.enum(typeStrings as [string, ...string[]]).meta({ + title: `${eventName} event type`, + description: "Event type using reverse-DNS style", + examples: typeStrings, + }), + + dataschema: z + .string() + .regex( + // eslint-disable-next-line security/detect-non-literal-regexp + new RegExp( + String.raw`^https://notify\.nhs\.uk/cloudevents/schemas/supplier-api/${resourceName}\.(?${statusRegex})\.1\.\d+\.\d+\.schema.json$`, + ), + ) + .meta({ + title: "Data Schema URI", + description: `URI of a schema that describes the event data\n\nData schema version must match the major version indicated by the type`, + examples: schemaExamples, + }), + + source: z + .string() + .regex(/^\/data-plane\/supplier-api(?:\/.*)?$/) + .meta({ + title: "Event Source", + description: + "Logical event producer path within the supplier-api domain", + }), + + subject: z + .string() + + .regex( + new RegExp( + `^${subjectPrefixRegex}${resourceName}/[a-z0-9-]+$`, + ), + ) + .meta({ + title: "Event Subject", + description: + "Resource path (no leading slash) within the source made of segments separated by '/'.", + examples: [ + `${subjectExamplePrefix}${resourceName}/f47ac10b-58cc-4372-a567-0e02b2c3d479`, + ], + }), + + data, + + time: z.iso.datetime().meta({ + title: "Event Time", + description: "Timestamp when the event occurred (RFC 3339).", + examples: ["2025-10-01T10:15:30.000Z"], + }), + + datacontenttype: z.optional( + z.literal("application/json").meta({ + title: "Data Content Type", + description: + "Media type for the data field (fixed to application/json).", + examples: ["application/json"], + }), + ), + + traceparent: z + .string() + .min(1) + .regex(/^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$/) + .meta({ + title: "Traceparent", + description: "W3C Trace Context traceparent header value.", + examples: ["00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"], + }), + + tracestate: z.optional( + z.string().meta({ + title: "Tracestate", + description: "Optional W3C Trace Context tracestate header value.", + examples: ["rojo=00f067aa0ba902b7,congo=t61rcWkgMzE"], + }), + ), + + partitionkey: z.optional( + z + .string() + .min(1) + .max(64) + .regex(/^[a-z0-9-]+$/) + .meta({ + title: "Partition Key", + description: + "Partition / ordering key (lowercase alphanumerics and hyphen, 1-64 chars).", + examples: ["customer-920fca11"], + }), + ), + + recordedtime: z.iso.datetime().meta({ + title: "Recorded Time", + description: + "Timestamp when the event was recorded/persisted (should be >= time).", + examples: ["2025-10-01T10:15:30.250Z"], + }), + + sampledrate: z.optional( + z + .number() + .int() + .min(1) + .meta({ + title: "Sampled Rate", + description: + "Sampling factor: number of similar occurrences this event represents.", + examples: [5], + }), + ), + + sequence: z.optional( + z + .string() + .regex(/^\d{20}$/) + .meta({ + title: "Sequence", + description: + "Zero-padded 20 digit numeric sequence (lexicographically sortable).", + examples: ["00000000000000000042"], + }), + ), + + severitytext: z.optional( + z.enum(["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"]).meta({ + title: "Severity Text", + description: "Log severity level name.", + examples: ["DEBUG"], + }), + ), + + severitynumber: z + .number() + .int() + .min(0) + .max(5) + .meta({ + title: "Severity Number", + description: + "Numeric severity (TRACE=0, DEBUG=1, INFO=2, WARN=3, ERROR=4, FATAL=5).", + examples: [1], + }), + + dataclassification: z.optional( + z.enum(["public", "internal", "confidential", "restricted"]).meta({ + title: "Data Classification", + description: "Data sensitivity classification.", + examples: ["restricted"], + }), + ), + + dataregulation: z.optional( + z + .enum([ + "GDPR", + "HIPAA", + "PCI-DSS", + "ISO-27001", + "NIST-800-53", + "CCPA", + ]) + .meta({ + title: "Data Regulation", + description: "Regulatory regime tag applied to this data.", + examples: ["ISO-27001"], + }), + ), + + datacategory: z.optional( + z + .enum(["non-sensitive", "standard", "sensitive", "special-category"]) + .meta({ + title: "Data Category", + description: + "Data category classification (e.g. standard, special-category).", + examples: ["sensitive"], + }), + ), + }) + .superRefine((obj, ctx) => { + if (obj.severitytext !== undefined) { + const mapping = { + TRACE: 0, + DEBUG: 1, + INFO: 2, + WARN: 3, + ERROR: 4, + FATAL: 5, + }; + if (obj.severitynumber !== mapping[obj.severitytext]) { + ctx.addIssue({ + code: "custom", + message: `severitynumber must be ${mapping[obj.severitytext]} when severitytext is ${obj.severitytext}`, + path: ["severitynumber"], + }); + } + } + if (obj.severitynumber && obj.severitytext === undefined) { + ctx.addIssue({ + code: "custom", + message: "severitytext is required when severitynumber is present", + path: ["severitytext"], + }); + } + }); +} diff --git a/internal/events/src/events/letter-events.ts b/internal/events/src/events/letter-events.ts index b26ffac26..497950687 100644 --- a/internal/events/src/events/letter-events.ts +++ b/internal/events/src/events/letter-events.ts @@ -4,88 +4,21 @@ import { $LetterStatus, LetterStatus, } from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src/domain/letter"; -import { $EnvelopeProfile } from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src/events/envelope-profile"; +import { EventEnvelope } from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src/events/event-envelope"; /** * A generic schema for parsing any letter status change event */ -export const $LetterEvent = $EnvelopeProfile - .safeExtend({ - type: z - .enum( - $LetterStatus.options.map( - (status) => `uk.nhs.notify.supplier-api.letter.${status}.v1`, - ), - ) - .meta({ - title: `Letter event type`, - description: "Event type using reverse-DNS style", - examples: [ - "uk.nhs.notify.supplier-api.letter.PENDING.v1", - "uk.nhs.notify.supplier-api.letter.ACCEPTED.v1", - "uk.nhs.notify.supplier-api.letter.DISPATCHED.v1", - ], - }), - - dataschema: z - .string() - .regex( - new RegExp( - `^https:\\/\\/notify\\.nhs\\.uk\\/cloudevents\\/schemas\\/supplier-api\\/letter\\.(?${$LetterStatus.options.join("|")})\\.1\\.\\d+\\.\\d+\\.schema.json$`, - ), - ) - .meta({ - title: "Data Schema URI", - description: `URI of a schema that describes the event data - -Data schema version must match the major version indicated by the type`, - examples: [ - "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json", - ], - }), - - dataschemaversion: z - .string() - .regex(/^1\.\d+\.\d+$/) - .meta({ - title: "Data Schema Version", - description: - "Matches semantic versioning format with fixed major version (Not part of cloudevents spec?)", - }), - - source: z - .string() - .regex(/^\/data-plane\/supplier-api(?:\/.*)?$/) - .meta({ - title: "Event Source", - description: - "Logical event producer path within the supplier-api domain", - }), - - subject: z - .string() - .regex(/^letter-origin\/[a-z0-9-]+\/letter\/[^/]+(?:\/.*)?/) - .meta({ - title: "Event Subject", - description: - "Resource path (no leading slash) within the source made of segments separated by '/'.", - examples: [ - "letter-origin/letter-rendering/letter/f47ac10b-58cc-4372-a567-0e02b2c3d479", - ], - }), - - // This replaces the data definition from EnvelopeProfile rather than extending it - data: $Letter.meta({ - title: "Letter", - description: `The status of a letter in the supplier-api domain. - -This will include the current production status, any reason provided for the status, if applicable, and identifiers used for grouping in reports.`, - }), - }) - .meta({ - title: `letter.* Event`, - description: `Event schema for generic letter status change`, - }); +export const $LetterEvent = EventEnvelope( + "letter", + "letter", + $Letter, + $LetterStatus.options, + "letter-origin", +).meta({ + title: `letter.* Event`, + description: `Event schema for generic letter status change`, +}); export type LetterEvent = z.infer; /** @@ -93,48 +26,16 @@ export type LetterEvent = z.infer; * @param status */ const eventSchema = (status: LetterStatus) => - $LetterEvent - .safeExtend({ - type: z.literal(`uk.nhs.notify.supplier-api.letter.${status}.v1`).meta({ - title: `Letter ${status} event type`, - description: "Event type using reverse-DNS style", - examples: [`uk.nhs.notify.supplier-api.letter.${status}.v1`], - }), - - dataschema: z - .string() - .regex( - new RegExp( - `^https:\\/\\/notify\\.nhs\\.uk\\/cloudevents\\/schemas\\/supplier-api\\/letter.${status}.1\\.\\d+\\.\\d+\\.schema.json$`, - ), - ) - .meta({ - title: "Data Schema URI", - description: `URI of a schema that describes the event data - -Data schema version must match the major version indicated by the type`, - examples: [ - "https://notify.nhs.uk/cloudevents/schemas/supplier-api/letter.ACCEPTED.1.0.0.schema.json", - ], - }), - - data: $Letter - .extend({ - status: z.literal(status), - }) - .meta({ - title: "Letter", - description: `The status of a letter in the supplier-api domain. - -This will include the current production status, any reason provided for the status, if applicable, and identifiers used for grouping in reports. - -For this event the status is always \`${status}\``, - }), - }) - .meta({ - title: `letter.${status} Event`, - description: `Event schema for letter status change to ${status}`, - }); + EventEnvelope( + `letter.${status}`, + "letter", + $Letter, + [status], + "letter-origin", + ).meta({ + title: `letter.${status} Event`, + description: `Event schema for letter status change to ${status}`, + }); export const letterEventMap = Object.fromEntries( $LetterStatus.options.map((status) => [ diff --git a/internal/events/src/events/mi-events.ts b/internal/events/src/events/mi-events.ts new file mode 100644 index 000000000..b6d4e44d3 --- /dev/null +++ b/internal/events/src/events/mi-events.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; +import { EventEnvelope } from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src/events/event-envelope"; +import { $MI } from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src/domain/mi"; + +export const $MISubmittedEvent = EventEnvelope("mi.SUBMITTED", "mi", $MI, [ + "SUBMITTED", +]).meta({ + title: `mi.SUBMITTED Event`, + description: `Event schema for reporting that MI data has been submitted`, +}); +export type MISubmittedEvent = z.infer; diff --git a/internal/events/src/index.ts b/internal/events/src/index.ts index 3de8cf319..03324bf20 100644 --- a/internal/events/src/index.ts +++ b/internal/events/src/index.ts @@ -1,34 +1,5 @@ -/** - * NHS Notify Supplier API Event Schemas - * - * This entrypoint re-exports the Zod schemas and associated TypeScript types - * for letter status change events and supporting domain models. - * - * Public exports: - * - Envelope / CloudEvent profile base schema - * - Letter status domain enum & schema - * - Letter status change domain schema - * - Individual letter status change event schemas (statusChangeEvents map) - * - Generic letter status change event schema - */ - -// Envelope / CloudEvents base profile -export { - $EnvelopeProfile, - type EnvelopeProfile, -} from "./events/envelope-profile"; - -// Domain schemas -export { - $LetterStatus, - type LetterStatus, - $Letter, - type Letter, -} from "./domain/letter"; - -// Event schemas (collection & generic) -export { - letterEventMap, - $LetterEvent, - type LetterEvent, -} from "./events/letter-events"; +export * from "./domain/letter"; +export * from "./domain/mi"; +export * from "./events/event-envelope"; +export * from "./events/letter-events"; +export * from "./events/mi-events"; diff --git a/internal/events/tsconfig.build.json b/internal/events/tsconfig.build.json new file mode 100644 index 000000000..69e51c06d --- /dev/null +++ b/internal/events/tsconfig.build.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "noEmit": false, + "outDir": "dist", + "rootDir": "src" + }, + "exclude": [ + "node_modules", + "dist", + "src/cli", + "src/**/__tests__" + ], + "extends": "./tsconfig.json", + "include": [ + "src/**/*" + ] +} diff --git a/internal/events/tsconfig.json b/internal/events/tsconfig.json index 27dbdb0d2..3426c90e3 100644 --- a/internal/events/tsconfig.json +++ b/internal/events/tsconfig.json @@ -3,9 +3,9 @@ "declaration": true, "isolatedModules": true, "module": "commonjs", - "noEmit": false, "outDir": "dist", - "resolveJsonModule": true + "resolveJsonModule": true, + "rootDir": "src" }, "exclude": [ "node_modules", diff --git a/lambdas/api-handler/jest.config.ts b/lambdas/api-handler/jest.config.ts index d30f4cd1c..f88e72778 100644 --- a/lambdas/api-handler/jest.config.ts +++ b/lambdas/api-handler/jest.config.ts @@ -1,7 +1,7 @@ -import type { Config } from 'jest'; +import type { Config } from "jest"; export const baseJestConfig: Config = { - preset: 'ts-jest', + preset: "ts-jest", // Automatically clear mock calls, instances, contexts and results before every test clearMocks: true, @@ -10,10 +10,10 @@ export const baseJestConfig: Config = { collectCoverage: true, // The directory where Jest should output its coverage files - coverageDirectory: './.reports/unit/coverage', + coverageDirectory: "./.reports/unit/coverage", // Indicates which provider should be used to instrument code for coverage - coverageProvider: 'babel', + coverageProvider: "babel", coverageThreshold: { global: { @@ -24,36 +24,36 @@ export const baseJestConfig: Config = { }, }, - coveragePathIgnorePatterns: ['/__tests__/'], - transform: { '^.+\\.ts$': 'ts-jest' }, - testPathIgnorePatterns: ['.build'], - testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], + coveragePathIgnorePatterns: ["/__tests__/"], + transform: { "^.+\\.ts$": "ts-jest" }, + testPathIgnorePatterns: [".build"], + testMatch: ["**/?(*.)+(spec|test).[jt]s?(x)"], // Use this configuration option to add custom reporters to Jest reporters: [ - 'default', + "default", [ - 'jest-html-reporter', + "jest-html-reporter", { - pageTitle: 'Test Report', - outputPath: './.reports/unit/test-report.html', + pageTitle: "Test Report", + outputPath: "./.reports/unit/test-report.html", includeFailureMsg: true, }, ], ], // The test environment that will be used for testing - testEnvironment: 'jsdom', + testEnvironment: "jsdom", }; const utilsJestConfig = { ...baseJestConfig, - testEnvironment: 'node', + testEnvironment: "node", coveragePathIgnorePatterns: [ ...(baseJestConfig.coveragePathIgnorePatterns ?? []), - 'zod-validators.ts', + "zod-validators.ts", ], }; diff --git a/package-lock.json b/package-lock.json index 516944cd9..69f093794 100644 --- a/package-lock.json +++ b/package-lock.json @@ -105,7 +105,7 @@ }, "internal/events": { "name": "@nhsdigital/nhs-notify-event-schemas-supplier-api", - "version": "1.0.0", + "version": "1.0.1", "license": "MIT", "dependencies": { "@asyncapi/bundler": "^0.6.4",