{
  "info": {
    "name": "EDK-Enterprise-Deployment",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "description": "Post-provisioning customer walkthrough for an EDK enterprise deployment hosted under one base domain: platform.<baseDomain> for the platform/operator plane and <tenantSlug>.<baseDomain> for tenant protocol routes plus protected administrative API paths. Install the license and complete platform setup in the Admin Console UI before running this collection; those setup steps are intentionally not modeled as Postman API calls. The collection targets gateway host/path routes only and never calls workload containers or runtime probes directly."
  },
  "variable": [
    {
      "key": "statusListSigningKeyAlias",
      "value": "issuer-signing-{{tenantSlug}}",
      "type": "string",
      "description": "Runtime status-list signing key alias. The collection refreshes this to issuer-signing-<tenantSlug> for the active tenant unless statusListSigningKeyAliasOverride is set."
    },
    {
      "key": "statusListSigningKeyAliasOverride",
      "value": "",
      "type": "string",
      "description": "Optional explicit status-list signing key alias. Leave empty to use issuer-signing-<tenantSlug> for the active tenant."
    },
    {
      "key": "operatorCodeVerifier",
      "value": "edk-e2e-operator-pkce-verifier-0123456789abcdefghijklmnopqrstuv",
      "type": "string",
      "description": "PKCE verifier for the operator authorization-code flow. Override when you need a different verifier."
    },
    {
      "key": "kmsProviderId",
      "value": "{{tenantSlug}}",
      "type": "string",
      "description": "Tenant/runtime KMS provider used by the walkthrough. It is derived from tenantSlug by the collection pre-request script."
    },
    {
      "key": "verifierId",
      "value": "",
      "type": "string",
      "description": "Resolved from the platform OID4VP verifier instance list after tenant onboarding."
    },
    {
      "key": "idpClientSecretRef",
      "value": "${secret:@env:IDP_CLIENT_SECRET}",
      "type": "string",
      "description": "Secret reference used when the optional federation IdP request is run."
    },
    {
      "key": "issuerId",
      "value": ""
    }
  ],
  "item": [
    {
      "name": "00 Before You Start",
      "description": "Use the Admin Console UI to install the license and complete platform setup before running this collection. This Postman collection starts after the platform is provisioned; it covers operator sign-in, tenant onboarding, runtime discovery, issuer/verifier configuration, credential issuance, status lists, DCQL binding, and verification.",
      "item": []
    },
    {
      "name": "02 Operator Sign-in",
      "description": "Signs in the platform operator through the hosted authorization server with authorization code flow and PKCE. The resulting operator token is used for platform administration calls.",
      "item": [
        {
          "name": "01 Start authorization request",
          "protocolProfileBehavior": {
            "followRedirects": false
          },
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/authorize?response_type=code&client_id=platform-operator-cli&redirect_uri={{operatorRedirectUri}}&scope=openid&state=qa-operator-state-0001&prompt=login&code_challenge={{operatorCodeChallenge}}&code_challenge_method=S256",
            "description": "Starts the authorization-code flow for the operator CLI client. The request uses standard OIDC prompt=login so an existing Postman or browser session cannot bypass the login form. The AS answers with a 302 to its hosted login page carrying the pending session id. The S256 code challenge is computed in the pre-request script from the fixed operatorCodeVerifier."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "['operatorLoginUrl', 'operatorAuthSessionId', 'operatorReturnUrl', 'operatorTabId', 'operatorSessionCode', 'operatorAuthCode', 'operatorCodeChallenge'].forEach((key) => pm.collectionVariables.unset(key));",
                  "const verifier = pm.variables.replaceIn('{{operatorCodeVerifier}}');",
                  "const challenge = CryptoJS.SHA256(verifier).toString(CryptoJS.enc.Base64)",
                  "  .replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "pm.collectionVariables.set('operatorCodeChallenge', challenge);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('authorize redirects to the hosted login page', () => pm.response.to.have.status(302));",
                  "const location = pm.response.headers.get('Location');",
                  "pm.expect(location, 'Location header').to.be.a('string').and.to.include('/login?');",
                  "pm.collectionVariables.set('operatorLoginUrl', location);",
                  "const sessionId = /[?&]session_id=([^&]+)/.exec(location);",
                  "pm.expect(sessionId, 'session_id in login URL').to.not.eql(null);",
                  "pm.collectionVariables.set('operatorAuthSessionId', decodeURIComponent(sessionId[1]));",
                  "const returnUrl = /[?&]return_url=([^&]+)/.exec(location);",
                  "pm.expect(returnUrl, 'return_url in login URL').to.not.eql(null);",
                  "pm.collectionVariables.set('operatorReturnUrl', decodeURIComponent(returnUrl[1]));"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Open login page",
          "request": {
            "method": "GET",
            "url": "{{operatorLoginUrl}}",
            "description": "Loads the hosted login page. The page sets the oidc_login_csrf cookie and embeds the matching tab_id and session_code hidden inputs that the credential submit must echo back."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('login page rendered', () => pm.response.to.have.status(200));",
                  "const html = pm.response.text();",
                  "const tabId = /name=\"tab_id\" value=\"([^\"]+)\"/.exec(html);",
                  "const sessionCode = /name=\"session_code\" value=\"([^\"]+)\"/.exec(html);",
                  "pm.expect(tabId, 'tab_id hidden input').to.not.eql(null);",
                  "pm.expect(sessionCode, 'session_code hidden input').to.not.eql(null);",
                  "pm.collectionVariables.set('operatorTabId', tabId[1]);",
                  "pm.collectionVariables.set('operatorSessionCode', sessionCode[1]);"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Submit operator credentials",
          "protocolProfileBehavior": {
            "followRedirects": false
          },
          "request": {
            "method": "POST",
            "url": "{{platformUrl}}/login",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "username",
                  "value": "{{operatorEmail}}"
                },
                {
                  "key": "password",
                  "value": "{{operatorPassword}}"
                },
                {
                  "key": "session_id",
                  "value": "{{operatorAuthSessionId}}"
                },
                {
                  "key": "tab_id",
                  "value": "{{operatorTabId}}"
                },
                {
                  "key": "session_code",
                  "value": "{{operatorSessionCode}}"
                },
                {
                  "key": "return_url",
                  "value": "{{operatorReturnUrl}}"
                }
              ]
            },
            "description": "Posts the operator credentials to the login form together with the CSRF tuple from the rendered page. A successful login answers 302 to the authorize callback and sets the oidc_login_sid session cookie."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('credentials accepted', () => pm.response.to.have.status(302));",
                  "const location = pm.response.headers.get('Location');",
                  "pm.expect(location, 'Location header').to.be.a('string');",
                  "pm.expect(location).to.include('/authorize/callback');",
                  "pm.expect(location).to.not.include('error=invalid_credentials');",
                  "const sessionId = pm.collectionVariables.get('operatorAuthSessionId');",
                  "const expectedPath = '/authorize/callback?session_id=' + encodeURIComponent(sessionId);",
                  "const expectedUrl = pm.variables.get('platformUrl').replace(/\\/$/, '') + expectedPath;",
                  "const normalizedLocation = /^https?:\\/\\//.test(location) ? location : pm.variables.get('platformUrl').replace(/\\/$/, '') + location;",
                  "pm.expect(normalizedLocation, 'authorization callback URL').to.eql(expectedUrl);"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Resume authorization callback",
          "protocolProfileBehavior": {
            "followRedirects": false
          },
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/authorize/callback?session_id={{operatorAuthSessionId}}",
            "description": "Resumes the pending authorization with the fresh login session cookie. The callback URL is deterministic: /authorize/callback?session_id=<session id> from step 01; no customer-provided callback variable is required. The AS issues the authorization code and answers 302 to the registered redirect URI with code and state."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const sessionId = pm.collectionVariables.get('operatorAuthSessionId');",
                  "if (!sessionId) {",
                  "  throw new Error('Run \"02 Operator Sign-in / 01 Start authorization request\" first; the callback URL is derived from that authorization session.');",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('authorization code issued', () => pm.response.to.have.status(302));",
                  "const location = pm.response.headers.get('Location');",
                  "pm.expect(location, 'Location header').to.be.a('string').and.to.include('code=');",
                  "pm.expect(location).to.include('state=qa-operator-state-0001');",
                  "const code = /[?&#]code=([^&]+)/.exec(location);",
                  "pm.expect(code, 'authorization code in redirect').to.not.eql(null);",
                  "pm.collectionVariables.set('operatorAuthCode', decodeURIComponent(code[1]));"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Exchange code for operator token",
          "request": {
            "method": "POST",
            "url": "{{platformUrl}}/token",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "authorization_code"
                },
                {
                  "key": "code",
                  "value": "{{operatorAuthCode}}"
                },
                {
                  "key": "redirect_uri",
                  "value": "{{operatorRedirectUri}}"
                },
                {
                  "key": "client_id",
                  "value": "platform-operator-cli"
                },
                {
                  "key": "code_verifier",
                  "value": "{{operatorCodeVerifier}}"
                }
              ]
            },
            "description": "Exchanges the authorization code for tokens, proving possession of the PKCE verifier. The access token carries the operator's roles and is stored as operatorToken for the platform-admin requests in folder 03."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('token issued', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.access_token, 'access_token').to.be.a('string');",
                  "const seg = j.access_token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');",
                  "const padded = seg + '='.repeat((4 - (seg.length % 4)) % 4);",
                  "const payload = JSON.parse(CryptoJS.enc.Base64.parse(padded).toString(CryptoJS.enc.Utf8));",
                  "const roles = payload.roles || (payload.realm_access && payload.realm_access.roles) || [];",
                  "pm.test('operator token carries platform-admin role', () => pm.expect(roles).to.include('platform-admin'));",
                  "pm.collectionVariables.set('operatorToken', j.access_token);"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "03 Tenant Onboarding",
      "description": "Creates the tenant. Tenant setup provisions the default authorization server, KMS provider/material, tenant DID, and the gateway protocol route bindings. Issuer and verifier are included by default but can be skipped and managed manually. The route bindings target the tenant gateway host, not direct container URLs.",
      "item": [
        {
          "name": "01 Register tenant",
          "request": {
            "method": "POST",
            "url": "{{platformUrl}}/api/platform/admin/v1/tenants",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"tenant\": {\n    \"tenantType\": \"organization\",\n    \"name\": \"{{tenantName}}\",\n    \"description\": \"{{tenantName}} issuing and verification tenant\",\n    \"slug\": \"{{tenantSlug}}\",\n    \"initialPlatformSubdomain\": true\n  },\n  \"contacts\": {\n    \"technical\": {\n      \"email\": \"admin@{{tenantSlug}}.example\",\n      \"displayName\": \"{{tenantName}} Technical Contact\"\n    },\n    \"administrativeSameAsTechnical\": true,\n    \"ownerAdmin\": {\n      \"source\": \"technical\"\n    }\n  },\n  \"login\": {\n    \"enabled\": true,\n    \"deliveryMode\": \"none\",\n    \"defaultAuthorizationServerRequired\": true\n  },\n  \"provisioning\": {\n    \"issuer\": true,\n    \"verifier\": true,\n    \"keysAndDids\": true,\n    \"sampleData\": true\n  }\n}"
            },
            "description": "Registers an organization tenant with natural-person contacts, owner/admin login, mandatory default authorization server, and default issuer/verifier/key/DID/sample-data provisioning. Owner credential delivery is disabled here; production deployments normally use email delivery."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant registered', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "const tenant = j.tenant || j;",
                  "if (tenant && tenant.id) pm.collectionVariables.set('tenantId', tenant.id);",
                  "const correlationId = j.correlationId || j.registration?.correlationId || j.registrationLogId || tenant?.correlationId;",
                  "pm.expect(correlationId, 'tenant onboarding correlation id').to.be.a('string').and.not.empty;",
                  "pm.collectionVariables.set('tenantRegistrationCorrelationId', correlationId);",
                  "pm.test('registration returned tenant gateway protocol URLs', () => {",
                  "  const serviceUrls = j.serviceUrls || {};",
                  "  pm.expect(j.tenantUrl || j.primaryDomainUrl, 'tenant URL').to.be.a('string').and.not.empty;",
                  "  pm.expect(serviceUrls.authorizationServerUrl || j.issuerUrl || j.tenant?.issuerUrl, 'AS issuer URL').to.be.a('string').and.not.empty;",
                  "  pm.expect(serviceUrls.oid4vciIssuerUrl || j.oid4vciIssuerUrl || j.tenant?.oid4vciIssuerUrl, 'OID4VCI issuer URL').to.be.a('string').and.not.empty;",
                  "  pm.expect(serviceUrls.oid4vpVerifierUrl || j.oid4vpVerifierUrl || j.tenant?.oid4vpVerifierUrl, 'OID4VP verifier URL').to.be.a('string').and.not.empty;",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Get tenant onboarding status",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/admin/v1/tenant-onboarding/{{tenantRegistrationCorrelationId}}",
            "description": "Reads the platform tenant-onboarding status row produced by registration and verifies the platform completed every default activation step before customer-facing setup continues.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const correlationId = pm.collectionVariables.get('tenantRegistrationCorrelationId');",
                  "if (!correlationId) {",
                  "  throw new Error('Run \"03 Tenant Onboarding / 01 Register tenant\" first; the onboarding status URL is derived from the returned correlationId.');",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant onboarding status returned', () => pm.response.to.have.status(200));",
                  "const status = pm.response.json();",
                  "pm.expect(status.correlationId, 'correlation id').to.eql(pm.collectionVariables.get('tenantRegistrationCorrelationId'));",
                  "pm.expect(status.tenantId, 'tenant id').to.eql(pm.collectionVariables.get('tenantId'));",
                  "pm.expect(status.status, 'onboarding status').to.eql('COMPLETED');",
                  "pm.expect(status.completedAt, 'completedAt').to.be.a('string').and.not.empty;",
                  "pm.expect(status.lastError, 'last error').to.satisfy((value) => value === null || value === undefined);",
                  "const steps = Array.isArray(status.steps) ? status.steps : [];",
                  "pm.expect(steps.length, 'onboarding step timeline').to.be.greaterThan(0);",
                  "const stepId = (record) => typeof record.step === 'string' ? record.step : (record.step && record.step.id) || record.stepId || record.id;",
                  "const failedSteps = steps.filter((record) => record.error);",
                  "pm.expect(failedSteps.map((record) => stepId(record) + '=' + record.error), 'failed onboarding steps').to.eql([]);",
                  "const completedSteps = steps.filter((record) => record.completedAt).map(stepId);",
                  "[",
                  "  'routing-inserted',",
                  "  'isolation-provisioned',",
                  "  'as-provisioned',",
                  "  'as-endpoint-bound',",
                  "  'issuer-provisioned',",
                  "  'issuer-endpoint-bound',",
                  "  'verifier-provisioned',",
                  "  'verifier-endpoint-bound',",
                  "  'did-provisioned',",
                  "  'default-settings-applied',",
                  "  'owner-provisioned'",
                  "].forEach((step) => pm.expect(completedSteps, step).to.include(step));"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Get tenant",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/admin/v1/tenants/{{tenantId}}",
            "description": "Reads the tenant record back by its identifier.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant returned', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "04 List tenants",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/admin/v1/tenants",
            "description": "Lists tenants visible to the platform operator. This read-only check catches tenant registry, auth, and pagination regressions that a single get-by-id does not cover.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenants listed', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "const text = JSON.stringify(j);",
                  "pm.expect(text, 'registered tenant appears in list').to.include(pm.collectionVariables.get('tenantId'));"
                ]
              }
            }
          ]
        },
        {
          "name": "05 List tenant gateway endpoint bindings",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/admin/v1/tenants/{{tenantId}}/public-endpoints",
            "description": "Lists the tenant gateway endpoint bindings created by tenant registration. These are route metadata records for the gateway contract, not health checks or direct workload URLs.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('endpoints listed', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "const endpoints = j.data || j.items || j.endpoints || (Array.isArray(j) ? j : []);",
                  "const serviceTypes = endpoints.map((endpoint) => endpoint.serviceType);",
                  "pm.test('registration created gateway bindings for AS, issuer, and verifier protocol routes', () => {",
                  "  ['OAUTH2_AUTHORIZATION_SERVER', 'OID4VCI_ISSUER', 'OID4VP_VERIFIER'].forEach((serviceType) => pm.expect(serviceTypes, serviceType).to.include(serviceType));",
                  "});",
                  "const trimBase = (value) => String(value || '').trim().replace(/\\/$/, '');",
                  "const platformUrl = trimBase(pm.variables.get('platformUrl'));",
                  "const platformParts = platformUrl.match(/^([a-z][a-z0-9+.-]*):\\/\\/([^/?#]+)/i);",
                  "const platformScheme = platformParts ? platformParts[1].toLowerCase() : 'https';",
                  "const platformPort = platformParts && platformParts[2].match(/:(\\d+)$/) ? ':' + platformParts[2].match(/:(\\d+)$/)[1] : '';",
                  "const endpointFor = (serviceType) => endpoints.filter((endpoint) => endpoint.enabled !== false && endpoint.serviceType === serviceType).find((endpoint) => endpoint.primaryEndpoint) || endpoints.find((endpoint) => endpoint.enabled !== false && endpoint.serviceType === serviceType);",
                  "const originFor = (endpoint, fallback) => {",
                  "  const host = String(endpoint && endpoint.host || '').trim();",
                  "  if (!host) return trimBase(fallback);",
                  "  if (/^https?:\\/\\//i.test(host)) return trimBase(host);",
                  "  return platformScheme + '://' + host + (/:(\\d+)$/.test(host) ? '' : platformPort);",
                  "};",
                  "const expectedGateway = trimBase(pm.variables.get('tenantGatewayUrl'));",
                  "const expectedHost = expectedGateway.match(/^https?:\\/\\/([^/?#]+)/i);",
                  "let expectedHostname = expectedHost ? expectedHost[1].replace(/:\\d+$/, '') : '';",
                  "const origins = [",
                  "  originFor(endpointFor('OID4VCI_ISSUER'), expectedGateway),",
                  "  originFor(endpointFor('OID4VP_VERIFIER'), expectedGateway),",
                  "  originFor(endpointFor('OAUTH2_AUTHORIZATION_SERVER'), expectedGateway),",
                  "];",
                  "if (!expectedHostname) {",
                  "  const firstHost = origins[0].match(/^https?:\\/\\/([^/?#]+)/i);",
                  "  expectedHostname = firstHost ? firstHost[1].replace(/:\\d+$/, '') : '';",
                  "}",
                  "pm.test('tenant public endpoint bindings resolve to the tenant gateway host', () => {",
                  "  origins.forEach((origin) => {",
                  "    pm.expect(origin, 'endpoint origin').to.match(/^https?:\\/\\//);",
                  "    const host = origin.match(/^https?:\\/\\/([^/?#]+)/i);",
                  "    pm.expect(host && host[1].replace(/:\\d+$/, ''), origin).to.eql(expectedHostname);",
                  "  });",
                  "});",
                  "if (expectedHostname) pm.collectionVariables.set('tenantHost', expectedHostname);"
                ]
              }
            }
          ]
        },
        {
          "name": "06 Resolve tenant runtime service discovery",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/bootstrap/v1/runtime-config/admin-console?tenantId={{tenantId}}&tenantSlug={{tenantSlug}}",
            "description": "Fetches the same platform runtime service discovery consumed by the admin console. The collection stores the discovered tenant service base URLs and named endpoint paths, then uses those variables for tenant KMS, DID, issuer, credential-design, status-list, DCQL, and verifier backend calls.",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant runtime config returned', () => pm.response.to.have.status(200));",
                  "const cfg = pm.response.json();",
                  "const services = (cfg.data && cfg.data.services) || {};",
                  "const trimBase = (value) => String(value || '').trim().replace(/\\/$/, '');",
                  "const service = (key) => services[key] || {};",
                  "const endpointPath = (svc, names, fallback) => {",
                  "  for (const name of names) {",
                  "    const path = svc.endpoints && svc.endpoints[name] && svc.endpoints[name].path;",
                  "    if (path) return String(path).trim();",
                  "  }",
                  "  return fallback;",
                  "};",
                  "const joinUrl = (base, path) => {",
                  "  const cleanBase = trimBase(base);",
                  "  const cleanPath = String(path || '').trim();",
                  "  if (/^https?:\\/\\//i.test(cleanPath)) return trimBase(cleanPath);",
                  "  return cleanBase.replace(/\\/$/, '') + '/' + cleanPath.replace(/^\\/+/, '');",
                  "};",
                  "const setPublic = (key, value) => {",
                  "  const normalized = trimBase(value);",
                  "  if (normalized) {",
                  "    pm.collectionVariables.set(key, normalized);",
                  "    pm.environment.set(key, normalized);",
                  "  }",
                  "};",
                  "const tenantKms = service('tenantKms');",
                  "const tenantDid = service('tenantDid');",
                  "const issuer = service('issuer');",
                  "const verifier = service('verifier');",
                  "pm.test('runtime config exposes tenant workload services', () => {",
                  "  ['tenantKms', 'tenantDid', 'issuer', 'verifier'].forEach((key) => {",
                  "    pm.expect(services[key] && services[key].baseUrl, key + ' baseUrl').to.be.a('string').and.match(/^https?:\\/\\//);",
                  "  });",
                  "  pm.expect(endpointPath(tenantKms, ['api'], ''), 'tenantKms api endpoint').to.eql('/api/kms/v1');",
                  "  pm.expect(endpointPath(tenantDid, ['api'], ''), 'tenantDid api endpoint').to.eql('/api/did/v1');",
                  "  pm.expect(endpointPath(issuer, ['credentialDesigns'], ''), 'credential-design endpoint').to.eql('/api/credential-design/v1');",
                  "  pm.expect(endpointPath(issuer, ['statusLists'], ''), 'status-list endpoint').to.eql('/api/statuslist/v1');",
                  "  pm.expect(endpointPath(verifier, ['dcqlApi'], ''), 'dcql endpoint').to.eql('/api/dcql/v1');",
                  "});",
                  "const tenantOrigin = trimBase(issuer.baseUrl || tenantKms.baseUrl || tenantDid.baseUrl || verifier.baseUrl || pm.variables.get('tenantGatewayUrl'));",
                  "setPublic('tenantGatewayUrl', tenantOrigin);",
                  "setPublic('tenantIssuerOrigin', issuer.baseUrl || tenantOrigin);",
                  "setPublic('tenantVerifierOrigin', verifier.baseUrl || tenantOrigin);",
                  "setPublic('tenantAuthorizationServerOrigin', tenantOrigin);",
                  "setPublic('tenantKmsApiBaseUrl', joinUrl(tenantKms.baseUrl || tenantOrigin, endpointPath(tenantKms, ['api'], '/api/kms/v1')));",
                  "setPublic('tenantDidApiBaseUrl', joinUrl(tenantDid.baseUrl || tenantOrigin, endpointPath(tenantDid, ['api'], '/api/did/v1')));",
                  "setPublic('tenantCredentialDesignApiBaseUrl', joinUrl(issuer.baseUrl || tenantOrigin, endpointPath(issuer, ['credentialDesigns'], '/api/credential-design/v1')));",
                  "setPublic('tenantStatusListApiBaseUrl', joinUrl(issuer.baseUrl || tenantOrigin, endpointPath(issuer, ['statusLists'], '/api/statuslist/v1')));",
                  "setPublic('tenantIssuerApiBaseUrl', joinUrl(issuer.baseUrl || tenantOrigin, endpointPath(issuer, ['api'], '/api/oid4vci/v1')));",
                  "setPublic('tenantDcqlApiBaseUrl', joinUrl(verifier.baseUrl || tenantOrigin, endpointPath(verifier, ['dcqlApi'], '/api/dcql/v1')));",
                  "setPublic('tenantVerifierBackendBaseUrl', joinUrl(verifier.baseUrl || tenantOrigin, '/oid4vp/backend'));",
                  "pm.collectionVariables.set('tenantRuntimeServicesResolved', 'true');",
                  "pm.environment.set('tenantRuntimeServicesResolved', 'true');"
                ]
              }
            }
          ]
        },
        {
          "name": "07 Resolve verifier party id",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vp/verifier/instances",
            "description": "Reads the verifier instance created during tenant registration and stores its verifier party id. DCQL binding and OID4VP verification requests use this UUID; tenantSlug remains only the public host/routing slug.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('verifier instances listed', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "const instances = j.data || j.items || j.instances || (Array.isArray(j) ? j : []);",
                  "const stringOf = (value) => typeof value === 'string' && value.trim() ? value.trim() : null;",
                  "const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;",
                  "const uuidOf = (value) => {",
                  "  const text = stringOf(value);",
                  "  return text && uuidPattern.test(text) ? text : null;",
                  "};",
                  "const capabilityOf = (instance) => instance && typeof instance.capability === 'object' && instance.capability ? instance.capability : {};",
                  "const idOf = (instance) => instance && (",
                  "  uuidOf(instance.partyId) ||",
                  "  uuidOf(capabilityOf(instance).softwarePartyId) ||",
                  "  uuidOf(instance.verifierId) ||",
                  "  uuidOf(instance.id)",
                  ");",
                  "const selected = instances.find((instance) => instance.enabled !== false) || instances[0];",
                  "const resolvedId = idOf(selected);",
                  "pm.test('verifier party id resolved from platform config', () => {",
                  "  pm.expect(resolvedId, 'verifier party id').to.be.a('string').and.match(uuidPattern);",
                  "});",
                  "if (resolvedId) {",
                  "  pm.collectionVariables.set('verifierId', resolvedId);",
                  "  pm.environment.set('verifierId', resolvedId);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "08 Resolve issuer instance id",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuer/instances",
            "description": "Reads the issuer instance created during tenant registration and stores its runtime instance id. Issuer branding and credential designs bind to this issuer instance id.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('issuer instances listed', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "const instances = j.data || j.items || j.instances || (Array.isArray(j) ? j : []);",
                  "const stringOf = (value) => typeof value === 'string' && value.trim() ? value.trim() : null;",
                  "const idOf = (instance) => instance && (",
                  "  stringOf(instance.instanceId) ||",
                  "  stringOf(instance.slug) ||",
                  "  stringOf(instance.id) ||",
                  "  stringOf(instance.issuerId) ||",
                  "  stringOf(instance.partyId)",
                  ");",
                  "const selected = instances.find((instance) => instance.enabled !== false) || instances[0];",
                  "const resolvedId = idOf(selected);",
                  "pm.test('issuer instance id resolved from platform config', () => {",
                  "  pm.expect(resolvedId, 'issuer instance id').to.be.a('string').and.not.empty;",
                  "});",
                  "if (resolvedId) {",
                  "  pm.collectionVariables.set('issuerId', resolvedId);",
                  "  pm.environment.set('issuerId', resolvedId);",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "04 Tenant Federation",
      "description": "Optional tenant federation configuration. This group registers and lists an external OpenID Connect identity provider using a secret reference rather than an inline secret.",
      "item": [
        {
          "name": "01 Register federation IdP",
          "request": {
            "method": "POST",
            "url": "{{platformUrl}}/api/platform/admin/v1/federation/idps",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"displayName\": \"{{tenantName}} Corporate IdP\",\n  \"issuer\": \"https://idp.{{tenantSlug}}.example\",\n  \"clientId\": \"{{tenantSlug}}-portal\",\n  \"clientSecretRef\": \"{{idpClientSecretRef}}\",\n  \"scopes\": [\"openid\", \"profile\", \"email\"],\n  \"claimsMapping\": {\n    \"subject\": \"sub\",\n    \"email\": \"email\",\n    \"displayName\": \"name\"\n  },\n  \"enabled\": false\n}"
            },
            "description": "Registers an external OpenID Connect identity provider for the tenant."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('idp register succeeds', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "pm.expect(j.idpId, 'idpId').to.be.a('string').and.not.empty;",
                  "pm.collectionVariables.set('idpId', j.idpId);"
                ]
              }
            }
          ]
        },
        {
          "name": "02 List federation IdPs",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/admin/v1/federation/idps",
            "description": "Lists the tenant's configured identity providers.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('idps listed', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "05 Tenant Service Token",
      "item": [
        {
          "name": "01 Get tenant service token",
          "request": {
            "method": "POST",
            "url": "{{platformUrl}}/token",
            "description": "Uses the platform AS RFC 8693 token-exchange path to turn the authenticated platform operator token into a tenant-scoped runtime token. Runtime services accept this platform-issued tenant token as a trusted second issuer; KMS remains strict-authenticated and never accepts anonymous writes.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "urn:ietf:params:oauth:grant-type:token-exchange",
                  "type": "text"
                },
                {
                  "key": "subject_token",
                  "value": "{{operatorToken}}",
                  "type": "text"
                },
                {
                  "key": "subject_token_type",
                  "value": "urn:ietf:params:oauth:token-type:access_token",
                  "type": "text"
                },
                {
                  "key": "requested_token_type",
                  "value": "urn:ietf:params:oauth:token-type:access_token",
                  "type": "text"
                },
                {
                  "key": "resource",
                  "value": "urn:sphereon:tenant:{{tenantId}}",
                  "type": "text"
                },
                {
                  "key": "audience",
                  "value": "enterprise-tenant-kms",
                  "type": "text"
                },
                {
                  "key": "audience",
                  "value": "enterprise-tenant-did",
                  "type": "text"
                },
                {
                  "key": "audience",
                  "value": "enterprise-issuer",
                  "type": "text"
                },
                {
                  "key": "audience",
                  "value": "enterprise-verifier",
                  "type": "text"
                },
                {
                  "key": "audience",
                  "value": "enterprise-wallet-unit",
                  "type": "text"
                },
                {
                  "key": "audience",
                  "value": "enterprise-wallet-interaction",
                  "type": "text"
                },
                {
                  "key": "client_id",
                  "value": "platform-operator-cli",
                  "type": "text"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant runtime token issued', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('access token returned', () => pm.expect(j.access_token).to.be.a('string').and.not.empty);",
                  "const tenantAccessToken = j.access_token || '';",
                  "const parts = tenantAccessToken.split('.');",
                  "pm.test('tenant runtime token is a compact JWT', () => pm.expect(parts.length).to.eql(3));",
                  "let tenantAccessTokenPayload = {};",
                  "if (parts.length === 3) {",
                  "  const payloadSegment = parts[1].replace(/-/g, '+').replace(/_/g, '/');",
                  "  const paddedPayload = payloadSegment + '='.repeat((4 - (payloadSegment.length % 4)) % 4);",
                  "  tenantAccessTokenPayload = JSON.parse(atob(paddedPayload));",
                  "}",
                  "pm.test('tenant runtime token has issuer', () => pm.expect(tenantAccessTokenPayload.iss).to.be.a('string').and.not.empty);",
                  "const tenantBinding = tenantAccessTokenPayload.tenant_id || tenantAccessTokenPayload.tenantId || tenantAccessTokenPayload.tenant;",
                  "pm.test('tenant runtime token binds the registered tenant', () => pm.expect(tenantBinding).to.eql(pm.collectionVariables.get('tenantId')));",
                  "const aud = tenantAccessTokenPayload.aud;",
                  "const audiences = Array.isArray(aud) ? aud : (aud ? [aud] : []);",
                  "pm.test('tenant runtime token is scoped to runtime audiences', () => {",
                  "  ['enterprise-tenant-kms', 'enterprise-tenant-did', 'enterprise-issuer', 'enterprise-verifier', 'enterprise-wallet-unit', 'enterprise-wallet-interaction'].forEach((expected) => pm.expect(audiences, expected).to.include(expected));",
                  "});",
                  "pm.collectionVariables.set('tenantToken', tenantAccessToken);"
                ]
              }
            }
          ]
        }
      ],
      "description": "Exchanges the platform operator token for a tenant runtime token. Authenticated tenant gateway/admin API calls use this tenant-scoped token."
    },
    {
      "name": "06 Tenant Keys and DID",
      "description": "Verifies tenant setup KMS material, discovers the tenant did:web identifier created during tenant activation, and verifies the public did.json document.",
      "item": [
        {
          "name": "01 List KMS providers",
          "request": {
            "method": "GET",
            "url": "{{tenantKmsApiBaseUrl}}/providers",
            "description": "Lists configured tenant KMS providers using the tenant bearer token. Exactly one customer-visible provider must be exposed, and its id must be the tenant slug.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('kms providers listed', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "const providers = Array.isArray(body) ? body : (body.providers || body.keyProviders || []);",
                  "const providerIds = providers.map((p) => p.providerId || p.id).filter(Boolean).sort();",
                  "pm.expect(providerIds, 'only the tenant slug provider is visible').to.eql([pm.variables.get('kmsProviderId')]);",
                  "pm.expect(providerIds, 'generic local provider is hidden').to.not.include('software');",
                  "pm.expect(providerIds, 'internal token verifier is hidden').to.not.include('internal-token-verifier');",
                  "pm.expect(providerIds, 'license provider is hidden').to.not.include('license');",
                  "pm.expect(providerIds, 'platform provider is hidden').to.not.include('platform');"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Get tenant provider capabilities",
          "request": {
            "method": "GET",
            "url": "{{tenantKmsApiBaseUrl}}/providers/{{kmsProviderId}}/capabilities",
            "description": "Reads the selected provider capability report so the suite fails when provider discovery is mounted but provider-specific capability routing is broken.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('provider capabilities returned', () => pm.response.to.have.status(200));",
                  "pm.expect(pm.response.text(), 'capability payload').to.include(pm.variables.get('kmsProviderId'));"
                ]
              }
            }
          ]
        },
        {
          "name": "03 List tenant setup KMS keys",
          "request": {
            "method": "GET",
            "url": "{{tenantKmsApiBaseUrl}}/keys",
            "description": "Lists keys provisioned by tenant setup. The collection must not generate the default tenant KMS material through KMS APIs.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('kms keys listed', () => pm.response.to.have.status(200));",
                  "const text = pm.response.text();",
                  "pm.expect(text, 'issuer signing key visible').to.include('issuer-signing-' + pm.variables.get('tenantSlug'));",
                  "pm.expect(text, 'verifier signing key visible').to.include('oid4vp-verifier-signing-' + pm.variables.get('tenantSlug'));"
                ]
              }
            }
          ]
        },
        {
          "name": "04 List activation-created DID identifiers",
          "request": {
            "method": "GET",
            "url": "{{tenantDidApiBaseUrl}}/identifiers",
            "description": "Lists managed DIDs for the tenant and verifies tenant activation created the default did:web record. This collection must not create the default DID through the management API.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('did identifiers listed', () => pm.response.to.have.status(200));",
                  "const setPublic = (key, value) => {",
                  "  pm.collectionVariables.set(key, value);",
                  "  pm.environment.set(key, value);",
                  "};",
                  "const didWebDocumentUrl = (did, gatewayUrl) => {",
                  "  const prefix = 'did:web:';",
                  "  if (!String(did || '').startsWith(prefix)) return '';",
                  "  const parts = String(did).slice(prefix.length).split(':').map((part) => decodeURIComponent(part));",
                  "  const didHost = parts.shift();",
                  "  if (!didHost) return '';",
                  "  let scheme = 'https';",
                  "  let gatewayAuthority = '';",
                  "  const gatewayMatch = String(gatewayUrl || '').match(/^([a-z][a-z0-9+.-]*):\\/\\/([^/?#]+)/i);",
                  "  if (gatewayMatch) {",
                  "    scheme = gatewayMatch[1].toLowerCase();",
                  "    gatewayAuthority = gatewayMatch[2];",
                  "  }",
                  "  const gatewayHostname = gatewayAuthority.replace(/:\\d+$/, '');",
                  "  const didHostname = didHost.replace(/:\\d+$/, '');",
                  "  const useGatewayAuthority = gatewayHostname === didHostname || gatewayAuthority === didHost;",
                  "  const authority = useGatewayAuthority ? gatewayAuthority : didHost;",
                  "  const outputScheme = useGatewayAuthority ? scheme : 'https';",
                  "  const documentPath = parts.length ? '/' + parts.map((part) => encodeURIComponent(part)).join('/') + '/did.json' : '/.well-known/did.json';",
                  "  return outputScheme + '://' + authority + documentPath;",
                  "};",
                  "const collectDids = (value, out) => {",
                  "  if (typeof value === 'string') {",
                  "    if (value.indexOf('did:web:') === 0 && value.indexOf('#') < 0) out.push(value);",
                  "    return out;",
                  "  }",
                  "  if (Array.isArray(value)) { value.forEach((item) => collectDids(item, out)); return out; }",
                  "  if (value && typeof value === 'object') Object.keys(value).forEach((key) => collectDids(value[key], out));",
                  "  return out;",
                  "};",
                  "const body = pm.response.json();",
                  "const expectedDid = pm.collectionVariables.get('did');",
                  "const discovered = collectDids(body, []).filter((did, index, all) => all.indexOf(did) === index);",
                  "const activationDid = discovered.find((did) => did === expectedDid) || discovered[0];",
                  "pm.expect(activationDid, 'activation-created did discovered from identifiers API').to.eql(expectedDid);",
                  "setPublic('did', activationDid);",
                  "setPublic('didEncoded', encodeURIComponent(activationDid));",
                  "setPublic('didJsonUrl', didWebDocumentUrl(activationDid, pm.variables.get('tenantGatewayUrl')));",
                  "pm.expect(pm.collectionVariables.get('didJsonUrl'), 'did.json URL discovered from activation-created did:web').to.include('/did.json');"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Resolve activation-created DID",
          "request": {
            "method": "GET",
            "url": "{{tenantDidApiBaseUrl}}/identifiers/{{didEncoded}}",
            "description": "Resolves the activation-created DID through the management API and returns the full record including the DID document.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('did resolved', () => pm.response.to.have.status(200));",
                  "pm.expect(pm.response.text(), 'resolved activation-created did appears').to.include(pm.collectionVariables.get('did'));",
                  "pm.expect(pm.collectionVariables.get('didJsonUrl'), 'did.json URL is available for hosted discovery').to.include('/did.json');"
                ]
              }
            }
          ]
        },
        {
          "name": "06 List activation-created DID verification methods",
          "request": {
            "method": "GET",
            "url": "{{tenantDidApiBaseUrl}}/identifiers/{{didEncoded}}/verification-methods",
            "description": "Lists verification methods on the activation-created managed DID, proving tenant setup attached the verifier authentication key and issuer assertion key.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('verification methods listed', () => pm.response.to.have.status(200));",
                  "const text = pm.response.text();",
                  "pm.expect(text, 'did appears in methods response').to.include(pm.collectionVariables.get('did'));",
                  "pm.expect(text, 'verifier signing key appears').to.include('oid4vp-verifier-signing-' + pm.variables.get('tenantSlug'));",
                  "pm.expect(text, 'issuer signing key appears').to.include('issuer-signing-' + pm.variables.get('tenantSlug'));"
                ]
              }
            }
          ]
        },
        {
          "name": "07 Fetch hosted activation did.json",
          "request": {
            "method": "GET",
            "url": "{{didJsonUrl}}",
            "description": "Fetches the activation-created did:web document from the did.json URL derived from the discovered DID.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('did.json returned', () => pm.response.to.have.status(200));",
                  "pm.expect(pm.variables.get('didJsonUrl'), 'did.json URL derived from activation DID').to.include('/did.json');",
                  "const j = pm.response.json();",
                  "pm.expect(j.id, 'hosted did id').to.eql(pm.collectionVariables.get('did'));",
                  "const methods = Array.isArray(j.verificationMethod) ? j.verificationMethod : [];",
                  "pm.expect(methods.length, 'hosted did verification methods').to.be.at.least(2);",
                  "const assertion = Array.isArray(j.assertionMethod) ? j.assertionMethod : [];",
                  "pm.expect(assertion.length, 'hosted did assertion methods').to.be.at.least(2);",
                  "pm.expect(JSON.stringify(assertion), 'assertion methods point at activation DID').to.include(pm.collectionVariables.get('did') + '#');"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "07 Issuer Settings",
      "description": "Creates the issuer design that represents the issuing party for the tenant and attaches its display metadata. Credential designs created later reference this issuer.",
      "item": [
        {
          "name": "01 Create issuer design",
          "request": {
            "method": "POST",
            "url": "{{tenantCredentialDesignApiBaseUrl}}/designs/issuers",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bindings\": [\n    {\n      \"issuerDid\": \"{{did}}\",\n      \"issuerId\": \"{{issuerId}}\",\n      \"issuerUri\": \"{{tenantGatewayUrl}}\"\n    }\n  ],\n  \"alias\": \"{{tenantSlug}}-authority\",\n  \"hostingMode\": \"LOCAL\",\n  \"displays\": [\n    {\n      \"locale\": \"en\",\n      \"displayName\": \"{{tenantName}} Authority\",\n      \"description\": \"{{tenantName}} credential issuing authority\"\n    },\n    {\n      \"locale\": \"nl\",\n      \"displayName\": \"{{tenantName}} Autoriteit\",\n      \"description\": \"Uitgevende autoriteit voor verifieerbare credentials van {{tenantName}}\"\n    }\n  ]\n}"
            },
            "description": "Binds the issuing identity to the tenant's did:web identifier. Wallets display this name and description when presenting a credential offer."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('issuer design created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.id) pm.collectionVariables.set('issuerDesignId', j.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Upload issuer logo asset",
          "request": {
            "method": "POST",
            "url": "{{tenantCredentialDesignApiBaseUrl}}/designs/credentials/{{issuerDesignId}}/assets/en/LOGO",
            "header": [
              {
                "key": "Content-Type",
                "value": "image/png"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "file",
              "file": {
                "src": "fixtures/logo.png"
              }
            },
            "description": "Uploads the binary PNG logo as raw application/octet-stream (Content-Type image/png) for this credential design. The response is an AssetReference whose uri is content-addressed (/public/assets/design/<sha256>.png) and whose integrity is sha256-<base64>. The captured uri/integrity are reused by the render variant(s) below so branding is hosted, de-duplicated by content hash, and SRI-verifiable. The runner mounts the file through its configured file resolver."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('02 Upload issuer logo asset succeeded', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const ref = pm.response.json();",
                  "pm.test('asset uri is content-addressed', () => pm.expect(ref.uri, 'uri').to.be.a('string').and.to.include('/public/assets/design/'));",
                  "pm.test('asset carries sha256 integrity', () => pm.expect(ref.integrity, 'integrity').to.be.a('string').and.to.match(/^sha256-/));",
                  "if (ref.uri) pm.collectionVariables.set('issuerLogoUri', ref.uri);",
                  "if (ref.integrity) pm.collectionVariables.set('issuerLogoIntegrity', ref.integrity);"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Create issuer render variant",
          "request": {
            "method": "POST",
            "url": "{{tenantCredentialDesignApiBaseUrl}}/designs/render/variants",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"SIMPLE_CARD\",\n  \"alias\": \"issuer-card\",\n  \"localeApplicability\": [\n    \"en\",\n    \"nl\"\n  ],\n  \"backgroundColor\": \"#0B5FFF\",\n  \"textColor\": \"#FFFFFF\",\n  \"logo\": {\n    \"uri\": \"{{issuerLogoUri}}\",\n    \"integrity\": \"{{issuerLogoIntegrity}}\",\n    \"altText\": \"{{tenantName}} authority logo\"\n  }\n}"
            },
            "description": "SIMPLE_CARD render variant whose logo references the content-addressed asset uploaded above (uri + uri#integrity SRI), so the hosted branding path is exercised end-to-end."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('issuer render variant created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.id) pm.collectionVariables.set('issuerVariantId', j.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Attach render variant to issuer design",
          "request": {
            "method": "PUT",
            "url": "{{tenantCredentialDesignApiBaseUrl}}/designs/issuers/{{issuerDesignId}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"renderVariantIds\": [\n    \"{{issuerVariantId}}\"\n  ]\n}"
            },
            "description": "Attaches the freshly created render variant(s) to the credential design. Done as a follow-up PUT because the design must exist first to give the asset-upload endpoint a real {designId}, and the variants must exist before they can be referenced. Ordering: create design -> upload asset -> create variant(s) -> update design."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('issuer design updated with render variant', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "08 Credential Designs",
      "description": "Creates the EuPid SD-JWT and Mdl mdoc credential designs, uploads branding assets, attaches render variants, and confirms the tenant can list the designs.",
      "item": [
        {
          "name": "01 Create EuPid SD-JWT design",
          "request": {
            "method": "POST",
            "url": "{{tenantCredentialDesignApiBaseUrl}}/designs/credentials",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bindings\": [\n    {\n      \"vct\": \"EuPid\",\n      \"vctHostingMode\": \"HOSTED\",\n      \"credentialConfigurationId\": \"EuPid\",\n      \"issuerUri\": \"{{tenantGatewayUrl}}\",\n      \"issuerId\": \"{{issuerId}}\"\n    }\n  ],\n  \"alias\": \"eu-pid\",\n  \"hostingMode\": \"LOCAL\",\n  \"credentialType\": {\n    \"format\": \"SD_JWT_VC\",\n    \"vct\": \"EuPid\"\n  },\n  \"displays\": [\n    {\n      \"locale\": \"en\",\n      \"name\": \"EU Personal ID\",\n      \"description\": \"European personal identity credential\"\n    },\n    {\n      \"locale\": \"nl\",\n      \"name\": \"EU Persoonlijke ID\",\n      \"description\": \"Europese persoonlijke identiteitscredential\"\n    }\n  ],\n  \"claims\": [\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"family_name\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Family name\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Achternaam\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 1,\n      \"sdPolicy\": \"ALWAYS\"\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"given_name\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Given name\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Voornaam\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 2,\n      \"sdPolicy\": \"ALWAYS\"\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"birth_date\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Date of birth\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Geboortedatum\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 3\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"age_over_18\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Age over 18\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Ouder dan 18\"\n        }\n      ],\n      \"mandatory\": false,\n      \"order\": 4\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"nationality\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Nationality\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Nationaliteit\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 5\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"issuing_authority\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Issuing authority\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Uitgevende autoriteit\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 6\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"issuing_country\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Issuing country\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Land van uitgifte\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 7,\n      \"sdPolicy\": \"ALWAYS\"\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"document_number\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Document number\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Documentnummer\"\n        }\n      ],\n      \"mandatory\": false,\n      \"order\": 8\n    }\n  ]\n}"
            },
            "description": "EuPid credential design with English and Dutch displays, per-locale claim labels, and the en/nl render variants attached (both at the design level and as per-display preferred variants). family_name, given_name, and issuing_country are always selectively disclosable."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('eupid design created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.id) pm.collectionVariables.set('eupidDesignId', j.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Upload EuPid logo asset",
          "request": {
            "method": "POST",
            "url": "{{tenantCredentialDesignApiBaseUrl}}/designs/credentials/{{eupidDesignId}}/assets/en/LOGO",
            "header": [
              {
                "key": "Content-Type",
                "value": "image/png"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "file",
              "file": {
                "src": "fixtures/logo.png"
              }
            },
            "description": "Uploads the binary PNG logo as raw application/octet-stream (Content-Type image/png) for this credential design. The response is an AssetReference whose uri is content-addressed (/public/assets/design/<sha256>.png) and whose integrity is sha256-<base64>. The captured uri/integrity are reused by the render variant(s) below so branding is hosted, de-duplicated by content hash, and SRI-verifiable. The runner mounts the file through its configured file resolver."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('02 Upload EuPid logo asset succeeded', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const ref = pm.response.json();",
                  "pm.test('asset uri is content-addressed', () => pm.expect(ref.uri, 'uri').to.be.a('string').and.to.include('/public/assets/design/'));",
                  "pm.test('asset carries sha256 integrity', () => pm.expect(ref.integrity, 'integrity').to.be.a('string').and.to.match(/^sha256-/));",
                  "if (ref.uri) pm.collectionVariables.set('eupidLogoUri', ref.uri);",
                  "if (ref.integrity) pm.collectionVariables.set('eupidLogoIntegrity', ref.integrity);"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Create EuPid render variant (en)",
          "request": {
            "method": "POST",
            "url": "{{tenantCredentialDesignApiBaseUrl}}/designs/render/variants",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"SIMPLE_CARD\",\n  \"alias\": \"eupid-card-en\",\n  \"localeApplicability\": [\n    \"en\"\n  ],\n  \"backgroundColor\": \"#0B5FFF\",\n  \"textColor\": \"#FFFFFF\",\n  \"logo\": {\n    \"uri\": \"{{eupidLogoUri}}\",\n    \"integrity\": \"{{eupidLogoIntegrity}}\",\n    \"altText\": \"EU PID logo\"\n  }\n}"
            },
            "description": "SIMPLE_CARD render variant whose logo references the content-addressed asset uploaded above (uri + uri#integrity SRI), so the hosted branding path is exercised end-to-end."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('eupid en render variant created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.id) pm.collectionVariables.set('eupidVariantEnId', j.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Create EuPid render variant (nl)",
          "request": {
            "method": "POST",
            "url": "{{tenantCredentialDesignApiBaseUrl}}/designs/render/variants",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"SIMPLE_CARD\",\n  \"alias\": \"eupid-card-nl\",\n  \"localeApplicability\": [\n    \"nl\"\n  ],\n  \"backgroundColor\": \"#0B5FFF\",\n  \"textColor\": \"#FFFFFF\",\n  \"logo\": {\n    \"uri\": \"{{eupidLogoUri}}\",\n    \"integrity\": \"{{eupidLogoIntegrity}}\",\n    \"altText\": \"EU PID logo\"\n  }\n}"
            },
            "description": "SIMPLE_CARD render variant whose logo references the content-addressed asset uploaded above (uri + uri#integrity SRI), so the hosted branding path is exercised end-to-end."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('eupid nl render variant created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.id) pm.collectionVariables.set('eupidVariantNlId', j.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Attach render variants to EuPid design",
          "request": {
            "method": "PUT",
            "url": "{{tenantCredentialDesignApiBaseUrl}}/designs/credentials/{{eupidDesignId}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"displays\": [\n    {\n      \"locale\": \"en\",\n      \"name\": \"EU Personal ID\",\n      \"description\": \"European personal identity credential\",\n      \"preferredRenderVariantIds\": [\n        \"{{eupidVariantEnId}}\"\n      ]\n    },\n    {\n      \"locale\": \"nl\",\n      \"name\": \"EU Persoonlijke ID\",\n      \"description\": \"Europese persoonlijke identiteitscredential\",\n      \"preferredRenderVariantIds\": [\n        \"{{eupidVariantNlId}}\"\n      ]\n    }\n  ],\n  \"renderVariantIds\": [\n    \"{{eupidVariantEnId}}\",\n    \"{{eupidVariantNlId}}\"\n  ]\n}"
            },
            "description": "Attaches the freshly created render variant(s) to the credential design. Done as a follow-up PUT because the design must exist first to give the asset-upload endpoint a real {designId}, and the variants must exist before they can be referenced. Ordering: create design -> upload asset -> create variant(s) -> update design."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('eupid design updated with render variants', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "06 Create Mdl mdoc design",
          "request": {
            "method": "POST",
            "url": "{{tenantCredentialDesignApiBaseUrl}}/designs/credentials",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bindings\": [\n    {\n      \"docType\": \"org.iso.18013.5.1.mDL\",\n      \"credentialConfigurationId\": \"Mdl\",\n      \"issuerUri\": \"{{tenantGatewayUrl}}\",\n      \"issuerId\": \"{{issuerId}}\"\n    }\n  ],\n  \"alias\": \"mdl\",\n  \"hostingMode\": \"LOCAL\",\n  \"credentialType\": {\n    \"format\": \"MSO_MDOC\",\n    \"docType\": \"org.iso.18013.5.1.mDL\"\n  },\n  \"displays\": [\n    {\n      \"locale\": \"en\",\n      \"name\": \"Mobile Driving Licence\",\n      \"description\": \"ISO 18013-5 mobile driving licence\"\n    },\n    {\n      \"locale\": \"nl\",\n      \"name\": \"Mobiel Rijbewijs\",\n      \"description\": \"ISO 18013-5 mobiel rijbewijs\"\n    }\n  ],\n  \"claims\": [\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"org.iso.18013.5.1\"\n        },\n        {\n          \"type\": \"property\",\n          \"name\": \"family_name\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Family name\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Achternaam\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 1\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"org.iso.18013.5.1\"\n        },\n        {\n          \"type\": \"property\",\n          \"name\": \"given_name\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Given name\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Voornaam\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 2\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"org.iso.18013.5.1\"\n        },\n        {\n          \"type\": \"property\",\n          \"name\": \"birth_date\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Date of birth\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Geboortedatum\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 3\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"org.iso.18013.5.1\"\n        },\n        {\n          \"type\": \"property\",\n          \"name\": \"issue_date\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Issue date\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Datum van uitgifte\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 4\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"org.iso.18013.5.1\"\n        },\n        {\n          \"type\": \"property\",\n          \"name\": \"expiry_date\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Expiry date\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Vervaldatum\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 5\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"org.iso.18013.5.1\"\n        },\n        {\n          \"type\": \"property\",\n          \"name\": \"issuing_country\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Issuing country\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Land van uitgifte\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 6\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"org.iso.18013.5.1\"\n        },\n        {\n          \"type\": \"property\",\n          \"name\": \"issuing_authority\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Issuing authority\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Uitgevende autoriteit\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 7\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"org.iso.18013.5.1\"\n        },\n        {\n          \"type\": \"property\",\n          \"name\": \"document_number\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Document number\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Documentnummer\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 8\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"org.iso.18013.5.1\"\n        },\n        {\n          \"type\": \"property\",\n          \"name\": \"portrait\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Portrait\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Portret\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 9\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"org.iso.18013.5.1\"\n        },\n        {\n          \"type\": \"property\",\n          \"name\": \"driving_privileges\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"Driving privileges\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"Rijbevoegdheden\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 10\n    },\n    {\n      \"path\": [\n        {\n          \"type\": \"property\",\n          \"name\": \"org.iso.18013.5.1\"\n        },\n        {\n          \"type\": \"property\",\n          \"name\": \"un_distinguishing_sign\"\n        }\n      ],\n      \"labels\": [\n        {\n          \"locale\": \"en\",\n          \"label\": \"UN distinguishing sign\"\n        },\n        {\n          \"locale\": \"nl\",\n          \"label\": \"VN-onderscheidingsteken\"\n        }\n      ],\n      \"mandatory\": true,\n      \"order\": 11\n    }\n  ]\n}"
            },
            "description": "Mdl credential design with English and Dutch displays, per-locale claim labels under the org.iso.18013.5.1 namespace, and the en render variant attached."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mdl design created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.id) pm.collectionVariables.set('mdlDesignId', j.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "07 Upload Mdl logo asset",
          "request": {
            "method": "POST",
            "url": "{{tenantCredentialDesignApiBaseUrl}}/designs/credentials/{{mdlDesignId}}/assets/en/LOGO",
            "header": [
              {
                "key": "Content-Type",
                "value": "image/png"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "file",
              "file": {
                "src": "fixtures/logo.png"
              }
            },
            "description": "Uploads the binary PNG logo as raw application/octet-stream (Content-Type image/png) for this credential design. The response is an AssetReference whose uri is content-addressed (/public/assets/design/<sha256>.png) and whose integrity is sha256-<base64>. The captured uri/integrity are reused by the render variant(s) below so branding is hosted, de-duplicated by content hash, and SRI-verifiable. The runner mounts the file through its configured file resolver."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('07 Upload Mdl logo asset succeeded', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const ref = pm.response.json();",
                  "pm.test('asset uri is content-addressed', () => pm.expect(ref.uri, 'uri').to.be.a('string').and.to.include('/public/assets/design/'));",
                  "pm.test('asset carries sha256 integrity', () => pm.expect(ref.integrity, 'integrity').to.be.a('string').and.to.match(/^sha256-/));",
                  "if (ref.uri) pm.collectionVariables.set('mdlLogoUri', ref.uri);",
                  "if (ref.integrity) pm.collectionVariables.set('mdlLogoIntegrity', ref.integrity);"
                ]
              }
            }
          ]
        },
        {
          "name": "08 Create Mdl render variant (en)",
          "request": {
            "method": "POST",
            "url": "{{tenantCredentialDesignApiBaseUrl}}/designs/render/variants",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"SIMPLE_CARD\",\n  \"alias\": \"mdl-card-en\",\n  \"localeApplicability\": [\n    \"en\"\n  ],\n  \"backgroundColor\": \"#1B5E20\",\n  \"textColor\": \"#FFFFFF\",\n  \"logo\": {\n    \"uri\": \"{{mdlLogoUri}}\",\n    \"integrity\": \"{{mdlLogoIntegrity}}\",\n    \"altText\": \"Mobile driving licence logo\"\n  }\n}"
            },
            "description": "SIMPLE_CARD render variant whose logo references the content-addressed asset uploaded above (uri + uri#integrity SRI), so the hosted branding path is exercised end-to-end."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mdl en render variant created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.id) pm.collectionVariables.set('mdlVariantEnId', j.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "09 Attach render variant to Mdl design",
          "request": {
            "method": "PUT",
            "url": "{{tenantCredentialDesignApiBaseUrl}}/designs/credentials/{{mdlDesignId}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"displays\": [\n    {\n      \"locale\": \"en\",\n      \"name\": \"Mobile Driving Licence\",\n      \"description\": \"ISO 18013-5 mobile driving licence\",\n      \"preferredRenderVariantIds\": [\n        \"{{mdlVariantEnId}}\"\n      ]\n    },\n    {\n      \"locale\": \"nl\",\n      \"name\": \"Mobiel Rijbewijs\",\n      \"description\": \"ISO 18013-5 mobiel rijbewijs\",\n      \"preferredRenderVariantIds\": [\n        \"{{mdlVariantEnId}}\"\n      ]\n    }\n  ],\n  \"renderVariantIds\": [\n    \"{{mdlVariantEnId}}\"\n  ]\n}"
            },
            "description": "Attaches the freshly created render variant(s) to the credential design. Done as a follow-up PUT because the design must exist first to give the asset-upload endpoint a real {designId}, and the variants must exist before they can be referenced. Ordering: create design -> upload asset -> create variant(s) -> update design."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mdl design updated with render variant', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "10 List credential designs",
          "request": {
            "method": "GET",
            "url": "{{tenantCredentialDesignApiBaseUrl}}/designs/credentials",
            "description": "Lists the tenant's credential designs.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('designs listed', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "09 Status Lists",
      "description": "Creates the EuPid token status list, verifies the hosted status-list token, updates an entry, reactivates it, and reads the resulting entry state.",
      "item": [
        {
          "name": "01 Create token status list",
          "request": {
            "method": "POST",
            "url": "{{tenantStatusListApiBaseUrl}}/statuslists",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"correlationId\": \"eupid-revocation\",\n  \"spec\": \"token_status_list\",\n  \"proofFormat\": \"jwt\",\n  \"issuer\": \"{{tenantGatewayUrl}}\",\n  \"statusListUri\": \"{{tenantGatewayUrl}}/public/statuslists/eupid-revocation\",\n  \"purposes\": [\"revocation\"],\n  \"length\": 131072,\n  \"bitsPerStatus\": 1,\n  \"signingKeyAlias\": \"{{statusListSigningKeyAlias}}\"\n}"
            },
            "description": "Creates the IETF Token Status List the EuPid configuration references, signed with the tenant's credential signing key via the derived `statusListSigningKeyAlias` variable. By default the collection refreshes that variable to `issuer-signing-<tenantSlug>` for the active tenant; set `statusListSigningKeyAliasOverride` only when you need an explicit custom alias. The deployment configuration declares the list under the statuslists namespace; this call materializes it. The correlation id is the stable business identifier; the signed token is re-issued whenever entries change, and indexes are allocated randomly at issuance time, never sequentially. Issuance refuses to mint when a configured list cannot be resolved."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status list created or already provisioned', () => pm.expect([200, 201, 409]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.id) pm.collectionVariables.set('statusListId', j.id);",
                  "if (j.statusListUri) pm.collectionVariables.set('statusListUri', j.statusListUri);",
                  "if (!pm.collectionVariables.get('statusListUri')) pm.collectionVariables.set('statusListUri', 'https://' + pm.variables.get('tenantHost') + '/public/statuslists/eupid-revocation');",
                  "const decodeJwtPart = (jwt, index) => {",
                  "  const compact = String(jwt || '').split('~')[0];",
                  "  const segment = compact.split('.')[index];",
                  "  pm.expect(segment, 'jwt segment ' + index).to.be.a('string').and.not.empty;",
                  "  const normalized = segment.replace(/-/g, '+').replace(/_/g, '/');",
                  "  const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);",
                  "  return JSON.parse(atob(padded));",
                  "};",
                  "pm.test('status list response matches EuPid revocation contract', () => {",
                  "  if (pm.response.code === 409) {",
                  "    pm.expect(j.error && j.error.code).to.eql('STATUSLIST_DUPLICATE_CORRELATION_ID');",
                  "    return;",
                  "  }",
                  "  pm.expect(j.correlationId).to.eql('eupid-revocation');",
                  "  pm.expect(j.spec).to.eql('token_status_list');",
                  "  pm.expect(j.proofFormat).to.eql('jwt');",
                  "  pm.expect(j.bitsPerStatus).to.eql(1);",
                  "  pm.expect(j.statusListUri).to.eql(pm.variables.get('tenantGatewayUrl') + '/public/statuslists/eupid-revocation');",
                  "  pm.expect(j.contentType).to.eql('application/statuslist+jwt');",
                  "});",
                  "if (j.signedToken) {",
                  "  const header = decodeJwtPart(j.signedToken, 0);",
                  "  const payload = decodeJwtPart(j.signedToken, 1);",
                  "  pm.test('status list signed token claims match hosted list', () => {",
                  "    pm.expect(header.typ).to.eql('statuslist+jwt');",
                  "    pm.expect(payload.iss).to.eql(pm.variables.get('tenantGatewayUrl'));",
                  "    pm.expect(payload.sub).to.eql(pm.collectionVariables.get('statusListUri'));",
                  "    pm.expect(payload.status_list && payload.status_list.bits).to.eql(1);",
                  "    pm.expect(payload.status_list && payload.status_list.lst).to.be.a('string').and.not.empty;",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "02 List status lists",
          "request": {
            "method": "GET",
            "url": "{{tenantStatusListApiBaseUrl}}/statuslists",
            "description": "Lists status lists for the tenant after materializing the EuPid revocation list.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status lists returned', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "const lists = Array.isArray(j) ? j : (j.data || j.items || []);",
                  "const expectedUri = pm.variables.get('tenantGatewayUrl') + '/public/statuslists/eupid-revocation';",
                  "const statusList = lists.find((entry) => entry.correlationId === 'eupid-revocation' || entry.statusListUri === expectedUri);",
                  "pm.test('EuPid revocation status list visible', () => {",
                  "  pm.expect(statusList, 'eupid-revocation status list').to.be.an('object');",
                  "  pm.expect(statusList.id, 'status list id').to.be.a('string').and.not.empty;",
                  "});",
                  "if (statusList && statusList.id) pm.collectionVariables.set('statusListId', statusList.id);",
                  "if (statusList && statusList.statusListUri) pm.collectionVariables.set('statusListUri', statusList.statusListUri);"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Get token status list",
          "request": {
            "method": "GET",
            "url": "{{tenantStatusListApiBaseUrl}}/statuslists/{{statusListId}}",
            "description": "Reads back the created status list through the management API before checking the hosted token.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status list returned', () => pm.response.to.have.status(200));",
                  "pm.expect(pm.response.text(), 'created status list id').to.include(pm.collectionVariables.get('statusListId'));"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Fetch hosted status list token",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantHost}}"
              }
            ],
            "url": "{{statusListUri}}",
            "description": "Fetches the signed status list token from the public hosting surface using the API-returned statusListUri. Verifiers dereference exactly this URL when checking credential status."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status list hosted', () => pm.response.to.have.status(200));",
                  "const decodeJwtPart = (jwt, index) => {",
                  "  const compact = String(jwt || '').split('~')[0];",
                  "  const segment = compact.split('.')[index];",
                  "  pm.expect(segment, 'jwt segment ' + index).to.be.a('string').and.not.empty;",
                  "  const normalized = segment.replace(/-/g, '+').replace(/_/g, '/');",
                  "  const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);",
                  "  return JSON.parse(atob(padded));",
                  "};",
                  "const hostedStatusToken = pm.response.text().trim();",
                  "const hostedHeader = decodeJwtPart(hostedStatusToken, 0);",
                  "const hostedPayload = decodeJwtPart(hostedStatusToken, 1);",
                  "pm.test('hosted status list has statuslist JWT media type and claims', () => {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '', 'content type').to.include('application/statuslist+jwt');",
                  "  pm.expect(hostedHeader.typ).to.eql('statuslist+jwt');",
                  "  pm.expect(hostedPayload.iss).to.eql(pm.variables.get('tenantGatewayUrl'));",
                  "  pm.expect(hostedPayload.sub).to.eql(pm.collectionVariables.get('statusListUri'));",
                  "  pm.expect(hostedPayload.status_list && hostedPayload.status_list.bits).to.eql(1);",
                  "  pm.expect(hostedPayload.status_list && hostedPayload.status_list.lst).to.be.a('string').and.not.empty;",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Revoke a status entry",
          "request": {
            "method": "POST",
            "url": "{{tenantStatusListApiBaseUrl}}/statuslists/{{statusListId}}/status",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"statusListIndex\": 42,\n  \"value\": 1\n}"
            },
            "description": "Sets the status bit at an index to 1 (revoked). A status read exposes only the bit value; whether an index is allocated is intentionally not observable."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('entry revoked', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "06 Reactivate the status entry",
          "request": {
            "method": "POST",
            "url": "{{tenantStatusListApiBaseUrl}}/statuslists/{{statusListId}}/status",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"statusListIndex\": 42,\n  \"value\": 0\n}"
            },
            "description": "Sets the same index back to 0 (valid)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('entry reactivated', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "07 Read status entry",
          "request": {
            "method": "GET",
            "url": "{{tenantStatusListApiBaseUrl}}/statuslists/{{statusListId}}/entries/42",
            "description": "Reads the status-list entry that was revoked and reactivated by index. This verifies the non-leaking upsert/read path for allocated and previously unallocated indexes.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status entry returned', () => pm.response.to.have.status(200));",
                  "const entry = pm.response.json();",
                  "pm.test('reactivated status entry is readable without allocation leak', () => {",
                  "  pm.expect(entry.statusListId).to.eql(pm.collectionVariables.get('statusListId'));",
                  "  pm.expect(entry.statusListIndex).to.eql(42);",
                  "  pm.expect(entry.value).to.eql(0);",
                  "  pm.expect(entry.purpose).to.eql('revocation');",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "10 Hosted Branding Verification",
      "description": "Fetches the public VC type metadata, issuer metadata, and content-addressed branding asset without a bearer token. These checks verify what wallets and verifiers can read publicly.",
      "item": [
        {
          "name": "01 Fetch hosted VCT metadata",
          "request": {
            "method": "GET",
            "url": "{{tenantGatewayUrl}}/public/schema/vct/EuPid",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantHost}}"
              }
            ],
            "description": "Public SD-JWT VC Type Metadata for EuPid. Asserts localized displays (en/nl with lang), rendering.simple logo + colors, content-addressed uri#integrity when hosted, and lowercase claims[].sd."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.environment.set(\"brandingAssetsHosted\", \"true\");",
                  "pm.test('vct metadata served', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('vct present', () => pm.expect(j.vct, 'vct').to.be.a('string'));",
                  "const displays = j.display || [];",
                  "pm.test('display has en and nl entries', () => {",
                  "  const langs = displays.map((d) => d.lang);",
                  "  pm.expect(langs, 'display[].lang').to.include('en');",
                  "  pm.expect(langs, 'display[].lang').to.include('nl');",
                  "});",
                  "// SD-JWT VC draft-11 Type Metadata uses `lang`, not OID4VCI metadata `locale`.",
                  "pm.test('display entries use lang (not locale)', () => {",
                  "  displays.forEach((d) => {",
                  "    pm.expect(d, 'display entry').to.have.property('lang');",
                  "    pm.expect(d, 'display entry').to.not.have.property('locale');",
                  "  });",
                  "});",
                  "const withSimple = displays.filter((d) => d.rendering && d.rendering.simple);",
                  "pm.test('display has rendering.simple', () => pm.expect(withSimple.length, 'display[].rendering.simple count').to.be.above(0));",
                  "const simpleLogos = withSimple.map((d) => d.rendering.simple.logo).filter(Boolean);",
                  "pm.test('rendering.simple has logo with uri under asset base', () => {",
                  "  pm.expect(simpleLogos.length, \"logos\").to.be.above(0);",
                  "  simpleLogos.forEach((logo) => {",
                  "    pm.expect(logo.uri, 'logo.uri').to.be.a('string');",
                  "  });",
                  "});",
                  "// Once binary assets are uploaded the uri is content-addressed under /public/assets/design/",
                  "// and carries a Subresource-Integrity sibling keyed literally as 'uri#integrity' (sha256-...).",
                  "const hostedLogos = simpleLogos.filter((l) => typeof l.uri === 'string' && l.uri.includes('/public/assets/design/'));",
                  "if (hostedLogos.length) {",
                  "  pm.test('hosted logo uri is content-addressed', () => {",
                  "    hostedLogos.forEach((l) => pm.expect(l.uri).to.include(\"/public/assets/design/\"));",
                  "  });",
                  "  pm.test('hosted logo carries uri#integrity sha256', () => {",
                  "    hostedLogos.forEach((l) => {",
                  "      pm.expect(l, \"logo\").to.have.property(\"uri#integrity\");",
                  "      pm.expect(l[\"uri#integrity\"], \"uri#integrity\").to.be.a(\"string\").and.to.match(/^sha256-/);",
                  "    });",
                  "  });",
                  "  // Same bytes -> same content-addressed url across en and nl displays (dedup).",
                  "  const enLogo = (displays.find((d) => d.lang === \"en\") || {}).rendering;",
                  "  const nlLogo = (displays.find((d) => d.lang === \"nl\") || {}).rendering;",
                  "  if (enLogo && enLogo.simple && enLogo.simple.logo && nlLogo && nlLogo.simple && nlLogo.simple.logo) {",
                  "    pm.test('en and nl displays share the same content-addressed logo uri', () =>",
                  "      pm.expect(enLogo.simple.logo.uri).to.eql(nlLogo.simple.logo.uri));",
                  "  }",
                  "  // Capture the hash leaf (last path segment) for the asset GET below.",
                  "  const leaf = String(hostedLogos[0].uri).split(\"/\").pop();",
                  "  if (leaf) pm.collectionVariables.set(\"eupidLogoHashLeaf\", leaf);",
                  "} else {",
                  "  pm.test('hosted (content-addressed) logo uri present in VCT', () => pm.expect(hostedLogos.length, 'hosted logos under /public/assets/design/').to.be.above(0));",
                  "  console.log('No /public/assets/design/ logo uri found in VCT - binary branding expected to be content-addressed.');",
                  "}",
                  "pm.test('rendering.simple has background_color and text_color', () => {",
                  "  withSimple.forEach((d) => {",
                  "    pm.expect(d.rendering.simple, 'rendering.simple').to.have.property('background_color');",
                  "    pm.expect(d.rendering.simple, 'rendering.simple').to.have.property('text_color');",
                  "  });",
                  "});",
                  "const claims = j.claims || [];",
                  "pm.test('claims carry localized display labels (en and nl)', () => {",
                  "  pm.expect(claims.length, \"claims\").to.be.above(0);",
                  "  const claimDisplays = claims.flatMap((cl) => cl.display || []);",
                  "  const claimLangs = claimDisplays.map((d) => d.lang);",
                  "  pm.expect(claimLangs, 'claims[].display[].lang').to.include('en');",
                  "  pm.expect(claimLangs, 'claims[].display[].lang').to.include('nl');",
                  "});",
                  "pm.test('claims[].sd is lowercase', () => {",
                  "  claims.forEach((cl) => {",
                  "    if (Object.prototype.hasOwnProperty.call(cl, 'sd')) {",
                  "      pm.expect([\"always\", \"allowed\", \"never\"], \"claims[].sd\").to.include(cl.sd);",
                  "    }",
                  "  });",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Fetch issuer well-known metadata",
          "request": {
            "method": "GET",
            "url": "{{tenantGatewayUrl}}/.well-known/openid-credential-issuer",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantHost}}"
              }
            ],
            "description": "Public OID4VCI issuer metadata. Asserts top-level issuer display (name + logo, en/nl) and the EuPid configuration credential_metadata display (logo, colors, claims) using the locale key."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('issuer metadata served', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "const display = j.display || [];",
                  "pm.test('top-level display has issuer name and logo (en and nl)', () => {",
                  "  pm.expect(display.length, \"display\").to.be.above(0);",
                  "  const locales = display.map((d) => d.locale);",
                  "  pm.expect(locales, 'display[].locale').to.include('en');",
                  "  pm.expect(locales, 'display[].locale').to.include('nl');",
                  "  display.forEach((d) => {",
                  "    pm.expect(d, 'display entry').to.have.property('name');",
                  "    pm.expect(d, 'display entry').to.have.property('logo');",
                  "    pm.expect(d.logo, 'display.logo').to.have.property('uri');",
                  "  });",
                  "});",
                  "pm.test('issuer metadata uses locale key (not lang)', () => {",
                  "  display.forEach((d) => pm.expect(d, 'display entry').to.not.have.property('lang'));",
                  "});",
                  "const cfgs = j.credential_configurations_supported || {};",
                  "pm.test('EuPid configuration present with branding metadata', () => {",
                  "  const eupid = cfgs.EuPid;",
                  "  pm.expect(eupid, 'credential_configurations_supported.EuPid').to.be.an('object');",
                  "  const meta = eupid.credential_metadata || eupid;",
                  "  const md = meta.display || eupid.display || [];",
                  "  pm.expect(md.length, 'EuPid display').to.be.above(0);",
                  "  const withLogo = md.filter((d) => d.logo && d.logo.uri);",
                  "  pm.expect(withLogo.length, 'EuPid display[].logo.uri').to.be.above(0);",
                  "  const withColors = md.filter((d) => Object.prototype.hasOwnProperty.call(d, \"background_color\"));",
                  "  pm.expect(withColors.length, 'EuPid display[].background_color').to.be.above(0);",
                  "  const claims = meta.claims || [];",
                  "  pm.expect(claims.length, 'EuPid credential_metadata.claims').to.be.above(0);",
                  "  const claimLocales = claims.flatMap((cl) => cl.display || []).map((d) => d.locale);",
                  "  pm.expect(claimLocales, 'EuPid claims[].display[].locale').to.include('en');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Fetch content-addressed asset",
          "request": {
            "method": "GET",
            "url": "{{tenantGatewayUrl}}/public/assets/design/{{eupidLogoHashLeaf}}",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantHost}}"
              }
            ],
            "description": "Fetches the public branding asset by its content-addressed path. This verifies that wallet-facing metadata points to a reachable image."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get('brandingAssetsHosted') === 'false' || !pm.collectionVariables.get('eupidLogoHashLeaf')) {",
                  "  pm.test.skip('asset GET skipped (external-URI branding; binary upload not wired)');",
                  "} else {",
                  "  pm.test('content-addressed asset served', () => pm.response.to.have.status(200));",
                  "  pm.test('asset has an image content-type', () => {",
                  "    const ct = pm.response.headers.get('Content-Type') || '';",
                  "    pm.expect(ct, 'Content-Type').to.match(/^image\\//);",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "11 Issue Credentials Simple",
      "description": "Issues EuPid and Mdl credentials with subject data supplied directly at offer creation, then follows the wallet-facing offer, token, proof, and credential request steps.",
      "item": [
        {
          "name": "01 Generate wallet holder key",
          "request": {
            "method": "POST",
            "url": "{{tenantKmsApiBaseUrl}}/keys",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"providerId\": \"{{kmsProviderId}}\",\n  \"alias\": \"wallet-holder\",\n  \"use\": \"sig\",\n  \"alg\": \"ECDSA_SHA256\",\n  \"keyOperations\": [\"sign\"]\n}"
            },
            "description": "Stand-in wallet key. In production this key lives in the holder's wallet; here the KMS holds it so the proof JWT can be signed without a real wallet."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('wallet key generated', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "const kp = j.keyPair || j;",
                  "if (kp && kp.kid) pm.collectionVariables.set('walletKid', kp.kid);",
                  "const jwk = kp && kp.jose ? kp.jose.publicJwk : (kp ? kp.publicJwk : null);",
                  "if (jwk) pm.collectionVariables.set('walletJwk', JSON.stringify(jwk));"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Create EuPid offer",
          "request": {
            "method": "POST",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/credential/offers",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_configuration_ids\": [\"EuPid\"],\n  \"grants\": {\n    \"urn:ietf:params:oauth:grant-type:pre-authorized_code\": {}\n  },\n  \"credential_subject_data\": {\n    \"family_name\": \"Mustermann\",\n    \"given_name\": \"Erika\",\n    \"birth_date\": \"1964-08-12\",\n    \"age_over_18\": true,\n    \"nationality\": \"DE\",\n    \"issuing_authority\": \"DE\",\n    \"issuing_country\": \"DE\",\n    \"document_number\": \"1234567890\"\n  },\n  \"correlation_id\": \"e2e-eupid-001\"\n}"
            },
            "description": "Creates a pre-authorized credential offer with the subject data supplied inline. The response carries the offer URI a wallet would scan."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('offer created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "const trimBase = (base) => base && base.endsWith('/') ? base.slice(0, -1) : base;",
                  "const rewritePublicUrl = (url, base) => {",
                  "  const publicHost = pm.variables.get('tenantHost');",
                  "  const gatewayBase = trimBase(base || pm.variables.get('tenantGatewayUrl'));",
                  "  const m = String(url || '').match(/^(https?:\\/\\/([^/]+))(.*)$/);",
                  "  if (m && publicHost && m[2] === publicHost && gatewayBase) return gatewayBase + (m[3] || '');",
                  "  return url;",
                  "};",
                  "if (j.offer_uri) {",
                  "  pm.collectionVariables.set('offerUri', j.offer_uri);",
                  "  const m = j.offer_uri.match(/credential_offer_uri=([^&]+)/);",
                  "  if (m) pm.collectionVariables.set('credentialOfferUri', rewritePublicUrl(decodeURIComponent(m[1]), pm.variables.get('tenantGatewayUrl')));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Resolve credential offer",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantHost}}"
              }
            ],
            "url": "{{credentialOfferUri}}",
            "description": "Resolves the offer exactly as a wallet does and extracts the pre-authorized code."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('offer resolved', () => pm.response.to.have.status(200));",
                  "const trimBase = (base) => base && base.endsWith('/') ? base.slice(0, -1) : base;",
                  "const rewritePublicUrl = (url, base) => {",
                  "  const publicHost = pm.variables.get('tenantHost');",
                  "  const gatewayBase = trimBase(base || pm.variables.get('tenantGatewayUrl'));",
                  "  const m = String(url || '').match(/^(https?:\\/\\/([^/]+))(.*)$/);",
                  "  if (m && publicHost && m[2] === publicHost && gatewayBase) return gatewayBase + (m[3] || '');",
                  "  return url;",
                  "};",
                  "const j = pm.response.json();",
                  "pm.test('resolved EuPid offer carries exact credential configuration id', () => {",
                  "  pm.expect(j.credential_issuer).to.eql(pm.variables.get('tenantGatewayUrl'));",
                  "  pm.expect(j.credential_configuration_ids).to.eql(['EuPid']);",
                  "});",
                  "const grant = j.grants && j.grants['urn:ietf:params:oauth:grant-type:pre-authorized_code'];",
                  "if (grant && grant['pre-authorized_code']) pm.collectionVariables.set('preAuthCode', grant['pre-authorized_code']);",
                  "// OID4VCI 1.0: for a path-bearing issuer identifier the metadata URL inserts",
                  "// the well-known segment between host and path.",
                  "if (j.credential_issuer) {",
                  "  pm.collectionVariables.set('credentialIssuerPublic', j.credential_issuer);",
                  "  pm.collectionVariables.set('credentialIssuer', j.credential_issuer);",
                  "  const m = j.credential_issuer.match(/^(https?:\\/\\/[^/]+)(\\/.*)?$/);",
                  "  if (m) pm.collectionVariables.set('issuerMetadataUrl', rewritePublicUrl(m[1] + '/.well-known/openid-credential-issuer' + (m[2] || ''), pm.variables.get('tenantGatewayUrl')));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Fetch OID4VCI metadata",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantHost}}"
              }
            ],
            "url": "{{issuerMetadataUrl}}",
            "description": "OID4VCI metadata: credential configurations, endpoints, and authorization-server references served through the tenant gateway. The URL derives from the offer's credential_issuer per OID4VCI 1.0 (the well-known segment goes between host and issuer path)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('OID4VCI metadata served', () => pm.response.to.have.status(200));",
                  "const trimBase = (base) => base && base.endsWith('/') ? base.slice(0, -1) : base;",
                  "const rewritePublicUrl = (url, base) => {",
                  "  const issuerHost = pm.variables.get('tenantHost');",
                  "  const asHost = pm.variables.get('tenantHost') || issuerHost;",
                  "  const gatewayBase = trimBase(base || pm.variables.get('tenantGatewayUrl'));",
                  "  const m = String(url || '').match(/^(https?:\\/\\/([^/]+))(.*)$/);",
                  "  if (m && issuerHost && m[2] === issuerHost && gatewayBase) return gatewayBase + (m[3] || '');",
                  "  if (m && asHost && m[2] === asHost && gatewayBase) return gatewayBase + (m[3] || '');",
                  "  return url;",
                  "};",
                  "const j = pm.response.json();",
                  "if (j.credential_endpoint) pm.collectionVariables.set('credentialEndpoint', rewritePublicUrl(j.credential_endpoint, pm.variables.get('tenantGatewayUrl')));",
                  "const tokenEndpoint = rewritePublicUrl(j.token_endpoint || (pm.variables.get('tenantGatewayUrl') + '/token'), pm.variables.get('tenantGatewayUrl'));",
                  "pm.collectionVariables.set('tokenEndpoint', tokenEndpoint);",
                  "if (j.nonce_endpoint) pm.collectionVariables.set('nonceEndpoint', rewritePublicUrl(j.nonce_endpoint, pm.variables.get('tenantGatewayUrl')));"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Exchange pre-authorized code for token",
          "request": {
            "method": "POST",
            "url": "{{tokenEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "urn:ietf:params:oauth:grant-type:pre-authorized_code"
                },
                {
                  "key": "pre-authorized_code",
                  "value": "{{preAuthCode}}"
                }
              ]
            },
            "description": "Token request with the pre-authorized code grant. The response carries the access token for the credential endpoint and a nonce for the proof of possession."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('token issued', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('EuPid token response is bound to requested credential configuration', () => {",
                  "  const detail = (j.authorization_details || []).find((entry) => entry.type === 'openid_credential');",
                  "  pm.expect(detail, 'openid credential authorization detail').to.be.an('object');",
                  "  pm.expect(detail.credential_configuration_id).to.eql('EuPid');",
                  "  pm.expect(detail.credential_identifiers || []).to.have.length.greaterThan(0);",
                  "});",
                  "if (j.access_token) pm.collectionVariables.set('walletAccessToken', j.access_token);",
                  "pm.collectionVariables.set('cNonce', j.c_nonce || '');"
                ]
              }
            }
          ]
        },
        {
          "name": "06 Request EuPid credential",
          "request": {
            "method": "POST",
            "url": "{{credentialEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{walletAccessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_configuration_id\": \"EuPid\",\n  \"proofs\": {\n    \"jwt\": [\"{{proofJwt}}\"]\n  }\n}"
            },
            "description": "Credential request with an ES256 proof of possession (typ openid4vci-proof+jwt) built in the pre-request script and signed through the KMS. The response carries the SD-JWT credential including the status claim that references the hosted status list."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const b64u = (obj) => CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(obj))).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "const jwk = JSON.parse(pm.collectionVariables.get('walletJwk') || '{}');",
                  "function buildAndSign(nonce) {",
                  "  const header = { alg: 'ES256', typ: 'openid4vci-proof+jwt', jwk: jwk };",
                  "  const payload = { aud: pm.collectionVariables.get('credentialIssuer') || pm.variables.get('tenantGatewayUrl'), iat: Math.floor(Date.now() / 1000), nonce: nonce };",
                  "  const signingInput = b64u(header) + '.' + b64u(payload);",
                  "  const inputB64 = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(signingInput));",
                  "  pm.sendRequest({",
                  "    url: pm.variables.get('tenantKmsApiBaseUrl') + '/signatures/raw/create',",
                  "    method: 'POST',",
                  "    header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + (pm.collectionVariables.get('tenantToken') || pm.variables.get('tenantToken')) },",
                  "    body: { mode: 'raw', raw: JSON.stringify({ keyInfo: { providerId: pm.variables.get('kmsProviderId'), alias: 'wallet-holder' }, input: inputB64 }) }",
                  "  }, (err, res) => {",
                  "    if (err) { console.error('KMS signing failed', err); return; }",
                  "    const j = res.json();",
                  "    const sigB64u = String(j.signature || '').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "    pm.collectionVariables.set('proofJwt', signingInput + '.' + sigB64u);",
                  "  });",
                  "}",
                  "// Nonce: the token response may omit c_nonce; the issuer's nonce endpoint",
                  "// (from metadata) is the authoritative source.",
                  "const tokenNonce = pm.collectionVariables.get('cNonce');",
                  "const nonceEndpoint = pm.collectionVariables.get('nonceEndpoint');",
                  "if (tokenNonce) {",
                  "  buildAndSign(tokenNonce);",
                  "} else if (nonceEndpoint) {",
                  "  pm.sendRequest({ url: nonceEndpoint, method: 'POST', header: { 'Host': pm.variables.get('tenantHost') } }, (err, res) => {",
                  "    if (err) { console.error('nonce fetch failed', err); return; }",
                  "    buildAndSign(res.json().c_nonce);",
                  "  });",
                  "} else {",
                  "  buildAndSign(undefined);",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('eupid credential issued', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "const cred = (j.credentials && j.credentials[0] && j.credentials[0].credential) || j.credential;",
                  "if (cred) pm.collectionVariables.set('eupidCredential', cred);",
                  "const decodeJwtPart = (jwt, index) => {",
                  "  const compact = String(jwt || '').split('~')[0];",
                  "  const segment = compact.split('.')[index];",
                  "  pm.expect(segment, 'jwt segment ' + index).to.be.a('string').and.not.empty;",
                  "  const normalized = segment.replace(/-/g, '+').replace(/_/g, '/');",
                  "  const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);",
                  "  return JSON.parse(atob(padded));",
                  "};",
                  "const eupidPayload = decodeJwtPart(cred, 1);",
                  "pm.test('EuPid credential carries expected VCT and status-list reference', () => {",
                  "  pm.expect(eupidPayload.vct).to.eql(pm.variables.get('tenantGatewayUrl') + '/public/schema/vct/EuPid');",
                  "  pm.expect(eupidPayload.status && eupidPayload.status.status_list && eupidPayload.status.status_list.uri).to.eql(pm.variables.get('tenantGatewayUrl') + '/public/statuslists/eupid-revocation');",
                  "  pm.expect(eupidPayload.status.status_list.idx).to.be.a('number');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "07 Check EuPid offer status",
          "request": {
            "method": "GET",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/credential/offers/e2e-eupid-001",
            "description": "Backend session status after issuance: the lifecycle ends in credential_issued.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('session tracked', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "08 Create Mdl offer",
          "request": {
            "method": "POST",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/credential/offers",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_configuration_ids\": [\"Mdl\"],\n  \"grants\": {\n    \"urn:ietf:params:oauth:grant-type:pre-authorized_code\": {}\n  },\n  \"credential_subject_data\": {\n    \"org.iso.18013.5.1.family_name\": \"Mustermann\",\n    \"org.iso.18013.5.1.given_name\": \"Erika\",\n    \"org.iso.18013.5.1.birth_date\": \"1964-08-12\",\n    \"org.iso.18013.5.1.issue_date\": \"2026-01-15\",\n    \"org.iso.18013.5.1.expiry_date\": \"2031-01-15\",\n    \"org.iso.18013.5.1.issuing_country\": \"DE\",\n    \"org.iso.18013.5.1.issuing_authority\": \"DE\",\n    \"org.iso.18013.5.1.document_number\": \"D123456789\",\n    \"org.iso.18013.5.1.portrait\": \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==\",\n    \"org.iso.18013.5.1.driving_privileges\": [\n      {\n        \"vehicle_category_code\": \"B\",\n        \"issue_date\": \"2010-03-01\",\n        \"expiry_date\": \"2031-01-15\"\n      }\n    ],\n    \"org.iso.18013.5.1.un_distinguishing_sign\": \"D\"\n  },\n  \"correlation_id\": \"e2e-mdl-001\"\n}"
            },
            "description": "Pre-authorized offer for the mdoc credential with namespace-qualified subject data."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mdl offer created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "const trimBase = (base) => base && base.endsWith('/') ? base.slice(0, -1) : base;",
                  "const rewritePublicUrl = (url, base) => {",
                  "  const publicHost = pm.variables.get('tenantHost');",
                  "  const gatewayBase = trimBase(base || pm.variables.get('tenantGatewayUrl'));",
                  "  const m = String(url || '').match(/^(https?:\\/\\/([^/]+))(.*)$/);",
                  "  if (m && publicHost && m[2] === publicHost && gatewayBase) return gatewayBase + (m[3] || '');",
                  "  return url;",
                  "};",
                  "if (j.offer_uri) {",
                  "  const m = j.offer_uri.match(/credential_offer_uri=([^&]+)/);",
                  "  if (m) pm.collectionVariables.set('mdlCredentialOfferUri', rewritePublicUrl(decodeURIComponent(m[1]), pm.variables.get('tenantGatewayUrl')));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "09 Resolve Mdl offer",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantHost}}"
              }
            ],
            "url": "{{mdlCredentialOfferUri}}",
            "description": "Resolves the Mdl offer and extracts the pre-authorized code."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mdl offer resolved', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('resolved mDL offer carries exact credential configuration id', () => {",
                  "  pm.expect(j.credential_issuer).to.eql(pm.variables.get('tenantGatewayUrl'));",
                  "  pm.expect(j.credential_configuration_ids).to.eql(['Mdl']);",
                  "});",
                  "const grant = j.grants && j.grants['urn:ietf:params:oauth:grant-type:pre-authorized_code'];",
                  "if (grant && grant['pre-authorized_code']) pm.collectionVariables.set('mdlPreAuthCode', grant['pre-authorized_code']);"
                ]
              }
            }
          ]
        },
        {
          "name": "10 Exchange Mdl code for token",
          "request": {
            "method": "POST",
            "url": "{{tokenEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "urn:ietf:params:oauth:grant-type:pre-authorized_code"
                },
                {
                  "key": "pre-authorized_code",
                  "value": "{{mdlPreAuthCode}}"
                }
              ]
            },
            "description": "Token request for the Mdl issuance session."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mdl token issued', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('mDL token response is bound to requested credential configuration', () => {",
                  "  const detail = (j.authorization_details || []).find((entry) => entry.type === 'openid_credential');",
                  "  pm.expect(detail, 'openid credential authorization detail').to.be.an('object');",
                  "  pm.expect(detail.credential_configuration_id).to.eql('Mdl');",
                  "  pm.expect(detail.credential_identifiers || []).to.have.length.greaterThan(0);",
                  "});",
                  "if (j.access_token) pm.collectionVariables.set('walletAccessToken', j.access_token);",
                  "pm.collectionVariables.set('cNonce', j.c_nonce || '');"
                ]
              }
            }
          ]
        },
        {
          "name": "11 Request Mdl credential",
          "request": {
            "method": "POST",
            "url": "{{credentialEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{walletAccessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_configuration_id\": \"Mdl\",\n  \"proofs\": {\n    \"jwt\": [\"{{proofJwt}}\"]\n  }\n}"
            },
            "description": "Credential request for the mdoc. The response carries the base64url-encoded ISO 18013-5 mdoc bound to the wallet key (cose_key binding)."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const b64u = (obj) => CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(obj))).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "const jwk = JSON.parse(pm.collectionVariables.get('walletJwk') || '{}');",
                  "function buildAndSign(nonce) {",
                  "  const header = { alg: 'ES256', typ: 'openid4vci-proof+jwt', jwk: jwk };",
                  "  const payload = { aud: pm.collectionVariables.get('credentialIssuer') || pm.variables.get('tenantGatewayUrl'), iat: Math.floor(Date.now() / 1000), nonce: nonce };",
                  "  const signingInput = b64u(header) + '.' + b64u(payload);",
                  "  const inputB64 = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(signingInput));",
                  "  pm.sendRequest({",
                  "    url: pm.variables.get('tenantKmsApiBaseUrl') + '/signatures/raw/create',",
                  "    method: 'POST',",
                  "    header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + (pm.collectionVariables.get('tenantToken') || pm.variables.get('tenantToken')) },",
                  "    body: { mode: 'raw', raw: JSON.stringify({ keyInfo: { providerId: pm.variables.get('kmsProviderId'), alias: 'wallet-holder' }, input: inputB64 }) }",
                  "  }, (err, res) => {",
                  "    if (err) { console.error('KMS signing failed', err); return; }",
                  "    const j = res.json();",
                  "    const sigB64u = String(j.signature || '').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "    pm.collectionVariables.set('proofJwt', signingInput + '.' + sigB64u);",
                  "  });",
                  "}",
                  "// Nonce: the token response may omit c_nonce; the issuer's nonce endpoint",
                  "// (from metadata) is the authoritative source.",
                  "const tokenNonce = pm.collectionVariables.get('cNonce');",
                  "const nonceEndpoint = pm.collectionVariables.get('nonceEndpoint');",
                  "if (tokenNonce) {",
                  "  buildAndSign(tokenNonce);",
                  "} else if (nonceEndpoint) {",
                  "  pm.sendRequest({ url: nonceEndpoint, method: 'POST', header: { 'Host': pm.variables.get('tenantHost') } }, (err, res) => {",
                  "    if (err) { console.error('nonce fetch failed', err); return; }",
                  "    buildAndSign(res.json().c_nonce);",
                  "  });",
                  "} else {",
                  "  buildAndSign(undefined);",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mdl credential issued', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "const cred = (j.credentials && j.credentials[0] && j.credentials[0].credential) || j.credential;",
                  "pm.test('mDL credential response contains an opaque mdoc payload', () => pm.expect(cred).to.be.a('string').and.not.empty);",
                  "if (cred) pm.collectionVariables.set('mdlCredential', cred);"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "12 Issue Credentials Pipeline",
      "description": "Exercises the pipeline issuance API where attributes are contributed into a session before approval releases issuance.",
      "item": [
        {
          "name": "01 Initialize pipeline session",
          "request": {
            "method": "POST",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/sessions",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"pipeline_configuration\": {\n    \"pipelineId\": \"eupid-registration\",\n    \"claimsBindings\": [\n      {\n        \"id\": \"EuPid\",\n        \"semanticAttributeSetRef\": {\n          \"bundleId\": \"credential-config:EuPid\"\n        },\n        \"deferralPolicy\": {\n          \"approvalRequired\": true\n        }\n      }\n    ]\n  },\n  \"correlation_id\": \"e2e-pipeline-001\",\n  \"ttl_seconds\": 600\n}"
            },
            "description": "Opens an attribute pipeline session keyed by correlation id, seeded with the lookup keys that attribute sources use to find the subject."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('pipeline session opened', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Contribute attributes",
          "request": {
            "method": "POST",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/sessions/e2e-pipeline-001/attributes",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"groups\": [\n    {\n      \"contributorId\": \"registration-office\",\n      \"phase\": \"oid4vci_credential_request\",\n      \"timestamp\": \"2026-06-01T00:00:00Z\",\n      \"attributes\": [\n        {\n          \"path\": \"family_name\",\n          \"value\": \"Mustermann\",\n          \"verified\": true\n        },\n        {\n          \"path\": \"given_name\",\n          \"value\": \"Erika\",\n          \"verified\": true\n        },\n        {\n          \"path\": \"birth_date\",\n          \"value\": \"1964-08-12\",\n          \"verified\": true\n        }\n      ]\n    }\n  ]\n}"
            },
            "description": "An attribute source contributes verified claims into the session. Multiple sources can contribute; priorities resolve conflicts."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('attributes contributed', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Read accumulated attributes",
          "request": {
            "method": "GET",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/sessions/e2e-pipeline-001/attributes",
            "description": "Reads the attributes accumulated so far across all sources.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('attributes returned', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Evaluate completeness",
          "request": {
            "method": "GET",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/sessions/e2e-pipeline-001/completeness",
            "description": "Evaluates whether the accumulated attributes satisfy each credential binding's mandatory claims, and whether deferral or approval is recommended.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('completeness evaluated', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Approve issuance",
          "request": {
            "method": "POST",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/sessions/e2e-pipeline-001/approve",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"decision\": \"APPROVE\",\n  \"reason\": \"Registration office data verified\"\n}"
            },
            "description": "Releases the approval gate. After approval, issuance proceeds with the pipeline-collected attributes instead of inline subject data."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('issuance approved', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "13 DCQL Queries",
      "description": "Creates and lists the DCQL query definitions used by the verifier, including a combined query that requests EuPid and Mdl together.",
      "item": [
        {
          "name": "01 Create EuPid query",
          "request": {
            "method": "POST",
            "url": "{{tenantDcqlApiBaseUrl}}/queries",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"queryId\": \"eupid-sdjwt\",\n  \"name\": \"EuPid identity check\",\n  \"description\": \"Requests name and age attestation from the EU Personal ID\",\n  \"enabled\": true,\n  \"dcqlQuery\": {\n    \"credentials\": [\n      {\n        \"id\": \"eupid\",\n        \"format\": \"dc+sd-jwt\",\n        \"meta\": {\n          \"vct_values\": [\"{{tenantGatewayUrl}}/public/schema/vct/EuPid\"]\n        },\n        \"claims\": [\n          { \"path\": [\"family_name\"] },\n          { \"path\": [\"given_name\"] },\n          { \"path\": [\"age_over_18\"] }\n        ]\n      }\n    ]\n  }\n}"
            },
            "description": "Selects only family_name, given_name, and age_over_18 from the EuPid. The other claims stay undisclosed."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('eupid query stored or already provisioned', () => pm.expect([200, 201, 409]).to.include(pm.response.code));",
                  "const q = pm.response.json();",
                  "const alreadyExists = pm.response.code === 409;",
                  "if (alreadyExists) {",
                  "  pm.test('EuPid DCQL query is already provisioned', () => {",
                  "    pm.expect(q.error.code).to.eql('ALREADY_EXISTS_ERROR');",
                  "    pm.expect(q.error.message).to.include('eupid-sdjwt');",
                  "  });",
                  "} else {",
                  "pm.test('EuPid DCQL query content is versioned and enabled', () => {",
                  "  pm.expect(q.queryId).to.eql('eupid-sdjwt');",
                  "  pm.expect(q.enabled).to.eql(true);",
                  "  pm.expect(q.currentVersion).to.eql(1);",
                  "  const credential = q.dcqlQuery.credentials[0];",
                  "  pm.expect(credential.format).to.eql('dc+sd-jwt');",
                  "  pm.expect(credential.meta.vct_values).to.eql([pm.variables.get('tenantGatewayUrl') + '/public/schema/vct/EuPid']);",
                  "});",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Create Mdl query",
          "request": {
            "method": "POST",
            "url": "{{tenantDcqlApiBaseUrl}}/queries",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"queryId\": \"mdl-mdoc\",\n  \"name\": \"Driving licence check\",\n  \"description\": \"Requests driving privileges from the mobile driving licence\",\n  \"enabled\": true,\n  \"dcqlQuery\": {\n    \"credentials\": [\n      {\n        \"id\": \"mdl\",\n        \"format\": \"mso_mdoc\",\n        \"meta\": {\n          \"doctype_value\": \"org.iso.18013.5.1.mDL\"\n        },\n        \"claims\": [\n          { \"path\": [\"org.iso.18013.5.1\", \"family_name\"] },\n          { \"path\": [\"org.iso.18013.5.1\", \"driving_privileges\"] }\n        ]\n      }\n    ]\n  }\n}"
            },
            "description": "Selects only family_name and driving_privileges from the ISO 18013-5 namespace."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mdl query stored or already provisioned', () => pm.expect([200, 201, 409]).to.include(pm.response.code));",
                  "const q = pm.response.json();",
                  "const alreadyExists = pm.response.code === 409;",
                  "if (alreadyExists) {",
                  "  pm.test('mDL DCQL query is already provisioned', () => {",
                  "    pm.expect(q.error.code).to.eql('ALREADY_EXISTS_ERROR');",
                  "    pm.expect(q.error.message).to.include('mdl-mdoc');",
                  "  });",
                  "} else {",
                  "pm.test('mDL DCQL query content is versioned and enabled', () => {",
                  "  pm.expect(q.queryId).to.eql('mdl-mdoc');",
                  "  pm.expect(q.enabled).to.eql(true);",
                  "  pm.expect(q.currentVersion).to.eql(1);",
                  "  const credential = q.dcqlQuery.credentials[0];",
                  "  pm.expect(credential.format).to.eql('mso_mdoc');",
                  "  pm.expect(credential.meta.doctype_value).to.eql('org.iso.18013.5.1.mDL');",
                  "});",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Create combined query",
          "request": {
            "method": "POST",
            "url": "{{tenantDcqlApiBaseUrl}}/queries",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"queryId\": \"eupid-and-mdl\",\n  \"name\": \"Identity and driving licence\",\n  \"description\": \"Requests the EuPid and the mobile driving licence in one presentation\",\n  \"enabled\": true,\n  \"dcqlQuery\": {\n    \"credentials\": [\n      {\n        \"id\": \"eupid\",\n        \"format\": \"dc+sd-jwt\",\n        \"meta\": {\n          \"vct_values\": [\"{{tenantGatewayUrl}}/public/schema/vct/EuPid\"]\n        },\n        \"claims\": [\n          { \"path\": [\"family_name\"] },\n          { \"path\": [\"given_name\"] }\n        ]\n      },\n      {\n        \"id\": \"mdl\",\n        \"format\": \"mso_mdoc\",\n        \"meta\": {\n          \"doctype_value\": \"org.iso.18013.5.1.mDL\"\n        },\n        \"claims\": [\n          { \"path\": [\"org.iso.18013.5.1\", \"driving_privileges\"] }\n        ]\n      }\n    ]\n  }\n}"
            },
            "description": "Requests both credentials in a single presentation, each with its own claim selection."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('combined query stored or already provisioned', () => pm.expect([200, 201, 409]).to.include(pm.response.code));",
                  "const q = pm.response.json();",
                  "const alreadyExists = pm.response.code === 409;",
                  "if (alreadyExists) {",
                  "  pm.test('combined DCQL query is already provisioned', () => {",
                  "    pm.expect(q.error.code).to.eql('ALREADY_EXISTS_ERROR');",
                  "    pm.expect(q.error.message).to.include('eupid-and-mdl');",
                  "  });",
                  "} else {",
                  "pm.test('combined DCQL query contains EuPid and mDL credentials', () => {",
                  "  pm.expect(q.queryId).to.eql('eupid-and-mdl');",
                  "  pm.expect(q.enabled).to.eql(true);",
                  "  pm.expect(q.currentVersion).to.eql(1);",
                  "  const byId = Object.fromEntries(q.dcqlQuery.credentials.map((credential) => [credential.id, credential]));",
                  "  pm.expect(byId.eupid.format).to.eql('dc+sd-jwt');",
                  "  pm.expect(byId.eupid.meta.vct_values).to.eql([pm.variables.get('tenantGatewayUrl') + '/public/schema/vct/EuPid']);",
                  "  pm.expect(byId.mdl.format).to.eql('mso_mdoc');",
                  "  pm.expect(byId.mdl.meta.doctype_value).to.eql('org.iso.18013.5.1.mDL');",
                  "});",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "04 List queries",
          "request": {
            "method": "GET",
            "url": "{{tenantDcqlApiBaseUrl}}/queries",
            "description": "Lists the tenant's stored DCQL query configurations.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('queries listed', () => pm.response.to.have.status(200));",
                  "const queries = pm.response.json();",
                  "const byId = Object.fromEntries(queries.map((query) => [query.queryId, query]));",
                  "pm.test('query list includes exact enabled EuPid, mDL, and combined definitions', () => {",
                  "  ['eupid-sdjwt', 'mdl-mdoc', 'eupid-and-mdl'].forEach((id) => pm.expect(byId[id], id).to.be.an('object'));",
                  "  pm.expect(byId['eupid-sdjwt'].enabled).to.eql(true);",
                  "  pm.expect(byId['eupid-sdjwt'].dcqlQuery.credentials[0].format).to.eql('dc+sd-jwt');",
                  "  pm.expect(byId['eupid-sdjwt'].dcqlQuery.credentials[0].meta.vct_values).to.eql([pm.variables.get('tenantGatewayUrl') + '/public/schema/vct/EuPid']);",
                  "  pm.expect(byId['mdl-mdoc'].enabled).to.eql(true);",
                  "  pm.expect(byId['mdl-mdoc'].dcqlQuery.credentials[0].format).to.eql('mso_mdoc');",
                  "  pm.expect(byId['mdl-mdoc'].dcqlQuery.credentials[0].meta.doctype_value).to.eql('org.iso.18013.5.1.mDL');",
                  "  pm.expect(byId['eupid-and-mdl'].enabled).to.eql(true);",
                  "  pm.expect(byId['eupid-and-mdl'].dcqlQuery.credentials.map((credential) => credential.id).sort()).to.eql(['eupid', 'mdl']);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "05 List combined query versions",
          "request": {
            "method": "GET",
            "url": "{{tenantDcqlApiBaseUrl}}/queries/eupid-and-mdl/versions",
            "description": "Lists version history for the combined DCQL query. This exercises the versioned DCQL store packaged in the verifier image.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('query versions listed', () => pm.response.to.have.status(200));",
                  "const versions = pm.response.json();",
                  "pm.test('combined query version history starts at version 1', () => {",
                  "  pm.expect(Array.isArray(versions) ? versions.length : 0).to.be.greaterThan(0);",
                  "  pm.expect(versions[0].version).to.eql(1);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "06 Bind combined query to verifier",
          "request": {
            "method": "POST",
            "url": "{{tenantDcqlApiBaseUrl}}/verifiers/{{verifierId}}/bindings",
            "description": "Binds the stored combined DCQL query to the tenant verifier instance. Verification requests below pass verifier_id so the verifier resolves the query through this instance binding.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"queryId\": \"eupid-and-mdl\",\n  \"version\": 1\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('combined query bound or already provisioned', () => pm.expect([200, 201, 409]).to.include(pm.response.code));",
                  "const binding = pm.response.json();",
                  "const alreadyExists = pm.response.code === 409;",
                  "if (alreadyExists) {",
                  "  pm.test('combined query binding is already provisioned', () => {",
                  "    pm.expect(binding.error.code).to.eql('ALREADY_EXISTS_ERROR');",
                  "    pm.expect(binding.error.message).to.include('eupid-and-mdl');",
                  "  });",
                  "} else {",
                  "if (binding.id) pm.collectionVariables.set('combinedDcqlBindingId', binding.id);",
                  "pm.test('combined query binding pins version 1 on the tenant verifier', () => {",
                  "  pm.expect(binding.verifierId).to.eql(pm.variables.get('verifierId'));",
                  "  pm.expect(binding.queryId).to.eql('eupid-and-mdl');",
                  "  pm.expect(binding.pinnedVersion).to.eql(1);",
                  "  pm.expect(binding.enabled).to.eql(true);",
                  "});",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "07 List verifier DCQL bindings",
          "request": {
            "method": "GET",
            "url": "{{tenantDcqlApiBaseUrl}}/verifiers/{{verifierId}}/bindings",
            "description": "Lists the verifier instance bindings and proves the combined query is available to the verifier before verification starts.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('verifier bindings listed', () => pm.response.to.have.status(200));",
                  "const bindings = pm.response.json();",
                  "const binding = bindings.find((b) => b.queryId === 'eupid-and-mdl');",
                  "pm.test('verifier binding list includes the combined query', () => {",
                  "  pm.expect(binding, 'combined binding').to.be.an('object');",
                  "  pm.expect(binding.verifierId).to.eql(pm.variables.get('verifierId'));",
                  "  pm.expect(binding.pinnedVersion).to.eql(1);",
                  "  pm.expect(binding.enabled).to.eql(true);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "08 List combined query verifier bindings",
          "request": {
            "method": "GET",
            "url": "{{tenantDcqlApiBaseUrl}}/queries/eupid-and-mdl/verifiers",
            "description": "Reverse-lists verifier bindings for the combined query, proving the query-to-verifier link is visible from the query resource as well.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('combined query verifier bindings listed', () => pm.response.to.have.status(200));",
                  "const bindings = pm.response.json();",
                  "const binding = bindings.find((b) => b.verifierId === pm.variables.get('verifierId') && b.queryId === 'eupid-and-mdl');",
                  "pm.test('combined query is bound to the tenant verifier', () => {",
                  "  pm.expect(binding, 'combined query verifier binding').to.be.an('object');",
                  "  pm.expect(binding.pinnedVersion).to.eql(1);",
                  "  pm.expect(binding.enabled).to.eql(true);",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "14 Verification",
      "description": "Creates, polls, and cancels a verification session for the stored combined DCQL query. The collection covers verifier session lifecycle without simulating a full wallet presentation.",
      "item": [
        {
          "name": "01 Create verification request",
          "request": {
            "method": "POST",
            "url": "{{tenantVerifierBackendBaseUrl}}/auth/requests",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"query_id\": \"eupid-and-mdl\",\n  \"verifier_id\": \"{{verifierId}}\",\n  \"client_id\": \"redirect_uri:{{tenantGatewayUrl}}/callback\",\n  \"correlation_id\": \"e2e-verify-001\"\n}"
            },
            "description": "Creates a verifier session for the combined DCQL query. The response includes the authorization request URI a wallet would open."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('verification session created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.authorization_request_uri) pm.collectionVariables.set('authorizationRequestUri', j.authorization_request_uri);",
                  "pm.collectionVariables.set('verifyCorrelationId', j.correlation_id || 'e2e-verify-001');"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Poll verification status",
          "request": {
            "method": "GET",
            "url": "{{tenantVerifierBackendBaseUrl}}/auth/requests/{{verifyCorrelationId}}",
            "description": "Polls the session. With no wallet attached, the session stays in authorization_request_created; after a wallet presents, it ends in authorization_response_verified with the disclosed claims.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status returned', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Cancel verification session",
          "request": {
            "method": "DELETE",
            "url": "{{tenantVerifierBackendBaseUrl}}/auth/requests/{{verifyCorrelationId}}",
            "description": "Cleans up the verification session.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('session removed', () => pm.expect([200, 204]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "15 Authorization Code Offer",
      "description": "Creates an OID4VCI offer that uses the authorization code grant and fetches authorization server metadata for the wallet flow.",
      "item": [
        {
          "name": "01 Create offer with authorization code grant",
          "request": {
            "method": "POST",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/credential/offers",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              },
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_configuration_ids\": [\"EuPid\"],\n  \"grants\": {\n    \"authorization_code\": {\n      \"issuer_state\": \"e2e-authcode-001\"\n    }\n  },\n  \"correlation_id\": \"e2e-authcode-001\"\n}"
            },
            "description": "Creates a credential offer that sends the wallet through the OAuth2 authorization code flow with PKCE before credential issuance."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('auth-code offer created', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Fetch authorization server metadata",
          "request": {
            "method": "GET",
            "url": "{{tenantAuthorizationServerOrigin}}/.well-known/oauth-authorization-server",
            "description": "Authorization server metadata the wallet uses to drive the flow: authorization endpoint, token endpoint, supported grants, and PKCE methods.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantHost}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('as metadata served', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    }
  ],
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "const setPublic = (key, value) => {",
          "  if (value !== undefined && value !== null && String(value).trim()) {",
          "    const normalized = String(value).trim().replace(/\\/$/, '');",
          "    pm.collectionVariables.set(key, normalized);",
          "    pm.environment.set(key, normalized);",
          "  }",
          "};",
          "const setRuntimeDefault = (key, value) => {",
          "  const runtimeResolved = pm.collectionVariables.get('tenantRuntimeServicesResolved') === 'true';",
          "  if (!runtimeResolved || !String(pm.collectionVariables.get(key) || '').trim()) setPublic(key, value);",
          "};",
          "const didWebDocumentUrl = (did, gatewayUrl) => {",
          "  const prefix = 'did:web:';",
          "  if (!String(did || '').startsWith(prefix)) return '';",
          "  const parts = String(did).slice(prefix.length).split(':').map((part) => decodeURIComponent(part));",
          "  const didHost = parts.shift();",
          "  if (!didHost) return '';",
          "  let scheme = 'https';",
          "  let gatewayAuthority = '';",
          "  const gatewayMatch = String(gatewayUrl || '').match(/^([a-z][a-z0-9+.-]*):\\/\\/([^/?#]+)/i);",
          "  if (gatewayMatch) {",
          "    scheme = gatewayMatch[1].toLowerCase();",
          "    gatewayAuthority = gatewayMatch[2];",
          "  }",
          "  const gatewayHostname = gatewayAuthority.replace(/:\\d+$/, '');",
          "  const didHostname = didHost.replace(/:\\d+$/, '');",
          "  const useGatewayAuthority = gatewayHostname === didHostname || gatewayAuthority === didHost;",
          "  const authority = useGatewayAuthority ? gatewayAuthority : didHost;",
          "  const outputScheme = useGatewayAuthority ? scheme : 'https';",
          "  const documentPath = parts.length ? '/' + parts.map((part) => encodeURIComponent(part)).join('/') + '/did.json' : '/.well-known/did.json';",
          "  return outputScheme + '://' + authority + documentPath;",
          "};",
          "const configuredPlatformUrl = String(pm.environment.get('platformUrl') || pm.collectionVariables.get('platformUrl') || '').trim().replace(/\\/$/, '');",
          "let tenantGatewayUrl = String(pm.environment.get('tenantGatewayUrl') || '').trim().replace(/\\/$/, '');",
          "let publicScheme = 'https';",
          "let publicPort = '';",
          "const platformUrlParts = configuredPlatformUrl.match(/^([a-z][a-z0-9+.-]*):\\/\\/([^/?#]+)/i);",
          "if (platformUrlParts) {",
          "  const scheme = platformUrlParts[1].toLowerCase();",
          "  if (scheme === 'http' || scheme === 'https') publicScheme = scheme;",
          "  const portMatch = platformUrlParts[2].match(/:(\\d+)$/);",
          "  if (portMatch) publicPort = ':' + portMatch[1];",
          "}",
          "let baseDomain = String(pm.environment.get('baseDomain') || pm.collectionVariables.get('baseDomain') || '').trim().replace(/^https?:\\/\\//, '').replace(/\\/$/, '');",
          "if (baseDomain) {",
          "  const basePortMatch = baseDomain.match(/:(\\d+)$/);",
          "  if (!publicPort && basePortMatch) publicPort = ':' + basePortMatch[1];",
          "  baseDomain = baseDomain.replace(/:\\d+$/, '');",
          "}",
          "const tenantSlug = String(pm.environment.get('tenantSlug') || pm.collectionVariables.get('tenantSlug') || 'acme').trim();",
          "const tenantName = String(pm.environment.get('tenantName') || pm.collectionVariables.get('tenantName') || 'Acme Corporation').trim();",
          "setPublic('tenantSlug', tenantSlug);",
          "setPublic('tenantName', tenantName);",
          "setPublic('kmsProviderId', tenantSlug);",
          "const statusListSigningKeyAliasOverride = String(pm.environment.get('statusListSigningKeyAliasOverride') || pm.collectionVariables.get('statusListSigningKeyAliasOverride') || '').trim();",
          "const statusListSigningKeyAlias = String(pm.environment.get('statusListSigningKeyAlias') || pm.collectionVariables.get('statusListSigningKeyAlias') || '').trim();",
          "const defaultStatusListSigningKeyAlias = 'issuer-signing-' + tenantSlug;",
          "const usesManagedStatusListSigningAlias = !statusListSigningKeyAlias || statusListSigningKeyAlias.includes('{{tenantSlug}}') || /^issuer-signing-[A-Za-z0-9._-]+$/.test(statusListSigningKeyAlias);",
          "setPublic('statusListSigningKeyAlias', statusListSigningKeyAliasOverride || (usesManagedStatusListSigningAlias ? defaultStatusListSigningKeyAlias : statusListSigningKeyAlias));",
          "if (baseDomain) {",
          "  const tenantHost = tenantSlug + '.' + baseDomain;",
          "  const derivedTenantGatewayUrl = publicScheme + '://' + tenantHost + publicPort;",
          "  const platformUrl = publicScheme + '://platform.' + baseDomain + publicPort;",
          "  tenantGatewayUrl = derivedTenantGatewayUrl;",
          "  setPublic('tenantHost', tenantHost);",
          "  setPublic('platformUrl', platformUrl);",
          "  setPublic('adminConsoleUrl', platformUrl + '/admin-console');",
          "  setPublic('operatorRedirectUri', platformUrl + '/admin-console/callback');",
          "}",
          "if (tenantGatewayUrl) {",
          "  setPublic('tenantGatewayUrl', tenantGatewayUrl);",
          "  setPublic('tenantIssuerOrigin', tenantGatewayUrl);",
          "  setPublic('tenantVerifierOrigin', tenantGatewayUrl);",
          "  setPublic('tenantAuthorizationServerOrigin', tenantGatewayUrl);",
          "  setRuntimeDefault('tenantKmsApiBaseUrl', tenantGatewayUrl + '/api/kms/v1');",
          "  setRuntimeDefault('tenantDidApiBaseUrl', tenantGatewayUrl + '/api/did/v1');",
          "  setRuntimeDefault('tenantCredentialDesignApiBaseUrl', tenantGatewayUrl + '/api/credential-design/v1');",
          "  setRuntimeDefault('tenantStatusListApiBaseUrl', tenantGatewayUrl + '/api/statuslist/v1');",
          "  setRuntimeDefault('tenantIssuerApiBaseUrl', tenantGatewayUrl + '/api/oid4vci/v1');",
          "  setRuntimeDefault('tenantDcqlApiBaseUrl', tenantGatewayUrl + '/api/dcql/v1');",
          "  setRuntimeDefault('tenantVerifierBackendBaseUrl', tenantGatewayUrl + '/oid4vp/backend');",
          "  const hostMatch = tenantGatewayUrl.match(/^[a-z][a-z0-9+.-]*:\\/\\/([^/?#]+)/i);",
          "  if (hostMatch && !pm.collectionVariables.get('tenantHost')) setPublic('tenantHost', hostMatch[1].replace(/:\\d+$/, ''));",
          "}",
          "for (const key of ['platformUrl', 'adminConsoleUrl', 'operatorRedirectUri', 'tenantGatewayUrl', 'tenantHost']) {",
          "  setPublic(key, pm.environment.get(key));",
          "}",
          "if (!pm.collectionVariables.get('operatorRedirectUri') && pm.collectionVariables.get('platformUrl')) setPublic('operatorRedirectUri', pm.collectionVariables.get('platformUrl') + '/admin-console/callback');",
          "if (pm.collectionVariables.get('tenantHost')) {",
          "  const didHost = String(pm.collectionVariables.get('tenantHost')).replace(/:\\d+$/, '');",
          "  const did = 'did:web:' + didHost.replace(/:/g, '%3A');",
          "  setPublic('did', did);",
          "  setPublic('didEncoded', encodeURIComponent(did));",
          "  setPublic('didJsonUrl', didWebDocumentUrl(did, pm.collectionVariables.get('tenantGatewayUrl')));",
          "}"
        ]
      }
    }
  ]
}
