{
  "info": {
    "name": "EDK-Enterprise-Deployment",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "description": "Developer credential journey for an EDK enterprise deployment. Read the cross-product guide at https://docs.sphereon.com/edk/guides/developer-credential-journey before running. The numbered folders are the runnable correlation points: 01 platform authentication, 02 tenant provisioning, 03 one-time tenant bootstrap, 04 confidential-client tenant token, 11 designs, 12 status lists, 13 issuer configurations, 15-20 issuance, 21 DCQL, 22 verification, and 23 trust domains. The collection uses platformAccessToken only for platform administration, tenantAccessToken only for tenant administration/runtime APIs, and walletAccessToken only for issuance credential requests. URL inputs are baseDomain, platformUrl, and tenantSubdomain; all tenant routes are derived from them."
  },
  "variable": [
    {
      "key": "operatorPassword",
      "value": "",
      "type": "secret",
      "description": "Password of the platform operator. Supplied by the environment and never returned by an API."
    },
    {
      "key": "operatorEmail",
      "value": "",
      "type": "string",
      "description": "Email of the platform operator who signs in through the hosted login form. Supplied by the environment."
    },
    {
      "key": "operatorCodeVerifier",
      "value": "",
      "type": "string",
      "description": "PKCE verifier for the operator authorization-code flow. Generated per run by 02 / 01 and cleared after the token exchange."
    },
    {
      "key": "tenantOwnerCodeVerifier",
      "value": "",
      "type": "secret",
      "description": "PKCE verifier for the tenant-owner authorization-code flow. Generated per run by 04 / 03 and cleared after the token exchange."
    },
    {
      "key": "tenantOwnerPassword",
      "value": "replace-before-running",
      "type": "secret",
      "description": "Password selected through the single-use owner activation action. It is write-only and is never returned by an API."
    },
    {
      "key": "kmsResourceHandle",
      "value": "",
      "type": "string",
      "description": "Opaque tenant KMS resource handle (krh_...) discovered from the typed KMS resource list. This is a platform management/setup identifier only; tenant runtime KMS REST operations use providerId plus the provider-native key alias."
    },
    {
      "key": "statusListDidKmsResourceHandle",
      "value": "",
      "type": "string",
      "description": "Exact KMS resource selected by the provisioned EuPid credential configuration."
    },
    {
      "key": "statusListDidKmsKeyAlias",
      "value": "",
      "type": "string",
      "description": "Exact KMS key selected by the provisioned EuPid credential configuration."
    },
    {
      "key": "statusListDidVerificationMethodId",
      "value": "",
      "type": "string",
      "description": "Exact DID assertionMethod selected by the provisioned EuPid credential configuration."
    },
    {
      "key": "statusListX509KmsResourceHandle",
      "value": "",
      "type": "string",
      "description": "Exact KMS resource selected by the provisioned mDL credential configuration."
    },
    {
      "key": "statusListX509KmsKeyAlias",
      "value": "",
      "type": "string",
      "description": "Exact X.509-backed KMS key selected by the provisioned mDL credential configuration."
    },
    {
      "key": "kmsResourceProviderId",
      "value": "",
      "type": "string",
      "description": "Provider id of the tenant setup KMS resource, read from the typed KMS resource list. The runtime KMS REST API addresses the same KMS by this id, and the admin console joins the two planes on it."
    },
    {
      "key": "verifierId",
      "value": "",
      "type": "string",
      "description": "Resolved from the platform OID4VP verifier instance list after tenant onboarding."
    },
    {
      "key": "issuerId",
      "value": ""
    },
    {
      "key": "verifierInstanceId",
      "value": "",
      "description": "Runtime verifier instance id resolved separately from the verifier party UUID."
    },
    {
      "key": "issuerTestingPublicBaseUrl",
      "value": ""
    },
    {
      "key": "verifierTestingPublicBaseUrl",
      "value": ""
    },
    {
      "key": "issuerTestingDisplayUrl",
      "value": ""
    },
    {
      "key": "verifierTestingDisplayUrl",
      "value": ""
    },
    {
      "key": "platformTenantId",
      "value": "",
      "type": "string",
      "description": "Platform tenant the operator token binds to. KMS sharing is authored only by the tenant that owns the KMS."
    },
    {
      "key": "tenantKmsApiBaseUrl",
      "value": "",
      "type": "string",
      "description": "Tenant KMS API base, <tenantGatewayUrl>/api/kms/v1. Derived before every request and kept in collection scope."
    },
    {
      "key": "externalAwsKmsProviderId",
      "value": "replace-with-authorized-aws-provider-id",
      "type": "string",
      "description": "Customer-chosen tenant provider id for the optional AWS KMS examples. The provider must already be active and authorized for this tenant."
    },
    {
      "key": "externalAzureKmsProviderId",
      "value": "replace-with-authorized-azure-provider-id",
      "type": "string",
      "description": "Customer-chosen tenant provider id for the optional Azure Key Vault example. The provider must already be active and authorized for this tenant."
    },
    {
      "key": "externalKeyAlias",
      "value": "customer-external-key",
      "type": "string",
      "description": "Exact provider-native alias of the existing external key. EDK stores it unchanged in this tenant's reference index; it is not a private key or secret handle. For a platform-shared AWS or Azure provider, the cloud object must have sphereon-tenant-id set to this collection's tenantId before registration."
    },
    {
      "key": "externalKeyKid",
      "value": "replace-with-provider-canonical-kid",
      "type": "string",
      "description": "Optional provider-canonical kid for the external key. The alias-only example omits this field; when supplied, the service verifies that alias and kid identify the same key."
    },
    {
      "key": "externalAwsCertificateAlias",
      "value": "customer-aws-certificate-chain",
      "type": "string",
      "description": "Customer-chosen alias for the public certificate chain linked to the AWS KMS key."
    },
    {
      "key": "externalAwsCertificateDerBase64",
      "value": "replace-with-base64-der-certificate",
      "type": "string",
      "description": "Public leaf-to-root DER certificate material encoded as Base64. Replace the placeholder with public bytes only; never paste a private key or credential."
    },
    {
      "key": "externalAzureCertificateAlias",
      "value": "customer-azure-leaf-certificate",
      "type": "string",
      "description": "Exact existing Azure Key Vault alias of the provider-native leaf certificate. EDK stores it unchanged in this tenant's certificate reference. For a platform-shared provider, the Azure certificate must have sphereon-tenant-id set to this collection's tenantId."
    },
    {
      "key": "externalAzureProviderCertificateId",
      "value": "replace-with-azure-certificate-id-or-version",
      "type": "string",
      "description": "Optional provider-native Azure certificate identifier or immutable version used by the leaf read."
    },
    {
      "key": "authorizationServerSlug",
      "value": "walkthrough-as"
    },
    {
      "key": "externalAuthorizationServerSlug",
      "value": "platform-upstream"
    },
    {
      "key": "hostedAuthorizationServerId",
      "value": ""
    },
    {
      "key": "hostedAuthorizationServerRevision",
      "value": ""
    },
    {
      "key": "hostedAuthorizationServerStaleRevision",
      "value": ""
    },
    {
      "key": "externalAuthorizationServerId",
      "value": ""
    },
    {
      "key": "externalAuthorizationServerRevision",
      "value": ""
    },
    {
      "key": "publicHostedClientId",
      "value": "walkthrough-public-client"
    },
    {
      "key": "confidentialHostedClientId",
      "value": "walkthrough-confidential-client"
    },
    {
      "key": "hostedClientSecret",
      "value": ""
    },
    {
      "key": "authorizationServerIdentityId",
      "value": ""
    },
    {
      "key": "federationBindingId",
      "value": ""
    },
    {
      "key": "federationBindingRevision",
      "value": ""
    },
    {
      "key": "federationClientId",
      "value": "walkthrough-federation-client"
    },
    {
      "key": "federationClientSecret",
      "value": ""
    },
    {
      "key": "issuerAuthorizationServerBindingId",
      "value": ""
    },
    {
      "key": "issuerAuthorizationServerBindingRevision",
      "value": ""
    },
    {
      "key": "oid4vciTargetProfile",
      "value": "OID4VCI_1_1_DRAFT_2A1F0513"
    },
    {
      "key": "oid4vciProfileRevision",
      "value": ""
    },
    {
      "key": "oid4vciProfileUpgradeIdempotencyKey",
      "value": "00000000-0000-4000-8000-000000000101"
    },
    {
      "key": "credentialAuthorizationServerOverrideRevision",
      "value": ""
    },
    {
      "key": "baseDomain",
      "value": "",
      "type": "string",
      "description": "Base domain of the installation, optionally with :port. Supplied by the environment; every URL derives from it."
    },
    {
      "key": "platformUrl",
      "value": "",
      "type": "string",
      "description": "Platform origin, https://platform.<baseDomain>. Derived before every request."
    },
    {
      "key": "tenantGatewayUrl",
      "value": "",
      "type": "string",
      "description": "Tenant origin, https://<tenantSubdomain>.<baseDomain>. Derived before every request."
    },
    {
      "key": "tenantSubdomain",
      "value": "",
      "type": "string",
      "description": "Tenant subdomain this run registers and works in. Supplied by the environment."
    },
    {
      "key": "tenantName",
      "value": "",
      "type": "string",
      "description": "Display name of the tenant this run registers. Supplied by the environment."
    },
    {
      "key": "tenantServiceClientId",
      "value": "",
      "type": "string",
      "description": "Client id of the confidential tenant service client. Defaults to <tenantSubdomain>-service; an environment value overrides it."
    },
    {
      "key": "tenantServiceClientSecret",
      "value": "",
      "type": "secret",
      "description": "Write-once secret of the tenant service client. Generated by 04 / 07b unless the environment supplies one; never returned by an API."
    },
    {
      "key": "subTenantId",
      "value": "",
      "description": "Tenant id of the acme-eu subtenant registered under the tenant of folder 03. Set by folder 03b; the children listings and the second registration address it."
    },
    {
      "key": "subSubTenantId",
      "value": "",
      "description": "Tenant id of the acme-eu-hr sub-subtenant registered under acme-eu. Set by folder 03b; its own authorization server, issuer and credential configuration are addressed with it."
    },
    {
      "key": "kmsTenantKeyAlias",
      "value": ""
    },
    {
      "key": "kmsTenantCertificateAlias",
      "value": ""
    },
    {
      "key": "kmsTenantKeyChain",
      "value": ""
    },
    {
      "key": "kmsTenantCertificateReferenceId",
      "value": ""
    },
    {
      "key": "brandingAssetsHosted",
      "value": "",
      "description": "Run state: 'true' once hosted branding assets were observed. Set by 10 / 01."
    },
    {
      "key": "tenantAzureProviderId",
      "value": "customer-azure-vault",
      "description": "Provider id you choose for your Azure Key Vault on the KMS runtime API. Lowercase letters, digits and hyphens."
    },
    {
      "key": "tenantAzureDisplayName",
      "value": "Customer Azure Key Vault",
      "description": "Display name of your Azure Key Vault KMS resource."
    },
    {
      "key": "tenantAzureVaultUri",
      "value": "replace-with-https://<vault-name>.vault.azure.net",
      "description": "HTTPS URI of your Key Vault."
    },
    {
      "key": "tenantAzureApplicationId",
      "value": "edk-signing",
      "description": "Label EDK stamps on keys it generates in your vault."
    },
    {
      "key": "tenantAzureTenantId",
      "value": "replace-with-entra-tenant-id",
      "description": "Entra tenant id of the application EDK signs in with."
    },
    {
      "key": "tenantAzureClientId",
      "value": "replace-with-entra-client-id",
      "description": "Client id of the Entra application EDK signs in with."
    },
    {
      "key": "tenantAzureClientSecret",
      "value": "replace-with-entra-client-secret",
      "description": "Client secret of that application. Written once by 07 / 01 / 02 and never returned by an API."
    },
    {
      "key": "tenantAzureResourceHandle",
      "value": "",
      "description": "Opaque handle of your Azure Key Vault KMS resource. Set by 07 / 01 / 01."
    },
    {
      "key": "tenantAzureResourceVersion",
      "value": "",
      "description": "Resource version used as the compare-and-set precondition of credential writes. Set by 07 / 01 / 01 and advanced by 07 / 01 / 02."
    },
    {
      "key": "tenantAzureCredentialSecretRef",
      "value": "",
      "description": "Reference of the credential slot the platform derived for your vault credential. Set by 07 / 01 / 01."
    },
    {
      "key": "tenantAwsProviderId",
      "value": "customer-aws-kms",
      "description": "Provider id you choose for your AWS KMS on the KMS runtime API."
    },
    {
      "key": "tenantAwsDisplayName",
      "value": "Customer AWS KMS",
      "description": "Display name of your AWS KMS resource."
    },
    {
      "key": "tenantAwsRegion",
      "value": "replace-with-aws-region",
      "description": "AWS region of the KMS endpoint, for example eu-west-1."
    },
    {
      "key": "tenantAwsApplicationId",
      "value": "edk-signing",
      "description": "Label EDK stamps on keys it generates in AWS KMS."
    },
    {
      "key": "tenantAwsSecretAccessKey",
      "value": "replace-with-aws-secret-access-key",
      "description": "Secret access key EDK signs AWS requests with. Written once by 07 / 01 / 05 and never returned by an API."
    },
    {
      "key": "tenantAwsResourceHandle",
      "value": "",
      "description": "Opaque handle of your AWS KMS resource. Set by 07 / 01 / 04."
    },
    {
      "key": "tenantAwsResourceVersion",
      "value": "",
      "description": "Resource version used as the compare-and-set precondition of credential writes. Set by 07 / 01 / 04 and advanced by 07 / 01 / 05."
    },
    {
      "key": "tenantAwsCredentialSecretRef",
      "value": "",
      "description": "Reference of the credential slot the platform derived for your AWS credential. Set by 07 / 01 / 04."
    },
    {
      "key": "tenantCloudKmsProviderId",
      "value": "",
      "description": "Provider id of the cloud KMS resource created last in 07 / 01; 07 / 01 / 07 makes it the tenant default."
    },
    {
      "key": "customerTrustListSourceId",
      "value": "customer-trust-list",
      "description": "Source id you choose for your published TS 119 612 trust list."
    },
    {
      "key": "customerTrustListUrl",
      "value": "https://trust.example.com/trust-list.xml",
      "description": "HTTPS URL of the trust list you publish. Replace with your own; the platform fetches it on validation and refresh."
    },
    {
      "key": "customerTrustListSchemeIdentity",
      "value": "https://trust.example.com/trust-list-scheme",
      "description": "Scheme identity URI your trust list declares."
    },
    {
      "key": "customerTrustListHost",
      "value": "",
      "description": "Host of customerTrustListUrl, used as the egress allow-list. Derived by 23 / 17a."
    },
    {
      "key": "customerLoteSourceId",
      "value": "customer-lote",
      "description": "Source id you choose for your published list of trusted entities."
    },
    {
      "key": "customerLoteUrl",
      "value": "https://trust.example.com/lote.jws",
      "description": "HTTPS URL of the JAdES-signed list of trusted entities you publish. Replace with your own."
    },
    {
      "key": "customerLoteHost",
      "value": "",
      "description": "Host of customerLoteUrl, used as the egress allow-list. Derived by 23 / 17c."
    },
    {
      "key": "customerLoteEtag",
      "value": "",
      "description": "Etag of the LoTE source as last read; present it as If-Match on the next mutation of the source."
    },
    {
      "key": "asIdentityActivationToken",
      "value": "",
      "description": "Captured from the response of '09 Authorization Server Configuration / 19 Create hosted identity'."
    },
    {
      "key": "bitstringStatusListId",
      "value": "",
      "description": "Captured from the response of '12 Status Lists / Bitstring VCDM / 01 Create the bitstring status list'."
    },
    {
      "key": "cwtStatusListId",
      "value": "",
      "description": "Captured from the response of '12 Status Lists / CWT mdoc / 01 Create the CWT token status list'."
    },
    {
      "key": "credentialEndpoint",
      "value": "",
      "description": "Captured from the response of '15 Issue SD-JWT VC and mdoc / 03 Fetch OID4VCI metadata'."
    },
    {
      "key": "credentialIdentifier",
      "value": "",
      "description": "Captured from the response of '15 Issue SD-JWT VC and mdoc / 04 Exchange pre-authorized code for token'."
    },
    {
      "key": "credentialOfferUri",
      "value": "",
      "description": "Captured from the response of '15 Issue SD-JWT VC and mdoc / 01 Create EuPid offer'."
    },
    {
      "key": "cwtStatusListUri",
      "value": "",
      "description": "Captured from the response of '12 Status Lists / CWT mdoc / 01 Create the CWT token status list'."
    },
    {
      "key": "cwtStatusListValidUntil",
      "value": "",
      "type": "string",
      "description": "Expiry the CWT mdoc status list is created with; computed by the create request as one year from the run."
    },
    {
      "key": "did",
      "value": "",
      "description": "Derived before every request from baseDomain and tenantSubdomain."
    },
    {
      "key": "didEncoded",
      "value": "",
      "description": "Derived before every request from baseDomain and tenantSubdomain."
    },
    {
      "key": "didJsonUrl",
      "value": "",
      "description": "Derived before every request from baseDomain and tenantSubdomain."
    },
    {
      "key": "walletAccessToken",
      "value": "",
      "description": "Captured from the response of '17 Issue W3C VCDM 1.1 / 05 Exchange the EmployeeBadge pre-authorized code'."
    },
    {
      "key": "employeeBadgeCredentialEndpoint",
      "value": "",
      "description": "Captured from the response of '17 Issue W3C VCDM 1.1 / 04 Fetch the VCDM 1.1 OID4VCI metadata'."
    },
    {
      "key": "employeeBadgeCredentialIdentifier",
      "value": "",
      "description": "Captured from the response of '17 Issue W3C VCDM 1.1 / 05 Exchange the EmployeeBadge pre-authorized code'."
    },
    {
      "key": "employeeBadgeMetadataUrl",
      "value": "",
      "description": "Captured from the response of '17 Issue W3C VCDM 1.1 / 03 Resolve the EmployeeBadge offer'."
    },
    {
      "key": "employeeBadgeOfferUri",
      "value": "",
      "description": "Captured from the response of '17 Issue W3C VCDM 1.1 / 02 Create the EmployeeBadge offer'."
    },
    {
      "key": "employeeBadgePreAuthCode",
      "value": "",
      "description": "Captured from the response of '17 Issue W3C VCDM 1.1 / 03 Resolve the EmployeeBadge offer'."
    },
    {
      "key": "employeeBadgeStatusIndex",
      "value": "",
      "description": "Captured from the response of '17 Issue W3C VCDM 1.1 / 06 Request the EmployeeBadge credential'."
    },
    {
      "key": "employeeBadgeTokenEndpoint",
      "value": "",
      "description": "Captured from the response of '17 Issue W3C VCDM 1.1 / 04 Fetch the VCDM 1.1 OID4VCI metadata'."
    },
    {
      "key": "eupidDesignId",
      "value": "",
      "description": "Captured from the response of '11 Credential Designs / 01 Create EuPid SD-JWT design'."
    },
    {
      "key": "eupidLogoHashLeaf",
      "value": "",
      "description": "Captured from the response of '14 Hosted Branding Verification / 01 Fetch hosted VCT metadata'."
    },
    {
      "key": "eupidLogoIntegrity",
      "value": "",
      "description": "Captured from the response of '11 Credential Designs / 02 Upload EuPid logo asset'."
    },
    {
      "key": "eupidLogoUri",
      "value": "",
      "description": "Captured from the response of '11 Credential Designs / 02 Upload EuPid logo asset'."
    },
    {
      "key": "eupidVariantEnId",
      "value": "",
      "description": "Captured from the response of '11 Credential Designs / 03 Create EuPid render variant (en)'."
    },
    {
      "key": "eupidVariantNlId",
      "value": "",
      "description": "Captured from the response of '11 Credential Designs / 04 Create EuPid render variant (nl)'."
    },
    {
      "key": "issuerDesignId",
      "value": "",
      "description": "Captured from the response of '10 Issuer Configuration / 01 Create issuer design'."
    },
    {
      "key": "issuerLogoIntegrity",
      "value": "",
      "description": "Captured from the response of '10 Issuer Configuration / 02 Upload issuer logo asset'."
    },
    {
      "key": "issuerLogoUri",
      "value": "",
      "description": "Captured from the response of '10 Issuer Configuration / 02 Upload issuer logo asset'."
    },
    {
      "key": "issuerMetadataUrl",
      "value": "",
      "description": "Captured from the response of '15 Issue SD-JWT VC and mdoc / 02 Resolve credential offer'."
    },
    {
      "key": "issuerVariantId",
      "value": "",
      "description": "Captured from the response of '10 Issuer Configuration / 03 Create issuer render variant'."
    },
    {
      "key": "kmsRuntimeKeyAlias",
      "value": "",
      "description": "Captured from the response of '24 KMS Runtime API / 01 List runtime providers'."
    },
    {
      "key": "kmsRuntimeProviderId",
      "value": "",
      "description": "Captured from the response of '24 KMS Runtime API / 01 List runtime providers'."
    },
    {
      "key": "kmsRuntimeSignature",
      "value": "",
      "description": "Captured from the response of '24 KMS Runtime API / 05 Create raw signature with providerId and alias'."
    },
    {
      "key": "mdlCredentialIdentifier",
      "value": "",
      "description": "Captured from the response of '15 Issue SD-JWT VC and mdoc / 09 Exchange Mdl code for token'."
    },
    {
      "key": "mdlCredentialOfferUri",
      "value": "",
      "description": "Captured from the response of '15 Issue SD-JWT VC and mdoc / 07 Create Mdl offer'."
    },
    {
      "key": "mdlDesignId",
      "value": "",
      "description": "Captured from the response of '11 Credential Designs / 06 Create Mdl mdoc design'."
    },
    {
      "key": "mdlLogoIntegrity",
      "value": "",
      "description": "Captured from the response of '11 Credential Designs / 07 Upload Mdl logo asset'."
    },
    {
      "key": "mdlLogoUri",
      "value": "",
      "description": "Captured from the response of '11 Credential Designs / 07 Upload Mdl logo asset'."
    },
    {
      "key": "mdlPreAuthCode",
      "value": "",
      "description": "Captured from the response of '15 Issue SD-JWT VC and mdoc / 08 Resolve Mdl offer'."
    },
    {
      "key": "mdlSettingsResourceId",
      "value": "",
      "description": "Captured from the response of '13 Credential Configurations / 02 Read the Mdl credential configuration'."
    },
    {
      "key": "mdlSettingsScope",
      "value": "",
      "description": "Captured from the response of '13 Credential Configurations / 02 Read the Mdl credential configuration'."
    },
    {
      "key": "mdlVariantEnId",
      "value": "",
      "description": "Captured from the response of '11 Credential Designs / 08 Create Mdl render variant (en)'."
    },
    {
      "key": "membershipCredentialEndpoint",
      "value": "",
      "description": "Captured from the response of '18 Issue W3C VCDM 2.0 / 04 Fetch the VCDM 2.0 OID4VCI metadata'."
    },
    {
      "key": "membershipCredentialIdentifier",
      "value": "",
      "description": "Captured from the response of '18 Issue W3C VCDM 2.0 / 05 Exchange the Membership pre-authorized code'."
    },
    {
      "key": "membershipMetadataUrl",
      "value": "",
      "description": "Captured from the response of '18 Issue W3C VCDM 2.0 / 03 Resolve the Membership offer'."
    },
    {
      "key": "membershipOfferUri",
      "value": "",
      "description": "Captured from the response of '18 Issue W3C VCDM 2.0 / 02 Create the Membership offer'."
    },
    {
      "key": "membershipPreAuthCode",
      "value": "",
      "description": "Captured from the response of '18 Issue W3C VCDM 2.0 / 03 Resolve the Membership offer'."
    },
    {
      "key": "membershipStatusIndex",
      "value": "",
      "description": "Captured from the response of '18 Issue W3C VCDM 2.0 / 06 Request the Membership credential'."
    },
    {
      "key": "membershipTokenEndpoint",
      "value": "",
      "description": "Captured from the response of '18 Issue W3C VCDM 2.0 / 04 Fetch the VCDM 2.0 OID4VCI metadata'."
    },
    {
      "key": "operatorAuthCode",
      "value": "",
      "description": "Captured from the response of '01 Operator Sign-in / 04 Resume authorization callback'."
    },
    {
      "key": "operatorAuthSessionId",
      "value": "",
      "description": "Captured from the response of '01 Operator Sign-in / 01 Start authorization request'."
    },
    {
      "key": "operatorSession",
      "value": "",
      "description": "Temporary operator authorization/PKCE session state; populated by 01 Operator Sign-in and cleared when platformAccessToken is issued."
    },
    {
      "key": "operatorCodeChallenge",
      "value": "",
      "description": "Captured from the response of '01 Operator Sign-in / 01 Start authorization request'."
    },
    {
      "key": "operatorLoginUrl",
      "value": "",
      "description": "Captured from the response of '01 Operator Sign-in / 01 Start authorization request'."
    },
    {
      "key": "operatorRedirectUri",
      "value": "",
      "description": "Derived before every request from baseDomain and tenantSubdomain."
    },
    {
      "key": "operatorReturnUrl",
      "value": "",
      "description": "Captured from the response of '01 Operator Sign-in / 01 Start authorization request'."
    },
    {
      "key": "operatorSessionCode",
      "value": "",
      "description": "Captured from the response of '01 Operator Sign-in / 02 Open login page'."
    },
    {
      "key": "operatorTabId",
      "value": "",
      "description": "Captured from the response of '01 Operator Sign-in / 02 Open login page'."
    },
    {
      "key": "platformAccessToken",
      "value": "",
      "description": "Captured only from 01 Operator Sign-in / 05 Exchange code for platform token; platform administration token."
    },
    {
      "key": "platformAzureApplicationId",
      "value": "edk-signing",
      "description": "Label the platform stamps on keys it generates in that vault (operators only)."
    },
    {
      "key": "platformAzureClientId",
      "value": "replace-with-entra-client-id",
      "description": "Client id of that application (operators only)."
    },
    {
      "key": "platformAzureClientSecret",
      "value": "replace-with-entra-client-secret",
      "description": "Client secret of that application, written once and never returned (operators only)."
    },
    {
      "key": "platformAzureCredentialSecretRef",
      "value": "",
      "description": "Captured from the response of '07 Bring Your Own KMS / 03 Platform Azure Key Vault (operators, optional) / 01 Resolve exact platform Azure resource'."
    },
    {
      "key": "platformAzureDisplayName",
      "value": "Platform Azure Key Vault signing",
      "description": "Display name of the platform tenant's Azure Key Vault resource (operators only)."
    },
    {
      "key": "platformAzureProviderId",
      "value": "azure-shared-signing",
      "description": "Provider id of the platform tenant's Azure Key Vault resource (operators only)."
    },
    {
      "key": "platformAzureResourceHandle",
      "value": "",
      "description": "Captured from the response of '07 Bring Your Own KMS / 03 Platform Azure Key Vault (operators, optional) / 01 Resolve exact platform Azure resource'."
    },
    {
      "key": "platformAzureResourceVersion",
      "value": "",
      "description": "Captured from the response of '07 Bring Your Own KMS / 03 Platform Azure Key Vault (operators, optional) / 01 Resolve exact platform Azure resource'."
    },
    {
      "key": "platformAzureTenantId",
      "value": "replace-with-entra-tenant-id",
      "description": "Entra tenant id of the application the platform signs in with (operators only)."
    },
    {
      "key": "platformAzureVaultUri",
      "value": "replace-with-https://<vault-name>.vault.azure.net",
      "description": "HTTPS URI of the platform tenant's Key Vault (operators only)."
    },
    {
      "key": "platformKmsDisplayName",
      "value": "",
      "description": "Captured from the response of '08 KMS Provider Sharing / 01 Create shareable platform KMS resource'."
    },
    {
      "key": "platformKmsProviderId",
      "value": "",
      "description": "Captured from the response of '08 KMS Provider Sharing / 01 Create shareable platform KMS resource'."
    },
    {
      "key": "platformKmsResourceHandle",
      "value": "",
      "description": "Captured from the response of '08 KMS Provider Sharing / 01 Create shareable platform KMS resource'."
    },
    {
      "key": "platformKmsResourceVersion",
      "value": "",
      "description": "Captured from the response of '08 KMS Provider Sharing / 01 Create shareable platform KMS resource'."
    },
    {
      "key": "preAuthCode",
      "value": "",
      "description": "Captured from the response of '15 Issue SD-JWT VC and mdoc / 02 Resolve credential offer'."
    },
    {
      "key": "proofJwt",
      "value": "",
      "description": "Captured from the response of '15 Issue SD-JWT VC and mdoc'."
    },
    {
      "key": "requestObjectUri",
      "value": "",
      "description": "Captured from the response of '22 Verification / 01 Create verification request'."
    },
    {
      "key": "statusListId",
      "value": "",
      "description": "Captured from the response of '12 Status Lists / 01 Create token status list'."
    },
    {
      "key": "statusListUri",
      "value": "",
      "description": "Captured from the response of '12 Status Lists / 01 Create token status list'."
    },
    {
      "key": "subTenantOnboardingCorrelationId",
      "value": "",
      "description": "Captured from the response of '05 Subtenants / 01 Register the acme-eu subtenant'."
    },
    {
      "key": "subTenantSlug",
      "value": "",
      "description": "Captured from the response of '05 Subtenants'."
    },
    {
      "key": "tenantAuthorizationEndpoint",
      "value": "",
      "description": "Captured from the response of '03 Tenant Owner Activation and Sign-in / 03 Start tenant owner authorization'."
    },
    {
      "key": "tenantAuthorizationServerOrigin",
      "value": "",
      "description": "Derived before every request from baseDomain and tenantSubdomain."
    },
    {
      "key": "tenantCredentialDesignApiBaseUrl",
      "value": "",
      "description": "Derived before every request from baseDomain and tenantSubdomain."
    },
    {
      "key": "tenantDcqlApiBaseUrl",
      "value": "",
      "description": "Derived before every request from baseDomain and tenantSubdomain."
    },
    {
      "key": "tenantDidApiBaseUrl",
      "value": "",
      "description": "Derived before every request from baseDomain and tenantSubdomain."
    },
    {
      "key": "tenantHostedAuthorizationServerId",
      "value": "",
      "description": "Captured from the response of '03 Tenant Owner Activation and Sign-in / 07a Resolve tenant hosted authorization server'."
    },
    {
      "key": "tenantId",
      "value": "",
      "description": "Captured from the response of '02 Tenant Onboarding / 01 Register tenant'."
    },
    {
      "key": "tenantIssuerApiBaseUrl",
      "value": "",
      "description": "Derived before every request from baseDomain and tenantSubdomain."
    },
    {
      "key": "tenantOwnerActivationToken",
      "value": "",
      "description": "Captured from the response of '02 Tenant Onboarding / 01 Register tenant'."
    },
    {
      "key": "tenantOwnerAuthCode",
      "value": "",
      "description": "Captured from the response of '03 Tenant Owner Activation and Sign-in / 06 Resume tenant owner authorization'."
    },
    {
      "key": "tenantOwnerAuthSessionId",
      "value": "",
      "description": "Captured from the response of '03 Tenant Owner Activation and Sign-in / 03 Start tenant owner authorization'."
    },
    {
      "key": "tenantBootstrapSession",
      "value": "",
      "description": "Temporary tenant-owner authorization/PKCE session state; populated by 03 Tenant Owner Activation and Sign-in and cleared after tenantAccessToken is issued."
    },
    {
      "key": "tenantOwnerCodeChallenge",
      "value": "",
      "description": "Captured from the response of '03 Tenant Owner Activation and Sign-in / 03 Start tenant owner authorization'."
    },
    {
      "key": "tenantOwnerEmail",
      "value": "",
      "description": "Captured from the response of '02 Tenant Onboarding / 01 Register tenant'."
    },
    {
      "key": "tenantOwnerLoginActionUrl",
      "value": "",
      "description": "Captured from the response of '03 Tenant Owner Activation and Sign-in / 03 Start tenant owner authorization'."
    },
    {
      "key": "tenantOwnerLoginUrl",
      "value": "",
      "description": "Captured from the response of '03 Tenant Owner Activation and Sign-in / 03 Start tenant owner authorization'."
    },
    {
      "key": "tenantOwnerProtocolBase",
      "value": "",
      "description": "Captured from the response of '03 Tenant Owner Activation and Sign-in / 03 Start tenant owner authorization'."
    },
    {
      "key": "tenantOwnerReturnUrl",
      "value": "",
      "description": "Captured from the response of '03 Tenant Owner Activation and Sign-in / 03 Start tenant owner authorization'."
    },
    {
      "key": "tenantOwnerSessionCode",
      "value": "",
      "description": "Captured from the response of '03 Tenant Owner Activation and Sign-in / 04 Open tenant owner login page'."
    },
    {
      "key": "tenantOwnerTabId",
      "value": "",
      "description": "Captured from the response of '03 Tenant Owner Activation and Sign-in / 04 Open tenant owner login page'."
    },
    {
      "key": "tenantBootstrapAccessToken",
      "value": "",
      "description": "Temporary bootstrap token from 03 Tenant Owner Activation and Sign-in / 07 Exchange tenant owner authorization code; cleared after tenant service client/token setup."
    },
    {
      "key": "tenantPlatformConfigApiBaseUrl",
      "value": "",
      "description": "Derived before every request from baseDomain and tenantSubdomain."
    },
    {
      "key": "tenantPublicAuthority",
      "value": "",
      "description": "Derived before every request from baseDomain and tenantSubdomain."
    },
    {
      "key": "tenantRegistrationCorrelationId",
      "value": "",
      "description": "Captured from the response of '02 Tenant Onboarding / 01 Register tenant'."
    },
    {
      "key": "tenantStatusListApiBaseUrl",
      "value": "",
      "description": "Derived before every request from baseDomain and tenantSubdomain."
    },
    {
      "key": "tenantAccessToken",
      "value": "",
      "description": "Captured only from 04 Tenant Service Token / 01 Tenant service token (client credentials); tenant administration/runtime token."
    },
    {
      "key": "tenantAccessTokenEndpoint",
      "value": "",
      "description": "Captured from the response of '04 Tenant Service Token / 01 Tenant service token (client credentials)'."
    },
    {
      "key": "tenantTrustDomainApiBaseUrl",
      "value": "",
      "description": "Derived before every request from baseDomain and tenantSubdomain."
    },
    {
      "key": "tenantVerifierBackendBaseUrl",
      "value": "",
      "description": "Derived before every request from baseDomain and tenantSubdomain."
    },
    {
      "key": "tokenEndpoint",
      "value": "",
      "description": "Captured from the response of '15 Issue SD-JWT VC and mdoc / 03 Fetch OID4VCI metadata'."
    },
    {
      "key": "transactionCode",
      "value": "",
      "description": "Captured from the response of '16 Issue with Transaction Code / 01 Create transaction-code EuPid offer'."
    },
    {
      "key": "transactionCorrelationId",
      "value": "",
      "description": "Captured from the response of '16 Issue with Transaction Code / 01 Create transaction-code EuPid offer'."
    },
    {
      "key": "transactionCredentialEndpoint",
      "value": "",
      "description": "Captured from the response of '16 Issue with Transaction Code / 03 Fetch transaction-code OID4VCI metadata'."
    },
    {
      "key": "transactionCredentialIdentifier",
      "value": "",
      "description": "Captured from the response of '16 Issue with Transaction Code / 05 Exchange transaction code for token'."
    },
    {
      "key": "transactionCredentialStatusUri",
      "value": "",
      "description": "Captured from the response of '16 Issue with Transaction Code / 06 Issue transaction-code EuPid credential'."
    },
    {
      "key": "transactionIssuerMetadataUrl",
      "value": "",
      "description": "Captured from the response of '16 Issue with Transaction Code / 02 Resolve transaction-code EuPid offer'."
    },
    {
      "key": "transactionNonceEndpoint",
      "value": "",
      "description": "Captured from the response of '16 Issue with Transaction Code / 03 Fetch transaction-code OID4VCI metadata'."
    },
    {
      "key": "transactionOfferUri",
      "value": "",
      "description": "Captured from the response of '16 Issue with Transaction Code / 01 Create transaction-code EuPid offer'."
    },
    {
      "key": "transactionPreAuthCode",
      "value": "",
      "description": "Captured from the response of '16 Issue with Transaction Code / 02 Resolve transaction-code EuPid offer'."
    },
    {
      "key": "transactionStatusUri",
      "value": "",
      "description": "Captured from the response of '16 Issue with Transaction Code / 01 Create transaction-code EuPid offer'."
    },
    {
      "key": "transactionTokenEndpoint",
      "value": "",
      "description": "Captured from the response of '16 Issue with Transaction Code / 03 Fetch transaction-code OID4VCI metadata'."
    },
    {
      "key": "trustAnchorId",
      "value": "",
      "description": "Captured from the response of '23 Trust Domains and Trust Lists / 02 List anchors of the seeded domain'."
    },
    {
      "key": "trustDomainId",
      "value": "",
      "description": "Captured from the response of '23 Trust Domains and Trust Lists / 01 List trust domains'."
    },
    {
      "key": "trustX509IdentityIdentifierId",
      "value": "",
      "description": "Captured from the response of '23 Trust Domains and Trust Lists / 02 List anchors of the seeded domain'."
    },
    {
      "key": "verifyCorrelationId",
      "value": "",
      "description": "Captured from the response of '22 Verification / 01 Create verification request'."
    },
    {
      "key": "vicalAnchorId",
      "value": "",
      "description": "Captured from the response of '23 Trust Domains and Trust Lists / 11 Create a VICAL signer anchor'."
    },
    {
      "key": "vicalAnchorVersion",
      "value": "",
      "description": "Captured from the response of '23 Trust Domains and Trust Lists / 11 Create a VICAL signer anchor'."
    },
    {
      "key": "vicalDomainId",
      "value": "",
      "description": "Captured from the response of '23 Trust Domains and Trust Lists / 07 Create a second trust domain'."
    },
    {
      "key": "vicalDomainVersion",
      "value": "",
      "description": "Captured from the response of '23 Trust Domains and Trust Lists / 07 Create a second trust domain'."
    },
    {
      "key": "x5cStatusListUri",
      "value": "",
      "description": "Captured from the response of '12 Status Lists / 04a Create X.509 token status list'."
    },
    {
      "key": "restoreIssuerBindingId",
      "value": "",
      "description": "Id of the issuer authorization-server binding that carries the restored default. Captured by 09 / 47a."
    },
    {
      "key": "restoreIssuerBindingRevision",
      "value": "",
      "description": "Revision of that binding, sent as the compare-and-set precondition of the restore. Captured by 09 / 47a."
    },
    {
      "key": "strayIssuerBindingId",
      "value": "",
      "description": "Id of the walkthrough authorization-server binding to delete once the default is restored; absent when the walkthrough binding itself carried the default. Captured by 09 / 47a."
    }
  ],
  "item": [
    {
      "name": "00 Before You Start",
      "description": "Start with the platform operator credentials and run this collection from platform authentication through tenant provisioning. It creates the tenant, automates the one-time tenant-owner bootstrap, creates the tenant confidential client, obtains tenantAccessToken, and uses that token for all later tenant administration and runtime calls. The collection covers issuer/verifier configuration, credential designs and claims, standalone status lists, credential configurations, issuance, DCQL, verification, trust domains, and KMS runtime operations.\n\nThe environment file has six values: baseDomain, tenantSubdomain, tenantName, operatorEmail, operatorPassword and tenantOwnerPassword. Every URL, host, DID and API base in the collection is derived from baseDomain and tenantSubdomain before each request. Nothing derived is written back to the environment.\n\nFolders are continuous and runnable in order: 01 platform authentication, 02 tenant provisioning, 03 one-time tenant bootstrap, 04 confidential-client tenant token, 05 optional subtenants, 06 keys and DID, 07 optional external KMS, 08 KMS sharing, 09 authorization-server configuration, 10 issuer configuration, 11 credential designs, 12 standalone status lists, 13 credential configurations, 14 public metadata checks, 15-20 issuance profiles, 21 DCQL, 22 verification, 23 trust domains, 24 KMS runtime, and 25 Developer Console policy. Each folder description links the corresponding developer guide and states its prerequisites; optional folders ship disabled when they require environment-specific provider resources or interactive operator privileges.\n\nDocumentation: https://docs.sphereon.com/edk/guides/developer-credential-journey",
      "item": []
    },
    {
      "name": "01 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.\n\nDocumentation: https://docs.sphereon.com/edk/admin-console/guides/onboard-a-tenant#platform-authentication",
      "item": [
        {
          "name": "01 Start authorization request",
          "protocolProfileBehavior": {
            "followRedirects": false
          },
          "request": {
            "auth": {
              "type": "noauth"
            },
            "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', 'operatorSession', 'operatorReturnUrl', 'operatorTabId', 'operatorSessionCode', 'operatorAuthCode', 'operatorCodeChallenge'].forEach((key) => pm.collectionVariables.unset(key));",
                  "const verifier = CryptoJS.lib.WordArray.random(48).toString(CryptoJS.enc.Base64).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "pm.collectionVariables.set('operatorCodeVerifier', verifier);",
                  "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])); pm.collectionVariables.set('operatorSession', 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": {
            "auth": {
              "type": "noauth"
            },
            "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": {
            "auth": {
              "type": "noauth"
            },
            "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": {
            "auth": {
              "type": "noauth"
            },
            "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": {
            "auth": {
              "type": "noauth"
            },
            "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 platformAccessToken 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'));",
                  "const platformBinding = payload.tenant_id || payload.tenantId || payload.tenant;",
                  "pm.test('operator token binds the platform tenant', () => pm.expect(platformBinding).to.be.a('string').and.not.empty);",
                  "pm.collectionVariables.set('platformTenantId', platformBinding);",
                  "pm.collectionVariables.set('platformAccessToken', j.access_token);",
                  "pm.collectionVariables.unset('operatorCodeVerifier');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const j = pm.response.json(); pm.collectionVariables.set('platformAccessToken', j.access_token); pm.collectionVariables.unset('operatorSession'); pm.collectionVariables.unset('tenantBootstrapAccessToken');"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "02 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.\n\nDocumentation: https://docs.sphereon.com/edk/admin-console/guides/onboard-a-tenant",
      "item": [
        {
          "name": "01 Register tenant",
          "request": {
            "method": "POST",
            "url": "{{platformUrl}}/api/platform/admin/v1/tenants",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"tenant\": {\n    \"tenantType\": \"organization\",\n    \"name\": \"{{tenantName}}\",\n    \"description\": \"{{tenantName}} issuing and verification tenant\",\n    \"slug\": \"{{tenantSubdomain}}\",\n    \"initialPlatformSubdomain\": true\n  },\n  \"contacts\": {\n    \"technical\": {\n      \"email\": \"admin@{{tenantSubdomain}}.example\",\n      \"displayName\": \"{{tenantName}} Technical Contact\"\n    },\n    \"administrativeSameAsTechnical\": true,\n    \"ownerAdmin\": {\n      \"source\": \"technical\"\n    }\n  },\n  \"login\": {\n    \"enabled\": true,\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. The platform selects its configured email route; when none is effective it returns a one-time manual activation link."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant registered', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "if (![200, 201].includes(pm.response.code)) {",
                  "  console.error('Tenant registration failed (' + pm.response.code + '); aborting this tenant run - every downstream request depends on a registered tenant.');",
                  "  postman.setNextRequest(null);",
                  "} else {",
                  "  try {",
                  "    const j = pm.response.json();",
                  "    const tenant = j.tenant || j;",
                  "    if (tenant && tenant.id) pm.collectionVariables.set('tenantId', tenant.id);",
                  "    const ownerEmail = `admin@${pm.variables.replaceIn('{{tenantSubdomain}}')}.example`;",
                  "    pm.collectionVariables.set('tenantOwnerEmail', ownerEmail);",
                  "    pm.expect(j.created && j.created.ownerIdentityId, 'persisted owner identity id').to.be.a('string').and.not.empty;",
                  "    pm.collectionVariables.set('tenantOwnerIdentityId', j.created.ownerIdentityId);",
                  "    const activationLink = j.delivery && j.delivery.manualActivationLink;",
                  "    pm.expect(j.delivery && j.delivery.status, 'owner activation delivery state').to.eql('MANUAL_READY');",
                  "    pm.expect(activationLink, 'single-use manual owner activation link').to.be.a('string').and.not.empty;",
                  "    const fragment = activationLink.split('#')[1] || '';",
                  "    pm.expect(fragment, 'owner activation token fragment').to.be.a('string').and.not.empty;",
                  "    pm.collectionVariables.set('tenantOwnerActivationToken', decodeURIComponent(fragment));",
                  "    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 || {};",
                  "      const expectedTenantOrigin = String(pm.variables.get('tenantGatewayUrl') || '').trim().replace(/\\/$/, '');",
                  "      const exactTenantOrigin = (label, value) => pm.expect(String(value || '').replace(/\\/$/, ''), label).to.eql(expectedTenantOrigin);",
                  "      const protocolUrl = (label, value, segment) => {",
                  "        const actual = String(value || '').replace(/\\/$/, '');",
                  "        const slug = pm.variables.replaceIn('{{tenantSubdomain}}');",
                  "        const allowed = [expectedTenantOrigin, expectedTenantOrigin + '/' + segment + '/' + slug];",
                  "        pm.expect(allowed, label).to.include(actual);",
                  "      };",
                  "      exactTenantOrigin('tenant URL', j.tenantUrl || j.primaryDomainUrl);",
                  "      protocolUrl('AS issuer URL', serviceUrls.authorizationServerUrl || j.issuerUrl || j.tenant?.issuerUrl, 'as');",
                  "      protocolUrl('OID4VCI issuer URL', serviceUrls.oid4vciIssuerUrl || j.oid4vciIssuerUrl || j.tenant?.oid4vciIssuerUrl, 'oid4vci');",
                  "      protocolUrl('OID4VP verifier URL', serviceUrls.oid4vpVerifierUrl || j.oid4vpVerifierUrl || j.tenant?.oid4vpVerifierUrl, 'oid4vp');",
                  "    });",
                  "  } catch (registrationPostConditionError) {",
                  "    console.error('Registration post-condition failed; aborting this tenant run: ' + registrationPostConditionError.message);",
                  "    postman.setNextRequest(null);",
                  "    throw registrationPostConditionError;",
                  "  }",
                  "}"
                ]
              }
            },
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "['tenantBootstrapAccessToken', 'tenantBootstrapSession', 'tenantAccessToken', 'walletAccessToken'].forEach((key) => pm.collectionVariables.unset(key));"
                ]
              }
            }
          ]
        },
        {
          "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."
          },
          "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."
          },
          "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."
          },
          "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."
          },
          "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 origins = [",
                  "  originFor(endpointFor('OID4VCI_ISSUER'), expectedGateway),",
                  "  originFor(endpointFor('OID4VP_VERIFIER'), expectedGateway),",
                  "  originFor(endpointFor('OAUTH2_AUTHORIZATION_SERVER'), expectedGateway),",
                  "];",
                  "const expectedTenantOrigin = expectedGateway;",
                  "pm.test('tenant public endpoint bindings use the exact HTTPS tenant gateway origin', () => {",
                  "  pm.expect(expectedGateway, 'configured tenant gateway origin').to.eql(expectedTenantOrigin);",
                  "  origins.forEach((origin) => {",
                  "    pm.expect(origin, 'endpoint origin').to.eql(expectedTenantOrigin);",
                  "  });",
                  "});",
                  "pm.collectionVariables.set('tenantHost', pm.variables.get('tenantSubdomain') + '.' + pm.variables.get('baseDomain'));"
                ]
              }
            }
          ]
        },
        {
          "name": "06 Resolve tenant runtime service discovery",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/bootstrap/v1/runtime-config/admin-console?tenantId={{tenantId}}&tenantSlug={{tenantSubdomain}}",
            "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 tenantKms = service('tenantKms');",
                  "const tenantDid = service('tenantDid');",
                  "const issuer = service('issuer');",
                  "const verifier = service('verifier');",
                  "const expectedTenantOrigin = trimBase(pm.variables.get('tenantGatewayUrl'));",
                  "pm.test('runtime config exposes tenant workload services', () => {",
                  "  ['tenantKms', 'tenantDid', 'issuer', 'verifier'].forEach((key) => {",
                  "    pm.expect(trimBase(services[key] && services[key].baseUrl), key + ' baseUrl').to.eql(expectedTenantOrigin);",
                  "  });",
                  "  pm.expect(tenantKms.audience, 'tenant-KMS audience').to.eql('enterprise-tenant-kms');",
                  "  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 || tenantDid.baseUrl || verifier.baseUrl || '');",
                  "// The collection derives every API base from baseDomain and tenantSubdomain before each request. This",
                  "// request proves the platform publishes the same bases; nothing here overwrites the derivation.",
                  "const derived = (key) => trimBase(pm.collectionVariables.get(key));",
                  "pm.test('runtime config matches the bases derived from baseDomain and tenantSubdomain', () => {",
                  "  pm.expect(tenantOrigin, 'tenant origin').to.eql(derived('tenantGatewayUrl'));",
                  "  pm.expect(joinUrl(tenantDid.baseUrl || tenantOrigin, endpointPath(tenantDid, ['api'], '/api/did/v1')), 'tenantDidApiBaseUrl').to.eql(derived('tenantDidApiBaseUrl'));",
                  "  pm.expect(joinUrl(issuer.baseUrl || tenantOrigin, endpointPath(issuer, ['credentialDesigns'], '/api/credential-design/v1')), 'tenantCredentialDesignApiBaseUrl').to.eql(derived('tenantCredentialDesignApiBaseUrl'));",
                  "  pm.expect(joinUrl(issuer.baseUrl || tenantOrigin, endpointPath(issuer, ['statusLists'], '/api/statuslist/v1')), 'tenantStatusListApiBaseUrl').to.eql(derived('tenantStatusListApiBaseUrl'));",
                  "  pm.expect(joinUrl(issuer.baseUrl || tenantOrigin, endpointPath(issuer, ['api'], '/api/oid4vci/v1')), 'tenantIssuerApiBaseUrl').to.eql(derived('tenantIssuerApiBaseUrl'));",
                  "  pm.expect(joinUrl(verifier.baseUrl || tenantOrigin, endpointPath(verifier, ['dcqlApi'], '/api/dcql/v1')), 'tenantDcqlApiBaseUrl').to.eql(derived('tenantDcqlApiBaseUrl'));",
                  "  pm.expect(joinUrl(verifier.baseUrl || tenantOrigin, '/oid4vp/backend'), 'tenantVerifierBackendBaseUrl').to.eql(derived('tenantVerifierBackendBaseUrl'));",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "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; tenantSubdomain remains only the public host/routing slug."
          },
          "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);",
                  "const resolvedInstanceId = stringOf(selected && selected.instanceId);",
                  "pm.test('verifier party id resolved from platform config', () => {",
                  "  pm.expect(resolvedId, 'verifier party id').to.be.a('string').and.match(uuidPattern);",
                  "});",
                  "pm.test('verifier runtime instance id resolved from platform config', () => {",
                  "  pm.expect(resolvedInstanceId, 'verifier instance id').to.be.a('string').and.not.empty;",
                  "});",
                  "if (resolvedId) {",
                  "  pm.collectionVariables.set('verifierId', resolvedId);",
                  "}",
                  "if (resolvedInstanceId) {",
                  "  pm.collectionVariables.set('verifierInstanceId', resolvedInstanceId);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "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."
          },
          "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 UUID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;",
                  "const uuidOf = (instance) => instance && ([instance.instanceId, instance.id, instance.issuerId, instance.partyId].map(stringOf).find((v) => v && UUID_SHAPE.test(v)) || 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 = uuidOf(selected) || 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);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "09 Get issuer testing-console config",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuer/instances/{{issuerId}}/testing-console",
            "description": "Confirms sample-data provisioning automatically enabled the exact issuer instance's PUBLIC testing console and resolves its tenant-gateway URLs."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('issuer testing-console config returned', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "const tenantGatewayUrl = pm.variables.replaceIn('{{tenantGatewayUrl}}').replace(/\\/+$/, '');",
                  "const issuerId = pm.variables.replaceIn('{{issuerId}}');",
                  "const publicBaseUrl = typeof j.publicBaseUrl === 'string' ? j.publicBaseUrl.replace(/\\/+$/, '') : '';",
                  "const displayUrl = typeof j.displayUrl === 'string' ? j.displayUrl : '';",
                  "const expectedDisplayUrl = `${tenantGatewayUrl}/testing-console/issuer/${encodeURIComponent(issuerId)}?publicBaseUrl=${encodeURIComponent(publicBaseUrl)}`;",
                  "pm.test('issuer testing-console config is enabled PUBLIC on the tenant host', () => {",
                  "  pm.expect(j.enabled && j.enabled.value, 'enabled.value').to.eql(true);",
                  "  pm.expect(j.exposureMode && j.exposureMode.value, 'exposureMode.value').to.eql('public');",
                  "  pm.expect(publicBaseUrl, 'publicBaseUrl').to.satisfy((value) => value === tenantGatewayUrl || value.startsWith(`${tenantGatewayUrl}/`));",
                  "  pm.expect(displayUrl, 'displayUrl').to.eql(expectedDisplayUrl);",
                  "});",
                  "if (publicBaseUrl && displayUrl) {",
                  "  pm.collectionVariables.set('issuerTestingPublicBaseUrl', publicBaseUrl);",
                  "  pm.collectionVariables.set('issuerTestingDisplayUrl', displayUrl);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "10 Get verifier testing-console config",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vp/verifier/instances/{{verifierInstanceId}}/testing-console",
            "description": "Confirms sample-data provisioning automatically enabled the exact verifier instance's PUBLIC testing console and resolves its tenant-gateway URLs."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('verifier testing-console config returned', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "const tenantGatewayUrl = pm.variables.replaceIn('{{tenantGatewayUrl}}').replace(/\\/+$/, '');",
                  "const verifierInstanceId = pm.variables.replaceIn('{{verifierInstanceId}}');",
                  "const publicBaseUrl = typeof j.publicBaseUrl === 'string' ? j.publicBaseUrl.replace(/\\/+$/, '') : '';",
                  "const displayUrl = typeof j.displayUrl === 'string' ? j.displayUrl : '';",
                  "const expectedDisplayUrl = `${tenantGatewayUrl}/testing-console/verifier/${encodeURIComponent(verifierInstanceId)}?publicBaseUrl=${encodeURIComponent(publicBaseUrl)}`;",
                  "pm.test('verifier testing-console config is enabled PUBLIC on the tenant host', () => {",
                  "  pm.expect(j.enabled && j.enabled.value, 'enabled.value').to.eql(true);",
                  "  pm.expect(j.exposureMode && j.exposureMode.value, 'exposureMode.value').to.eql('public');",
                  "  pm.expect(publicBaseUrl, 'publicBaseUrl').to.satisfy((value) => value === tenantGatewayUrl || value.startsWith(`${tenantGatewayUrl}/`));",
                  "  pm.expect(displayUrl, 'displayUrl').to.eql(expectedDisplayUrl);",
                  "});",
                  "if (publicBaseUrl && displayUrl) {",
                  "  pm.collectionVariables.set('verifierTestingPublicBaseUrl', publicBaseUrl);",
                  "  pm.collectionVariables.set('verifierTestingDisplayUrl', displayUrl);",
                  "}"
                ]
              }
            }
          ]
        }
      ],
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{platformAccessToken}}",
            "type": "string"
          }
        ]
      }
    },
    {
      "name": "03 Tenant Owner Activation and Sign-in",
      "description": "Activates the tenant owner created by registration through the one-time activation link, signs the owner in with the authorization-code flow on the tenant host, and registers the confidential tenant service client on the tenant's hosted authorization server. Only the owner may create the client because its secret is tenant-scoped material.\n\nDocumentation: https://docs.sphereon.com/edk/admin-console/guides/onboard-a-tenant#tenant-confidential-client",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantBootstrapAccessToken}}",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "01 Resolve tenant owner activation",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "url": "{{platformUrl}}/api/account-actions/v1/resolve",
            "description": "Resolves the one-time onboarding action through a request body. The activation token is never sent in a path or query parameter.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"token\": \"{{tenantOwnerActivationToken}}\"\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant owner activation resolves', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.action, 'safe activation metadata').to.be.an('object');",
                  "pm.expect(j.action.action).to.be.oneOf(['tenant.onboarding', 'identity.activation']);",
                  "pm.expect(j.action.requiresPassword, 'activation requires a password').to.eql(true);",
                  "pm.expect(JSON.stringify(j)).not.to.include(pm.collectionVariables.get('tenantOwnerActivationToken'));",
                  "for (const forbidden of ['tenantId', 'identityId', 'email', 'token']) pm.expect(j.action).not.to.have.property(forbidden);"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Complete tenant owner activation",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "url": "{{platformUrl}}/api/account-actions/v1/complete",
            "description": "Selects the owner's initial password and atomically consumes the one-time activation action.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"token\": \"{{tenantOwnerActivationToken}}\",\n  \"password\": \"{{tenantOwnerPassword}}\"\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant owner activation completed', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.action && j.action.outcomeRef, 'activation outcome').to.be.an('object');",
                  "pm.expect(JSON.stringify(j)).not.to.include(pm.variables.replaceIn('{{tenantOwnerPassword}}'));",
                  "pm.collectionVariables.unset('tenantOwnerActivationToken');"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Start tenant owner authorization",
          "protocolProfileBehavior": {
            "followRedirects": false
          },
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "url": "{{tenantAuthorizationEndpoint}}?response_type=code&client_id=platform-operator-cli&redirect_uri={{operatorRedirectUri}}&scope=openid&state=qa-tenant-owner-state-0001&prompt=login&code_challenge={{tenantOwnerCodeChallenge}}&code_challenge_method=S256",
            "description": "Starts a fresh tenant-gateway authorization-code flow for the activated owner. The S256 challenge is derived from the secret tenantOwnerCodeVerifier."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// The tenant's authorization server is not mounted at the tenant origin: every hosted instance,",
                  "// including the default one, publishes under its own path so routes on a shared tenant host stay",
                  "// disjoint. Ask the tenant for its authorization endpoint instead of assuming where it lives, so",
                  "// this keeps working whatever route shape the deployment uses.",
                  "const discoveryUrl = String(pm.variables.get('tenantGatewayUrl') || '').trim().replace(/\\/+$/, '') + '/.well-known/openid-configuration';",
                  "pm.sendRequest({ url: discoveryUrl, method: 'GET', header: { Accept: 'application/json' } }, (err, res) => {",
                  "  if (err) { throw new Error('tenant authorization-server discovery failed: ' + err); }",
                  "  if (res.code !== 200) { throw new Error('tenant authorization-server discovery returned HTTP ' + res.code); }",
                  "  const endpoint = res.json().authorization_endpoint;",
                  "  if (!endpoint) { throw new Error('tenant discovery document has no authorization_endpoint'); }",
                  "  pm.collectionVariables.set('tenantAuthorizationEndpoint', endpoint);",
                  "});",
                  "['tenantAuthorizationEndpoint', 'tenantOwnerLoginUrl', 'tenantOwnerLoginActionUrl', 'tenantOwnerProtocolBase', 'tenantOwnerAuthSessionId', 'tenantBootstrapSession', 'tenantOwnerReturnUrl', 'tenantOwnerTabId', 'tenantOwnerSessionCode', 'tenantOwnerAuthCode', 'tenantOwnerCodeChallenge', 'tenantBootstrapAccessToken'].forEach((key) => pm.collectionVariables.unset(key));",
                  "const verifier = CryptoJS.lib.WordArray.random(48).toString(CryptoJS.enc.Base64).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "pm.collectionVariables.set('tenantOwnerCodeVerifier', verifier);",
                  "const challenge = CryptoJS.SHA256(verifier).toString(CryptoJS.enc.Base64)",
                  "  .replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "pm.collectionVariables.set('tenantOwnerCodeChallenge', challenge);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant authorize redirects to hosted login', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(302));",
                  "const location = pm.response.headers.get('Location');",
                  "pm.expect(location, 'Location header').to.be.a('string').and.to.include('/login?');",
                  "const tenantBase = pm.variables.replaceIn('{{tenantGatewayUrl}}').replace(/\\/$/, '');",
                  "const loginUrl = /^https?:\\/\\//.test(location) ? location : tenantBase + location;",
                  "pm.expect(loginUrl, 'tenant-scoped login URL').to.match(new RegExp('^' + tenantBase.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&') + '(/as/[^/]+)?/login\\\\?'));",
                  "pm.collectionVariables.set('tenantOwnerLoginUrl', loginUrl);",
                  "pm.collectionVariables.set('tenantOwnerLoginActionUrl', loginUrl.split('?')[0]);",
                  "pm.collectionVariables.set('tenantOwnerProtocolBase', loginUrl.replace(/\\/login\\?.*/, ''));",
                  "const sessionId = /[?&]session_id=([^&]+)/.exec(location);",
                  "pm.expect(sessionId, 'session_id in login URL').to.not.eql(null);",
                  "pm.collectionVariables.set('tenantOwnerAuthSessionId', decodeURIComponent(sessionId[1])); pm.collectionVariables.set('tenantBootstrapSession', decodeURIComponent(sessionId[1]));",
                  "const returnUrl = /[?&]return_url=([^&]+)/.exec(location);",
                  "pm.expect(returnUrl, 'return_url in login URL').to.not.eql(null);",
                  "pm.collectionVariables.set('tenantOwnerReturnUrl', decodeURIComponent(returnUrl[1]));"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Open tenant owner login page",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "url": "{{tenantOwnerLoginUrl}}",
            "description": "Loads the tenant-hosted login page and captures its CSRF-bound hidden inputs."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant owner login page rendered', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(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('tenantOwnerTabId', tabId[1]);",
                  "pm.collectionVariables.set('tenantOwnerSessionCode', sessionCode[1]);"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Submit tenant owner credentials",
          "protocolProfileBehavior": {
            "followRedirects": false
          },
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "url": "{{tenantOwnerLoginActionUrl}}",
            "description": "Authenticates the activated owner at the tenant gateway with the CSRF tuple from the rendered form.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "username",
                  "value": "{{tenantOwnerEmail}}"
                },
                {
                  "key": "password",
                  "value": "{{tenantOwnerPassword}}"
                },
                {
                  "key": "session_id",
                  "value": "{{tenantOwnerAuthSessionId}}"
                },
                {
                  "key": "tab_id",
                  "value": "{{tenantOwnerTabId}}"
                },
                {
                  "key": "session_code",
                  "value": "{{tenantOwnerSessionCode}}"
                },
                {
                  "key": "return_url",
                  "value": "{{tenantOwnerReturnUrl}}"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant owner credentials accepted', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(302));",
                  "const location = pm.response.headers.get('Location');",
                  "pm.expect(location, 'Location header').to.be.a('string').and.to.include('/authorize/callback');",
                  "pm.expect(location).to.not.include('error=invalid_credentials');",
                  "const expectedUrl = pm.collectionVariables.get('tenantOwnerReturnUrl');",
                  "const normalizedLocation = /^https?:\\/\\//.test(location) ? location : pm.variables.replaceIn('{{tenantGatewayUrl}}').replace(/\\/$/, '') + location;",
                  "pm.expect(normalizedLocation, 'tenant authorization callback URL').to.eql(expectedUrl);"
                ]
              }
            }
          ]
        },
        {
          "name": "06 Resume tenant owner authorization",
          "protocolProfileBehavior": {
            "followRedirects": false
          },
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "url": "{{tenantOwnerReturnUrl}}",
            "description": "Resumes the pending tenant authorization with the authenticated owner session."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant owner authorization code issued', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(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-tenant-owner-state-0001');",
                  "const code = /[?&#]code=([^&]+)/.exec(location);",
                  "pm.expect(code, 'authorization code in redirect').to.not.eql(null);",
                  "pm.collectionVariables.set('tenantOwnerAuthCode', decodeURIComponent(code[1]));"
                ]
              }
            }
          ]
        },
        {
          "name": "07 Exchange tenant owner authorization code",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "url": "{{tenantOwnerProtocolBase}}/token",
            "description": "Exchanges the tenant authorization code with the matching PKCE verifier. Only this tenant-bound owner token is used on tenant secret routes.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "authorization_code"
                },
                {
                  "key": "code",
                  "value": "{{tenantOwnerAuthCode}}"
                },
                {
                  "key": "redirect_uri",
                  "value": "{{operatorRedirectUri}}"
                },
                {
                  "key": "client_id",
                  "value": "platform-operator-cli"
                },
                {
                  "key": "code_verifier",
                  "value": "{{tenantOwnerCodeVerifier}}"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant owner token issued', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.access_token, 'access_token').to.be.a('string').and.not.empty;",
                  "const parts = j.access_token.split('.');",
                  "pm.expect(parts, 'tenant owner compact JWT').to.have.length(3);",
                  "const segment = parts[1].replace(/-/g, '+').replace(/_/g, '/');",
                  "const padded = segment + '='.repeat((4 - (segment.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) || [];",
                  "const tokenTenant = payload.tenant_id || payload.tenantId || payload.tenant;",
                  "pm.expect(tokenTenant, 'tenant owner token tenant').to.eql(pm.collectionVariables.get('tenantId'));",
                  "pm.expect(payload.sub, 'tenant owner token subject').to.eql(pm.collectionVariables.get('tenantOwnerIdentityId'));",
                  "pm.expect(roles, 'tenant owner roles').to.include('tenant-admin');",
                  "pm.expect(roles, 'tenant owner is not a platform administrator').not.to.include('platform-admin');",
                  "pm.collectionVariables.set('tenantBootstrapAccessToken', j.access_token); pm.collectionVariables.unset('tenantBootstrapSession');",
                  "pm.collectionVariables.unset('tenantOwnerCodeVerifier');"
                ]
              }
            }
          ]
        },
        {
          "name": "07a Resolve tenant hosted authorization server",
          "request": {
            "method": "GET",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/authorization-servers?page=0&size=100",
            "description": "Lists the authorization servers of the registered tenant through the platform configuration API and selects the tenant default: the active hosted instance tenant registration created and marked default for the GENERAL purpose. The tenant service client is registered on that instance in the next request.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant authorization servers listed', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.items, 'items').to.be.an('array');",
                  "const hosted = j.items.filter((item) => item.deployment === 'HOSTED' && item.lifecycle === 'ACTIVE');",
                  "pm.test('tenant registration created an active hosted authorization server', () => pm.expect(hosted.length).to.be.above(0));",
                  "const defaults = hosted.filter((item) => Array.isArray(item.defaultForPurposes) && item.defaultForPurposes.includes('GENERAL'));",
                  "pm.test('exactly one hosted authorization server is the tenant default', () => pm.expect(defaults.length).to.eql(1));",
                  "const tenantDefault = defaults[0];",
                  "pm.collectionVariables.set('tenantHostedAuthorizationServerId', tenantDefault.id);",
                  "pm.collectionVariables.set('tenantHostedAuthorizationServerSlug', tenantDefault.slug);"
                ]
              }
            }
          ]
        },
        {
          "name": "07b Register walkthrough tenant service client",
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/authorization-servers/{{tenantHostedAuthorizationServerId}}/clients",
            "description": "Registers the confidential edk-walkthrough-tenant-service client on the tenant hosted authorization server: client_credentials grant, client_secret_basic authentication, the tenant-admin principal role, and the runtime audiences the tenant service token needs. The secret is write-once; the response returns only its opaque reference. Folder 05 uses this client to obtain the tenant service token.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"clientId\": \"{{tenantServiceClientId}}\",\n  \"clientName\": \"{{tenantName}} walkthrough tenant service\",\n  \"clientType\": \"confidential\",\n  \"enabled\": true,\n  \"grantTypes\": [\n    \"client_credentials\"\n  ],\n  \"responseTypes\": [],\n  \"redirectUris\": [],\n  \"defaultAccessTokenAudience\": \"enterprise-platform\",\n  \"allowedAccessTokenAudiences\": [\n    \"enterprise-tenant-kms\",\n    \"enterprise-tenant-did\",\n    \"enterprise-issuer\",\n    \"enterprise-verifier\"\n  ],\n  \"tokenEndpointAuthMethod\": \"client_secret_basic\",\n  \"principalRoles\": [\n    \"tenant-admin\"\n  ],\n  \"clientCredential\": {\n    \"method\": \"client_secret_basic\",\n    \"clientId\": \"{{tenantServiceClientId}}\",\n    \"secretValue\": \"{{tenantServiceClientSecret}}\"\n  }\n}"
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// The client id defaults to <tenantSubdomain>-service and the write-once secret is generated here.",
                  "// An environment value for either key is honoured when present so an operator can pin them.",
                  "const clientId = String(pm.variables.get('tenantServiceClientId') || '').trim() || (pm.collectionVariables.get('tenantSubdomain') + '-service');",
                  "let clientSecret = String(pm.variables.get('tenantServiceClientSecret') || '').trim();",
                  "if (!clientSecret || /^PASTE-/i.test(clientSecret)) clientSecret = CryptoJS.lib.WordArray.random(32).toString(CryptoJS.enc.Hex);",
                  "pm.collectionVariables.set('tenantServiceClientId', clientId);",
                  "pm.collectionVariables.set('tenantServiceClientSecret', clientSecret);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant service client registered', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(201));",
                  "const j = pm.response.json();",
                  "pm.expect(JSON.stringify(j)).not.to.include(pm.collectionVariables.get('tenantServiceClientSecret'));",
                  "pm.expect(j.clientId, 'client id').to.eql(pm.collectionVariables.get('tenantServiceClientId'));",
                  "pm.expect(j.grantTypes, 'grant types').to.include('client_credentials');",
                  "pm.expect(j.tokenEndpointAuthMethod, 'token endpoint auth method').to.eql('client_secret_basic');",
                  "pm.expect(j.principalRoles, 'principal roles').to.include('tenant-admin');",
                  "pm.expect(j.clientCredential).not.to.have.property('secretValue');",
                  "pm.expect(j.clientCredential.secretReference.resourceHandle, 'opaque secret handle').to.be.a('string').and.not.empty;"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "04 Tenant Service Token",
      "description": "Obtains the tenant-bound service token from the tenant authorization server with the client_credentials grant of the walkthrough tenant service client. Authenticated tenant gateway and administrative API calls use this tenant-scoped token.\n\nDocumentation: https://docs.sphereon.com/edk/admin-console/guides/onboard-a-tenant#tenant-service-token",
      "item": [
        {
          "name": "01 Tenant service token (client credentials)",
          "request": {
            "auth": {
              "type": "basic",
              "basic": [
                {
                  "key": "username",
                  "value": "{{tenantServiceClientId}}",
                  "type": "string"
                },
                {
                  "key": "password",
                  "value": "{{tenantServiceClientSecret}}",
                  "type": "string"
                }
              ]
            },
            "method": "POST",
            "url": "{{tenantAccessTokenEndpoint}}",
            "description": "Authenticates the edk-walkthrough-tenant-service client registered in folder 03 with HTTP basic authentication (tenantServiceClientId:tenantServiceClientSecret) at the tenant authorization server token endpoint and requests a tenant-bound access token for the runtime audiences. The token carries the tenant-admin role and is the folder-level bearer credential for the tenant runtime folders.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "client_credentials",
                  "type": "text"
                },
                {
                  "key": "audience",
                  "value": "enterprise-platform",
                  "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"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const clientId = String(pm.variables.get('tenantServiceClientId') || '').trim();",
                  "const clientSecret = String(pm.variables.get('tenantServiceClientSecret') || '').trim();",
                  "if (!clientId || !clientSecret || /^PASTE-/i.test(clientSecret)) {",
                  "  throw new Error('Run \"03 Tenant Onboarding / 16 Register walkthrough tenant service client\" first; it registers the tenant service client and stores tenantServiceClientId and tenantServiceClientSecret.');",
                  "}",
                  "['tenantAccessToken', 'tenantAccessTokenExpiresAt', 'tenantAccessTokenEndpoint'].forEach((key) => pm.collectionVariables.unset(key));",
                  "// The tenant authorization server publishes under its own path on the tenant host, so its token",
                  "// endpoint is discovered instead of assumed.",
                  "const discoveryUrl = String(pm.variables.get('tenantGatewayUrl') || '').trim().replace(/\\/+$/, '') + '/.well-known/openid-configuration';",
                  "pm.sendRequest({ url: discoveryUrl, method: 'GET', header: { Accept: 'application/json' } }, (err, res) => {",
                  "  if (err) { throw new Error('tenant authorization-server discovery failed: ' + err); }",
                  "  if (res.code !== 200) { throw new Error('tenant authorization-server discovery returned HTTP ' + res.code); }",
                  "  const endpoint = res.json().token_endpoint;",
                  "  if (!endpoint) { throw new Error('tenant discovery document has no token_endpoint'); }",
                  "  pm.collectionVariables.set('tenantAccessTokenEndpoint', endpoint);",
                  "});"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant service token issued', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.test('access token returned', () => pm.expect(j.access_token).to.be.a('string').and.not.empty);",
                  "const parts = String(j.access_token || '').split('.');",
                  "pm.test('access token is a compact JWT', () => pm.expect(parts.length).to.eql(3));",
                  "const segment = parts[1].replace(/-/g, '+').replace(/_/g, '/');",
                  "const padded = segment + '='.repeat((4 - (segment.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('tenant service token has issuer', () => pm.expect(payload.iss).to.be.a('string').and.not.empty);",
                  "const tenantBinding = payload.tenant_id || payload.tenantId || payload.tenant;",
                  "pm.test('tenant service token binds the registered tenant', () => pm.expect(tenantBinding).to.eql(pm.collectionVariables.get('tenantId')));",
                  "pm.test('tenant service token is a workload token of the registered client', () => pm.expect(payload.sub).to.eql(pm.collectionVariables.get('tenantServiceClientId')));",
                  "pm.test('tenant service token carries the tenant-admin role', () => pm.expect(roles).to.include('tenant-admin'));",
                  "const audiences = Array.isArray(payload.aud) ? payload.aud : (payload.aud ? [payload.aud] : []);",
                  "pm.test('tenant service token is scoped to runtime audiences', () => {",
                  "  [\"enterprise-platform\",\"enterprise-tenant-kms\",\"enterprise-tenant-did\",\"enterprise-issuer\",\"enterprise-verifier\"].forEach((expected) => pm.expect(audiences, expected).to.include(expected));",
                  "});",
                  "pm.collectionVariables.set('tenantAccessToken', j.access_token);",
                  "if (payload.exp) pm.collectionVariables.set('tenantAccessTokenExpiresAt', new Date(payload.exp * 1000).toISOString());"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "04a Authentication Preflight",
      "description": "Runs after platform and tenant tokens exist. It decodes every available JWT locally and fails before tenant work if issuer, expiry, tenant binding, role, or audience is wrong. This request is intentionally public and carries no bearer token. Documentation: https://docs.sphereon.com/edk/guides/developer-credential-journey#authentication-contract",
      "item": [
        {
          "name": "01 Validate platform and tenant token scopes",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "auth": {
              "type": "noauth"
            },
            "url": {
              "raw": "{{tenantGatewayUrl}}/.well-known/openid-configuration",
              "host": [
                "{{tenantGatewayUrl}}"
              ],
              "path": [
                ".well-known",
                "openid-configuration"
              ]
            },
            "description": "Public discovery is used only to keep this preflight request harmless. The assertions validate the administration credentials stored by the preceding authentication folders. Run 01 Operator Sign-in / 05 Exchange code for operator token and 04 Tenant Service Token / 01 Obtain tenant access token first."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const decode = (name) => { const token = String(pm.collectionVariables.get(name) || '').trim(); if (!token) throw new Error('Missing ' + name + '. Run the authentication request that issues it first.'); const parts = token.split('.'); if (parts.length !== 3) throw new Error(name + ' must be a compact JWT'); const segment = parts[1].replace(/-/g, '+').replace(/_/g, '/'); const payload = JSON.parse(CryptoJS.enc.Base64.parse(segment + '='.repeat((4 - segment.length % 4) % 4)).toString(CryptoJS.enc.Utf8)); const roles = payload.roles || (payload.realm_access && payload.realm_access.roles) || []; const audiences = Array.isArray(payload.aud) ? payload.aud : (payload.aud ? [payload.aud] : []); if (!payload.iss) throw new Error(name + ' has no issuer'); if (!Number.isFinite(Number(payload.exp)) || Number(payload.exp) <= Math.floor(Date.now() / 1000)) throw new Error(name + ' is expired or has no expiry'); return { payload, roles, audiences }; };",
                  "const platform = decode('platformAccessToken');",
                  "pm.test('platform token has platform-admin role', () => pm.expect(platform.roles).to.include('platform-admin'));",
                  "pm.test('platform token binds the platform tenant', () => pm.expect(String(platform.payload.tenant_id || platform.payload.tenantId || platform.payload.tenant)).to.eql('platform'));",
                  "pm.test('platform token audience is enterprise-platform', () => pm.expect(platform.audiences).to.include('enterprise-platform'));",
                  "const tenant = decode('tenantAccessToken');",
                  "pm.test('tenant token has tenant-admin role', () => pm.expect(tenant.roles).to.include('tenant-admin'));",
                  "pm.test('tenant token binds the registered tenant', () => pm.expect(String(tenant.payload.tenant_id || tenant.payload.tenantId || tenant.payload.tenant)).to.eql(String(pm.collectionVariables.get('tenantId'))));",
                  "pm.test('tenant token has the required runtime audiences', () => ['enterprise-platform', 'enterprise-tenant-kms', 'enterprise-tenant-did', 'enterprise-issuer', 'enterprise-verifier'].forEach((audience) => pm.expect(tenant.audiences).to.include(audience)));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "05 Subtenants",
      "description": "Registers a subtenant under the tenant of folder 02 and lists the children of the parent. Subtenant slugs are globally unique and every tenant is reachable at <slug>.<baseDomain>.",
      "event": [
        {
          "listen": "prerequest",
          "script": {
            "type": "text/javascript",
            "exec": [
              "// Subtenant slugs are globally unique, not unique per parent, so both levels derive from the",
              "// slug of the tenant folder 03 registered. Tenant URLs are flat: <slug>.<baseDomain>.",
              "const gateway = String(pm.collectionVariables.get('tenantGatewayUrl') || '').trim().replace(/\\/+$/, '');",
              "const rootSlug = String(pm.collectionVariables.get('tenantSubdomain') || '').trim();",
              "if (gateway && rootSlug) {",
              "  const subSlug = rootSlug + '-eu';",
              "  const subSubSlug = rootSlug + '-eu-hr';",
              "  pm.collectionVariables.set('subTenantSlug', subSlug);",
              "  pm.collectionVariables.set('subSubTenantSlug', subSubSlug);",
              "  const parsed = gateway.match(/^([a-z][a-z0-9+.-]*):\\/\\/([^/?#]+)$/i);",
              "  if (parsed) {",
              "    const scheme = parsed[1].toLowerCase();",
              "    const authority = parsed[2];",
              "    const port = (authority.match(/:(\\d+)$/) || [])[1];",
              "    const host = authority.replace(/:\\d+$/, '');",
              "    const domain = host.startsWith(rootSlug + '.') ? host.slice(rootSlug.length + 1) : host;",
              "    const subSubHost = subSubSlug + '.' + domain;",
              "    const subSubAuthority = subSubHost + (port ? ':' + port : '');",
              "    const subSubGateway = scheme + '://' + subSubAuthority;",
              "    // subTenant* addresses acme-eu-hr, the tenant this folder issues from.",
              "    pm.collectionVariables.set('subTenantHost', subSubHost);",
              "    pm.collectionVariables.set('subTenantPublicAuthority', subSubAuthority);",
              "    pm.collectionVariables.set('subTenantGatewayUrl', subSubGateway);",
              "    pm.collectionVariables.set('subTenantIssuerApiBaseUrl', subSubGateway + '/api/oid4vci/v1');",
              "    pm.collectionVariables.set('subTenantPlatformConfigApiBaseUrl', subSubGateway + '/api/platform/config/v1');",
              "  }",
              "}"
            ]
          }
        }
      ],
      "item": [
        {
          "name": "01 Register the acme-eu subtenant",
          "request": {
            "method": "POST",
            "url": "{{platformUrl}}/api/platform/admin/v1/tenants",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"tenant\": {\n    \"tenantType\": \"organization\",\n    \"name\": \"{{tenantName}} EU\",\n    \"description\": \"{{tenantName}} EU subtenant\",\n    \"slug\": \"{{subTenantSlug}}\",\n    \"parentTenantId\": \"{{tenantId}}\",\n    \"initialPlatformSubdomain\": true\n  },\n  \"contacts\": {\n    \"technical\": {\n      \"email\": \"admin@{{subTenantSlug}}.example\",\n      \"displayName\": \"{{tenantName}} EU Technical Contact\"\n    },\n    \"administrativeSameAsTechnical\": true,\n    \"ownerAdmin\": {\n      \"source\": \"technical\"\n    }\n  },\n  \"login\": {\n    \"enabled\": true,\n    \"defaultAuthorizationServerRequired\": true\n  },\n  \"provisioning\": {\n    \"issuer\": true,\n    \"verifier\": true,\n    \"keysAndDids\": true,\n    \"sampleData\": true\n  }\n}"
            },
            "description": "Registers the first subtenant under the tenant of folder 03. `parentTenantId` sits inside the `tenant` section; a null value there would make the registration a root tenant. Slugs are globally unique rather than unique per parent, so the subtenant slug derives from the root slug. The QA licence must allow subtenants, otherwise the platform rejects the registration with `license_subtenants_disallowed`."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('acme-eu registered', () => pm.expect([200, 201], 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "const tenant = j.tenant || j;",
                  "pm.test('acme-eu is a child of the expected parent', () => {",
                  "  pm.expect(tenant.id, 'subtenant id').to.be.a('string').and.not.empty;",
                  "  pm.expect(tenant.parentTenantId, 'parentTenantId').to.eql(pm.collectionVariables.get('tenantId'));",
                  "});",
                  "pm.collectionVariables.set('subTenantId', tenant.id);",
                  "const correlationId = j.correlationId || (j.registration && j.registration.correlationId) || tenant.correlationId;",
                  "pm.expect(correlationId, 'tenant onboarding correlation id').to.be.a('string').and.not.empty;",
                  "pm.collectionVariables.set('subTenantOnboardingCorrelationId', correlationId);"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Wait for the acme-eu onboarding to complete",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/admin/v1/tenant-onboarding/{{subTenantOnboardingCorrelationId}}",
            "description": "Polls the onboarding status row the registration created. The status is one of IN_FLIGHT, COMPLETED, COMPENSATED or ORPHANED; the request re-runs itself while the saga is still in flight. Subtenants receive the same provisioning as roots: hosted authorization server, issuer instance, verifier and a did:web identifier."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const attempt = Number(pm.collectionVariables.get('subTenantOnboardingAttempt') || 0);",
                  "pm.collectionVariables.set('subTenantOnboardingAttempt', String(attempt + 1));"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('acme-eu onboarding status returned', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const status = pm.response.json();",
                  "const attempt = Number(pm.collectionVariables.get('subTenantOnboardingAttempt') || 1);",
                  "pm.expect(status.status, 'onboarding status').to.be.oneOf(['IN_FLIGHT', 'COMPLETED', 'COMPENSATED', 'ORPHANED']);",
                  "if (status.status === 'IN_FLIGHT' && attempt < 20) {",
                  "  postman.setNextRequest('02 Wait for the acme-eu onboarding to complete');",
                  "} else {",
                  "  pm.collectionVariables.unset('subTenantOnboardingAttempt');",
                  "  pm.test('acme-eu onboarding completed', () => {",
                  "    pm.expect(status.status, 'terminal onboarding status after ' + attempt + ' poll(s)').to.eql('COMPLETED');",
                  "    pm.expect(status.tenantId, 'tenant id').to.eql(pm.collectionVariables.get('subTenantId'));",
                  "  });",
                  "  const steps = Array.isArray(status.steps) ? status.steps : [];",
                  "  const stepId = (record) => typeof record.step === 'string' ? record.step : (record.step && record.step.id) || record.stepId || record.id;",
                  "  pm.test('acme-eu onboarding provisioned every step without error', () => {",
                  "    pm.expect(steps.length, 'onboarding step timeline').to.be.greaterThan(0);",
                  "    pm.expect(steps.filter((record) => record.error).map((record) => stepId(record) + '=' + record.error), 'failed steps').to.eql([]);",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "05 List the children of the root tenant",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/admin/v1/tenants/{{tenantId}}/children?page=0&size=50",
            "description": "Lists the immediate children of the root tenant as a paginated envelope. Only acme-eu is an immediate child; acme-eu-hr hangs off acme-eu and is therefore not in this page."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('root tenant children listed', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "const children = j.data || j.items || (Array.isArray(j) ? j : []);",
                  "const subTenantId = pm.collectionVariables.get('subTenantId');",
                  "const subSubTenantId = pm.collectionVariables.get('subSubTenantId');",
                  "pm.test('the root tenant lists acme-eu as its immediate child', () => {",
                  "  const child = children.find((entry) => entry.id === subTenantId);",
                  "  pm.expect(child, 'acme-eu child row').to.be.an('object');",
                  "  pm.expect(child.slug, 'child slug').to.eql(pm.collectionVariables.get('subTenantSlug'));",
                  "  pm.expect(child.parentTenantId, 'child parent').to.eql(pm.collectionVariables.get('tenantId'));",
                  "});",
                  "pm.test('the children listing is immediate, not transitive', () => {",
                  "  pm.expect(children.map((entry) => entry.id), 'acme-eu-hr must not appear under the root').to.not.include(subSubTenantId);",
                  "});"
                ]
              }
            }
          ]
        }
      ],
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{platformAccessToken}}",
            "type": "string"
          }
        ]
      }
    },
    {
      "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.",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "01 List KMS offerings",
          "request": {
            "method": "GET",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/kms/offerings",
            "description": "Lists the KMS offerings a tenant may select, using the tenant bearer token. Typed KMS resource management is part of the platform configuration API. Only SOFTWARE, AWS_KMS, and AZURE_KEY_VAULT are tenant-visible; server-only offerings are never discoverable.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('kms offerings listed', () => pm.response.to.have.status(200));",
                  "const kinds = (pm.response.json().offerings || []).map((offering) => offering.kind).filter(Boolean);",
                  "pm.expect(kinds, 'the tenant default SOFTWARE offering is available').to.include('SOFTWARE');",
                  "pm.expect(kinds.filter((kind) => kind === 'SOFTWARE'), 'SOFTWARE is offered once').to.have.lengthOf(1);",
                  "kinds.forEach((kind) => pm.expect(['SOFTWARE', 'AWS_KMS', 'AZURE_KEY_VAULT'], 'offering kind is tenant-visible').to.include(kind));",
                  "(pm.response.json().offerings || []).forEach((offering) => {",
                  "  pm.expect(offering.supportedCredentialOwnerships, 'offering advertises credential ownership').to.be.an('array').and.not.empty;",
                  "  offering.supportedCredentialOwnerships.forEach((ownership) => pm.expect(['PRODUCT_MANAGED', 'TENANT_SUPPLIED'], 'credential ownership').to.include(ownership));",
                  "});",
                  "const text = pm.response.text();",
                  "['INTERNAL_SOFTWARE_KMS', 'internal-token-verifier', 'providerId', 'keyAlias', 'locator', 'permit'].forEach((shape) => pm.expect(text, 'offerings hide ' + shape).to.not.include(shape));"
                ]
              }
            }
          ]
        },
        {
          "name": "02 List KMS resources",
          "request": {
            "method": "GET",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/kms/resources",
            "description": "Lists the tenant's typed KMS resources through the platform configuration API. Every resource carries the opaque krh_ handle used for management and the operator-chosen providerId that addresses the same KMS on the runtime KMS REST API. Alias, revision, and locator stay hidden.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('kms resources listed', () => pm.response.to.have.status(200));",
                  "const handles = (pm.response.json().resources || []).map((resource) => resource.handle).filter(Boolean);",
                  "pm.expect(handles, 'tenant setup provisioned a KMS resource').to.have.length.of.at.least(1);",
                  "handles.forEach((handle) => pm.expect(handle, 'resource handle is opaque').to.match(/^krh_[A-Za-z0-9_-]+$/));",
                  "const resources = pm.response.json().resources || [];",
                  "pm.test('every resource publishes the provider id the runtime KMS plane uses', () => {",
                  "  resources.forEach((resource) => {",
                  "    pm.expect(resource.providerId, 'resource provider id').to.match(/^[a-z][a-z0-9-]{2,63}$/);",
                  "    pm.expect(resource.providerId, 'provider id is not the management handle').to.not.match(/^krh_/);",
                  "    pm.expect(['PRODUCT_MANAGED', 'TENANT_SUPPLIED'], 'credential ownership').to.include(resource.credentialOwnership);",
                  "  });",
                  "});",
                  "pm.collectionVariables.set('kmsResourceHandle', handles[0]);",
                  "pm.collectionVariables.set('kmsResourceProviderId', resources[0] && resources[0].providerId);",
                  "pm.collectionVariables.set('kmsResourceKind', resources[0] && resources[0].kind);",
                  "const text = pm.response.text();",
                  "['INTERNAL_SOFTWARE_KMS', 'internal-token-verifier', 'keyAlias', 'locator', 'permit'].forEach((shape) => pm.expect(text, 'resources hide ' + shape).to.not.include(shape));"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Validate tenant setup KMS resource",
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/kms/resources/{{kmsResourceHandle}}/validate",
            "description": "Validates the activation-created typed KMS resource through its opaque krh_ handle. Product DID, issuer, and verifier behavior provides key-operation evidence without exposing internal keys or selectors.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant setup KMS resource validates', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('tenant setup KMS validation returns the required public shape', () => {",
                  "  pm.expect(j.handle, 'validated resource handle').to.eql(pm.collectionVariables.get('kmsResourceHandle'));",
                  "  pm.expect(j.handle, 'validated resource handle is opaque').to.match(/^krh_[A-Za-z0-9_-]+$/);",
                  "  pm.expect(['CONFIGURED', 'DEGRADED'], 'validation state').to.include(j.state);",
                  "  pm.expect(['UNKNOWN', 'HEALTHY', 'STALE', 'DEGRADED', 'FAILED'], 'validation health').to.include(j.health);",
                  "});",
                  "const text = pm.response.text();",
                  "['INTERNAL_SOFTWARE_KMS', 'internal-token-verifier', 'providerId', 'keyAlias', 'locator', 'permit'].forEach((shape) => pm.expect(text, 'validation hides ' + shape).to.not.include(shape));"
                ]
              }
            }
          ]
        },
        {
          "name": "11 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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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);",
                  "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 didAuthority = parts.shift();",
                  "  if (!didAuthority) 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 didAuthorityHostname = didAuthority.replace(/:\\d+$/, '');",
                  "  const useGatewayAuthority = gatewayHostname === didAuthorityHostname || gatewayAuthority === didAuthority;",
                  "  const authority = useGatewayAuthority ? gatewayAuthority : didAuthority;",
                  "  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": "12 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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "13 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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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'));",
                  "const methods = Array.isArray(pm.response.json()) ? pm.response.json() : (pm.response.json().verificationMethods || pm.response.json().items || []);",
                  "pm.expect(methods.length, 'activation DID has product verification methods').to.be.at.least(2);",
                  "methods.forEach((method) => pm.expect(String(method.id || method.verificationMethodId || ''), 'verification method belongs to activation DID').to.include(pm.collectionVariables.get('did') + '#'));",
                  "const verifierMethodId = pm.collectionVariables.get('did') + '#verifier-request-object-' + pm.collectionVariables.get('verifierInstanceId');",
                  "const verifierMethod = methods.find((method) => String(method.id || method.verificationMethodId || '') === verifierMethodId);",
                  "pm.expect(verifierMethod, 'verifier request-object verification method is published').to.be.an('object');",
                  "pm.collectionVariables.set('verifierDidVerificationMethodId', verifierMethodId);"
                ]
              }
            }
          ]
        },
        {
          "name": "14 Fetch hosted activation did.json",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "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": "{{tenantPublicAuthority}}",
                "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 Bring Your Own KMS",
      "description": "Signing with keys you generate and keep in your own cloud KMS, under certificate chains from your own PKI. The external registration sub-folder registers references to keys and certificates that already exist in an authorized AWS KMS or Azure Key Vault provider; EDK never imports private key material. The platform Azure sub-folder is for operators of an on-premises platform tenant. Both ship disabled until you supply your provider values.",
      "item": [
        {
          "name": "01 Tenant Cloud KMS Resource (optional)",
          "description": "Bring your own Azure Key Vault or AWS KMS. Fill in the tenantAzure* or tenantAws* collection variables, enable this sub-folder, and run it after 04 Tenant Service Token. The create call registers the vault as a typed KMS resource, the credential call writes the client secret or secret access key once, validate proves the platform can reach it, and the last request makes it the default provider for new keys. Generated and externally registered keys never leave your KMS; EDK stores only references.",
          "disabled": true,
          "auth": {
            "type": "bearer",
            "bearer": [
              {
                "key": "token",
                "value": "{{tenantAccessToken}}",
                "type": "string"
              }
            ]
          },
          "item": [
            {
              "name": "01 Create an Azure Key Vault KMS resource",
              "request": {
                "method": "POST",
                "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/kms/resources",
                "description": "Registers your own Azure Key Vault as a KMS resource of this tenant. providerId is the name you will use for this KMS on the KMS runtime API; applicationId is the label EDK stamps on keys it generates in the vault; tenantId and clientId identify the Entra application EDK signs in with. Keys generated through this resource never leave the vault.",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json",
                    "type": "text"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"providerId\": \"{{tenantAzureProviderId}}\",\n  \"kind\": \"AZURE_KEY_VAULT\",\n  \"displayName\": \"{{tenantAzureDisplayName}}\",\n  \"configuration\": {\n    \"vaultUri\": \"{{tenantAzureVaultUri}}\",\n    \"applicationId\": \"{{tenantAzureApplicationId}}\",\n    \"tenantId\": \"{{tenantAzureTenantId}}\",\n    \"clientId\": \"{{tenantAzureClientId}}\",\n    \"hsmType\": \"KEYVAULT\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// This example needs your own provider values. Until the collection variables below are filled in it is skipped.",
                      "for (const name of [\"tenantAzureVaultUri\",\"tenantAzureTenantId\",\"tenantAzureClientId\",\"tenantAzureClientSecret\"]) {",
                      "  const value = String(pm.variables.get(name) || '').trim();",
                      "  if (!value || /^replace-with-/i.test(value)) { pm.test('Azure Key Vault example skipped: fill in ' + name + ' before enabling it', () => {}); pm.execution.skipRequest(); }",
                      "}",
                      "['tenantAzureResourceHandle', 'tenantAzureResourceVersion', 'tenantAzureCredentialSecretRef'].forEach((key) => pm.collectionVariables.unset(key));"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('Azure Key Vault KMS resource created', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.be.oneOf([201]));",
                      "const resource = pm.response.json();",
                      "pm.test('the resource is addressed by an opaque handle and your provider id', () => {",
                      "  pm.expect(resource.handle, 'resource handle').to.match(/^krh_[A-Za-z0-9_-]+$/);",
                      "  pm.expect(resource.providerId, 'provider id').to.eql(pm.variables.get('tenantAzureProviderId'));",
                      "  pm.expect(resource.kind, 'kind').to.eql('AZURE_KEY_VAULT');",
                      "  pm.expect(resource.credentialConfigured, 'no credential yet').to.eql(false);",
                      "  pm.expect(resource.credentialSecretRef, 'credential slot reference').to.match(/^kcr_[A-Za-z0-9_-]{20,180}_azure$/);",
                      "});",
                      "pm.collectionVariables.set('tenantAzureResourceHandle', resource.handle);",
                      "pm.collectionVariables.set('tenantAzureResourceVersion', String(resource.resourceVersion));",
                      "pm.collectionVariables.set('tenantAzureCredentialSecretRef', resource.credentialSecretRef);",
                      "pm.collectionVariables.set('tenantCloudKmsProviderId', resource.providerId);"
                    ]
                  }
                }
              ],
              "disabled": true
            },
            {
              "name": "02 Attach the Azure client secret",
              "request": {
                "method": "PUT",
                "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/kms/resources/{{tenantAzureResourceHandle}}/credentials/azure-key-vault",
                "description": "Writes the Entra client secret once. The request names the credential slot the create response returned and the resource version it expects; the response confirms the write without ever returning the secret.",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json",
                    "type": "text"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"expectedResourceVersion\": \"{{tenantAzureResourceVersion}}\",\n  \"clientSecretRef\": \"{{tenantAzureCredentialSecretRef}}\",\n  \"clientSecret\": \"{{tenantAzureClientSecret}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// This example needs your own provider values. Until the collection variables below are filled in it is skipped.",
                      "for (const name of [\"tenantAzureVaultUri\",\"tenantAzureTenantId\",\"tenantAzureClientId\",\"tenantAzureClientSecret\"]) {",
                      "  const value = String(pm.variables.get(name) || '').trim();",
                      "  if (!value || /^replace-with-/i.test(value)) { pm.test('Azure Key Vault example skipped: fill in ' + name + ' before enabling it', () => {}); pm.execution.skipRequest(); }",
                      "}",
                      "// expectedResourceVersion is an integer; Postman substitutes it into the JSON body unquoted.",
                      "const raw = pm.request.body.raw.replace('\"{{tenantAzureResourceVersion}}\"', String(Number(pm.collectionVariables.get('tenantAzureResourceVersion'))));",
                      "pm.request.body.update(raw);"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('Azure client secret attached', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.be.oneOf([200]));",
                      "const result = pm.response.json();",
                      "pm.test('the write advances the resource version and marks the credential present', () => {",
                      "  pm.expect(result.handle, 'resource handle').to.eql(pm.collectionVariables.get('tenantAzureResourceHandle'));",
                      "  pm.expect(result.credentialConfigured, 'credential configured').to.eql(true);",
                      "  pm.expect(result.secretRef, 'credential slot reference').to.eql(pm.collectionVariables.get('tenantAzureCredentialSecretRef'));",
                      "  pm.expect(result.resourceVersion, 'resource version').to.be.above(Number(pm.collectionVariables.get('tenantAzureResourceVersion')));",
                      "});",
                      "const responseText = pm.response.text();",
                      "pm.expect(responseText, 'the response never echoes the supplied secret').to.not.include(pm.variables.get('tenantAzureClientSecret'));",
                      "pm.expect(responseText, 'the response carries no secret value field').to.not.match(/\"(?:clientSecret|secretAccessKey|password|secretValue)\"\\s*:/i);",
                      "pm.collectionVariables.set('tenantAzureResourceVersion', String(result.resourceVersion));"
                    ]
                  }
                }
              ],
              "disabled": true
            },
            {
              "name": "03 Validate the Azure Key Vault resource",
              "request": {
                "method": "POST",
                "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/kms/resources/{{tenantAzureResourceHandle}}/validate",
                "description": "Asks the platform to reach the vault with the stored credential. A CONFIGURED state with HEALTHY health means keys can be generated and used there.",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  }
                ]
              },
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// This example needs your own provider values. Until the collection variables below are filled in it is skipped.",
                      "for (const name of [\"tenantAzureVaultUri\",\"tenantAzureTenantId\",\"tenantAzureClientId\",\"tenantAzureClientSecret\"]) {",
                      "  const value = String(pm.variables.get(name) || '').trim();",
                      "  if (!value || /^replace-with-/i.test(value)) { pm.test('Azure Key Vault example skipped: fill in ' + name + ' before enabling it', () => {}); pm.execution.skipRequest(); }",
                      "}"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('Azure Key Vault resource validated', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.be.oneOf([200]));",
                      "const result = pm.response.json();",
                      "pm.test('the vault is reachable with the stored credential', () => {",
                      "  pm.expect(result.handle, 'resource handle').to.eql(pm.collectionVariables.get('tenantAzureResourceHandle'));",
                      "  pm.expect(result.state, 'state').to.eql('CONFIGURED');",
                      "  pm.expect(['HEALTHY', 'UNKNOWN'], 'health').to.include(result.health);",
                      "});"
                    ]
                  }
                }
              ],
              "disabled": true
            },
            {
              "name": "04 Create an AWS KMS resource",
              "request": {
                "method": "POST",
                "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/kms/resources",
                "description": "Registers your own AWS KMS as a KMS resource of this tenant. region selects the KMS endpoint, applicationId is the label EDK stamps on keys it generates. Key material stays in AWS KMS.",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json",
                    "type": "text"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"providerId\": \"{{tenantAwsProviderId}}\",\n  \"kind\": \"AWS_KMS\",\n  \"displayName\": \"{{tenantAwsDisplayName}}\",\n  \"configuration\": {\n    \"region\": \"{{tenantAwsRegion}}\",\n    \"applicationId\": \"{{tenantAwsApplicationId}}\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// This example needs your own provider values. Until the collection variables below are filled in it is skipped.",
                      "for (const name of [\"tenantAwsRegion\",\"tenantAwsSecretAccessKey\"]) {",
                      "  const value = String(pm.variables.get(name) || '').trim();",
                      "  if (!value || /^replace-with-/i.test(value)) { pm.test('AWS KMS example skipped: fill in ' + name + ' before enabling it', () => {}); pm.execution.skipRequest(); }",
                      "}",
                      "['tenantAwsResourceHandle', 'tenantAwsResourceVersion', 'tenantAwsCredentialSecretRef'].forEach((key) => pm.collectionVariables.unset(key));"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('AWS KMS resource created', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.be.oneOf([201]));",
                      "const resource = pm.response.json();",
                      "pm.test('the resource is addressed by an opaque handle and your provider id', () => {",
                      "  pm.expect(resource.handle, 'resource handle').to.match(/^krh_[A-Za-z0-9_-]+$/);",
                      "  pm.expect(resource.providerId, 'provider id').to.eql(pm.variables.get('tenantAwsProviderId'));",
                      "  pm.expect(resource.kind, 'kind').to.eql('AWS_KMS');",
                      "  pm.expect(resource.credentialConfigured, 'no credential yet').to.eql(false);",
                      "  pm.expect(resource.credentialSecretRef, 'credential slot reference').to.match(/^kcr_[A-Za-z0-9_-]{20,180}_aws$/);",
                      "});",
                      "pm.collectionVariables.set('tenantAwsResourceHandle', resource.handle);",
                      "pm.collectionVariables.set('tenantAwsResourceVersion', String(resource.resourceVersion));",
                      "pm.collectionVariables.set('tenantAwsCredentialSecretRef', resource.credentialSecretRef);",
                      "pm.collectionVariables.set('tenantCloudKmsProviderId', resource.providerId);"
                    ]
                  }
                }
              ],
              "disabled": true
            },
            {
              "name": "05 Attach the AWS secret access key",
              "request": {
                "method": "PUT",
                "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/kms/resources/{{tenantAwsResourceHandle}}/credentials/aws",
                "description": "Writes the AWS secret access key once against the credential slot the create response returned. The response confirms the write without returning the key.",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json",
                    "type": "text"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"expectedResourceVersion\": \"{{tenantAwsResourceVersion}}\",\n  \"secretAccessKeySecretRef\": \"{{tenantAwsCredentialSecretRef}}\",\n  \"secretAccessKey\": \"{{tenantAwsSecretAccessKey}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// This example needs your own provider values. Until the collection variables below are filled in it is skipped.",
                      "for (const name of [\"tenantAwsRegion\",\"tenantAwsSecretAccessKey\"]) {",
                      "  const value = String(pm.variables.get(name) || '').trim();",
                      "  if (!value || /^replace-with-/i.test(value)) { pm.test('AWS KMS example skipped: fill in ' + name + ' before enabling it', () => {}); pm.execution.skipRequest(); }",
                      "}",
                      "const raw = pm.request.body.raw.replace('\"{{tenantAwsResourceVersion}}\"', String(Number(pm.collectionVariables.get('tenantAwsResourceVersion'))));",
                      "pm.request.body.update(raw);"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('AWS secret access key attached', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.be.oneOf([200]));",
                      "const result = pm.response.json();",
                      "pm.test('the write advances the resource version and marks the credential present', () => {",
                      "  pm.expect(result.handle, 'resource handle').to.eql(pm.collectionVariables.get('tenantAwsResourceHandle'));",
                      "  pm.expect(result.credentialConfigured, 'credential configured').to.eql(true);",
                      "  pm.expect(result.secretRef, 'credential slot reference').to.eql(pm.collectionVariables.get('tenantAwsCredentialSecretRef'));",
                      "});",
                      "const responseText = pm.response.text();",
                      "pm.expect(responseText, 'the response never echoes the supplied secret').to.not.include(pm.variables.get('tenantAwsSecretAccessKey'));",
                      "pm.expect(responseText, 'the response carries no secret value field').to.not.match(/\"(?:clientSecret|secretAccessKey|password|secretValue)\"\\s*:/i);",
                      "pm.collectionVariables.set('tenantAwsResourceVersion', String(result.resourceVersion));"
                    ]
                  }
                }
              ],
              "disabled": true
            },
            {
              "name": "06 Validate the AWS KMS resource",
              "request": {
                "method": "POST",
                "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/kms/resources/{{tenantAwsResourceHandle}}/validate",
                "description": "Asks the platform to reach AWS KMS in the configured region with the stored credential.",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  }
                ]
              },
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// This example needs your own provider values. Until the collection variables below are filled in it is skipped.",
                      "for (const name of [\"tenantAwsRegion\",\"tenantAwsSecretAccessKey\"]) {",
                      "  const value = String(pm.variables.get(name) || '').trim();",
                      "  if (!value || /^replace-with-/i.test(value)) { pm.test('AWS KMS example skipped: fill in ' + name + ' before enabling it', () => {}); pm.execution.skipRequest(); }",
                      "}"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('AWS KMS resource validated', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.be.oneOf([200]));",
                      "const result = pm.response.json();",
                      "pm.test('AWS KMS is reachable with the stored credential', () => {",
                      "  pm.expect(result.handle, 'resource handle').to.eql(pm.collectionVariables.get('tenantAwsResourceHandle'));",
                      "  pm.expect(result.state, 'state').to.eql('CONFIGURED');",
                      "  pm.expect(['HEALTHY', 'UNKNOWN'], 'health').to.include(result.health);",
                      "});"
                    ]
                  }
                }
              ],
              "disabled": true
            },
            {
              "name": "07 Make the cloud KMS the tenant default",
              "request": {
                "method": "PUT",
                "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/kms/default-provider",
                "description": "Selects the cloud KMS registered above as the provider new keys default to. tenantCloudKmsProviderId holds the provider id of whichever resource you created last; the KMS runtime API (folder 24) then addresses it by that id.",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json",
                    "type": "text"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"providerId\": \"{{tenantCloudKmsProviderId}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if (!String(pm.collectionVariables.get('tenantCloudKmsProviderId') || '').trim()) { pm.test('tenant default provider skipped: create the Azure Key Vault or AWS KMS resource first', () => {}); pm.execution.skipRequest(); }"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('tenant default KMS provider set', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.be.oneOf([200]));",
                      "const j = pm.response.json();",
                      "pm.test('the default now names your cloud KMS', () => {",
                      "  pm.expect(j.providerId, 'provider id').to.eql(pm.collectionVariables.get('tenantCloudKmsProviderId'));",
                      "  pm.expect(j.source, 'default source').to.eql('TENANT');",
                      "});"
                    ]
                  }
                }
              ],
              "disabled": true
            }
          ]
        },
        {
          "name": "02 External Key and Certificate Registration (optional)",
          "description": "Optional customer templates for registering references to existing cloud-provider resources. Every request is disabled by default because it requires a provider that is already active and authorized for this tenant. Replace only the collection-local example values, enable the requests you need, and run them through the tenant gateway. External key and provider-native certificate aliases are exact native provider identifiers and are stored unchanged in this tenant's reference index. For a platform-shared AWS or Azure provider, assign the native object with the cloud tag sphereon-tenant-id set to this collection's tenantId before registration. Tenant-owned providers do not require that shared-provider tag. Registration never creates or imports private key material. The DELETE examples remove only the EDK reference for externally managed resources and never delete the provider resource.",
          "auth": {
            "type": "bearer",
            "bearer": [
              {
                "key": "token",
                "value": "{{tenantAccessToken}}",
                "type": "string"
              }
            ]
          },
          "item": [
            {
              "name": "01 Register existing external key with optional kid",
              "disabled": true,
              "request": {
                "method": "POST",
                "url": "{{tenantKmsApiBaseUrl}}/keys/register",
                "description": "Registers an existing key by its exact native alias in an authorized provider. EDK does not add the tenant prefix used for platform-managed generated keys. For a platform-shared AWS or Azure provider, set sphereon-tenant-id on the native key to this collection's tenantId before running the request. The kid is optional on this route; when supplied, EDK verifies that alias and kid identify the same provider key. This does not create, import, or export key material.",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"providerId\": \"{{externalAwsKmsProviderId}}\",\n  \"alias\": \"{{externalKeyAlias}}\",\n  \"kid\": \"{{externalKeyKid}}\"\n}"
                }
              },
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const externalKmsConfigured = !String(pm.collectionVariables.get('externalAwsKmsProviderId') || '').startsWith('replace-with-') &&",
                      "  !String(pm.collectionVariables.get('externalAzureKmsProviderId') || '').startsWith('replace-with-');",
                      "if (!externalKmsConfigured) {",
                      "  pm.test('external KMS is not configured; key registration is refused', () => pm.expect([400,404]).to.include(pm.response.code));",
                      "} else {",
                      "  pm.test('external key reference registered', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(201));",
                      "  const j = pm.response.json();",
                      "  pm.expect(j.registered, 'registered').to.eql(true);",
                      "  pm.expect(j.providerId, 'provider id').to.eql(pm.collectionVariables.get('externalAwsKmsProviderId'));",
                      "  pm.expect(j.alias, 'provider-native alias is preserved').to.eql(pm.collectionVariables.get('externalKeyAlias'));",
                      "  pm.expect(j.origin, 'origin').to.eql('external');",
                      "  pm.expect(j.controlMode, 'control mode').to.eql('externally_managed');",
                      "}"
                    ]
                  }
                }
              ]
            },
            {
              "name": "02 Register existing external key by alias only",
              "disabled": true,
              "request": {
                "method": "POST",
                "url": "{{tenantKmsApiBaseUrl}}/keys/register",
                "description": "Alias-only variant of external key registration. The alias remains the exact native provider alias. For a platform-shared AWS or Azure provider, set sphereon-tenant-id on the native key to this collection's tenantId before running the request. Omit kid when the provider does not expose a stable identifier to the customer. EDK resolves and persists the provider's canonical identity without importing key material.",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"providerId\": \"{{externalAwsKmsProviderId}}\",\n  \"alias\": \"{{externalKeyAlias}}\"\n}"
                }
              },
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const externalKmsConfigured = !String(pm.collectionVariables.get('externalAwsKmsProviderId') || '').startsWith('replace-with-') &&",
                      "  !String(pm.collectionVariables.get('externalAzureKmsProviderId') || '').startsWith('replace-with-');",
                      "if (!externalKmsConfigured) {",
                      "  pm.test('external KMS is not configured; alias-only registration is refused', () => pm.expect([400,404]).to.include(pm.response.code));",
                      "} else {",
                      "  pm.test('external key reference registered without optional selector', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(201));",
                      "  const j = pm.response.json();",
                      "  pm.expect(j.registered, 'registered').to.eql(true);",
                      "  pm.expect(j.providerId, 'provider id').to.eql(pm.collectionVariables.get('externalAwsKmsProviderId'));",
                      "  pm.expect(j.alias, 'provider-native alias is preserved').to.eql(pm.collectionVariables.get('externalKeyAlias'));",
                      "  pm.expect(j.origin, 'origin').to.eql('external');",
                      "  pm.expect(j.controlMode, 'control mode').to.eql('externally_managed');",
                      "}"
                    ]
                  }
                }
              ]
            },
            {
              "name": "02a Read external key public metadata",
              "disabled": true,
              "request": {
                "method": "GET",
                "url": "{{tenantKmsApiBaseUrl}}/keys/{{externalKeyAlias}}?providerId={{externalAwsKmsProviderId}}",
                "description": "Reads the registered external key through the tenant gateway. When the provider public JWK contains x5c, x5t, or x5t#S256, those public members remain visible on the key response without creating a certificate-management resource. Private JWK members and x5u must not be returned.",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  }
                ]
              },
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const externalKmsConfigured = !String(pm.collectionVariables.get('externalAwsKmsProviderId') || '').startsWith('replace-with-') &&",
                      "  !String(pm.collectionVariables.get('externalAzureKmsProviderId') || '').startsWith('replace-with-');",
                      "if (!externalKmsConfigured) {",
                      "  pm.test('external KMS is not configured; key metadata read is refused', () => pm.expect([400,404]).to.include(pm.response.code));",
                      "} else {",
                      "  pm.test('external key public metadata returned', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                      "  const jwk = pm.response.json().keyInfo.key;",
                      "  pm.test('private and remote-locator JWK members are absent', () => {",
                      "    ['d', 'p', 'q', 'dp', 'dq', 'qi', 'oth', 'k', 'x5u'].forEach((member) => pm.expect(jwk, member).not.to.have.property(member));",
                      "  });",
                      "  pm.test('provider certificate metadata keeps its public wire shape when present', () => {",
                      "    if (Object.prototype.hasOwnProperty.call(jwk, 'x5c')) pm.expect(jwk.x5c, 'x5c').to.be.an('array');",
                      "    if (Object.prototype.hasOwnProperty.call(jwk, 'x5t')) pm.expect(jwk.x5t, 'x5t').to.be.a('string');",
                      "    if (Object.prototype.hasOwnProperty.call(jwk, 'x5t#S256')) pm.expect(jwk['x5t#S256'], 'x5t#S256').to.be.a('string');",
                      "  });",
                      "}"
                    ]
                  }
                }
              ]
            },
            {
              "name": "03 Register AWS-linked stored public certificate",
              "disabled": true,
              "request": {
                "method": "POST",
                "url": "{{tenantKmsApiBaseUrl}}/certificates/register",
                "description": "Registers a public leaf-to-root DER chain linked to the existing AWS KMS key. The wire source value is stored_public_material, corresponding to STORED_PUBLIC_MATERIAL. AWS KMS supplies key operations but is not a certificate store, so only public DER is sent and persisted.",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"providerId\": \"{{externalAwsKmsProviderId}}\",\n  \"alias\": \"{{externalAwsCertificateAlias}}\",\n  \"kind\": \"key_certificate_chain\",\n  \"source\": \"stored_public_material\",\n  \"linkedKeyAlias\": \"{{externalKeyAlias}}\",\n  \"linkedKeyKid\": \"{{externalKeyKid}}\",\n  \"certificateChain\": [\n    \"{{externalAwsCertificateDerBase64}}\"\n  ]\n}"
                }
              },
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const externalKmsConfigured = !String(pm.collectionVariables.get('externalAwsKmsProviderId') || '').startsWith('replace-with-') &&",
                      "  !String(pm.collectionVariables.get('externalAzureKmsProviderId') || '').startsWith('replace-with-');",
                      "if (!externalKmsConfigured) {",
                      "  pm.test('external KMS is not configured; certificate registration is refused', () => pm.expect([400,404]).to.include(pm.response.code));",
                      "} else {",
                      "  pm.test('AWS-linked public certificate reference registered', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(201));",
                      "  const j = pm.response.json();",
                      "  pm.expect(j.providerId, 'provider id').to.eql(pm.collectionVariables.get('externalAwsKmsProviderId'));",
                      "  pm.expect(j.kind, 'kind').to.eql('key_certificate_chain');",
                      "  pm.expect(j.source, 'source').to.eql('stored_public_material');",
                      "  pm.expect(j.origin, 'origin').to.eql('external');",
                      "  pm.expect(j.controlMode, 'control mode').to.eql('externally_managed');",
                      "}"
                    ]
                  }
                }
              ]
            },
            {
              "name": "04 Register Azure provider-native leaf certificate",
              "disabled": true,
              "request": {
                "method": "POST",
                "url": "{{tenantKmsApiBaseUrl}}/certificates/register",
                "description": "Registers an Azure Key Vault provider-native leaf certificate by its exact existing Azure alias. EDK stores that alias unchanged. For a platform-shared provider, set sphereon-tenant-id on the Azure certificate to this collection's tenantId before running the request. The wire source value is provider_native, corresponding to PROVIDER_NATIVE. Azure leaf reads are delegated to the provider; this example does not submit a chain, follow AIA URLs, or export a PFX.",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"providerId\": \"{{externalAzureKmsProviderId}}\",\n  \"alias\": \"{{externalAzureCertificateAlias}}\",\n  \"providerCertificateId\": \"{{externalAzureProviderCertificateId}}\",\n  \"kind\": \"trusted_certificate\",\n  \"source\": \"provider_native\"\n}"
                }
              },
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const externalKmsConfigured = !String(pm.collectionVariables.get('externalAwsKmsProviderId') || '').startsWith('replace-with-') &&",
                      "  !String(pm.collectionVariables.get('externalAzureKmsProviderId') || '').startsWith('replace-with-');",
                      "if (!externalKmsConfigured) {",
                      "  pm.test('external KMS is not configured; provider-native registration is refused', () => pm.expect([400,404]).to.include(pm.response.code));",
                      "} else {",
                      "  pm.test('Azure provider-native leaf reference registered', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(201));",
                      "  const j = pm.response.json();",
                      "  pm.expect(j.providerId, 'provider id').to.eql(pm.collectionVariables.get('externalAzureKmsProviderId'));",
                      "  pm.expect(j.alias, 'provider-native alias is preserved').to.eql(pm.collectionVariables.get('externalAzureCertificateAlias'));",
                      "  pm.expect(j.kind, 'kind').to.eql('trusted_certificate');",
                      "  pm.expect(j.source, 'source').to.eql('provider_native');",
                      "  pm.expect(j.origin, 'origin').to.eql('external');",
                      "  pm.expect(j.controlMode, 'control mode').to.eql('externally_managed');",
                      "}"
                    ]
                  }
                }
              ]
            },
            {
              "name": "05 Read Azure provider-native leaf certificate",
              "disabled": true,
              "request": {
                "method": "GET",
                "url": "{{tenantKmsApiBaseUrl}}/certificates/{{externalAzureCertificateAlias}}?providerId={{externalAzureKmsProviderId}}",
                "description": "Reads the registered Azure provider-native leaf through the tenant gateway. The provider performs the read; the walkthrough does not enumerate the provider or expose a backend locator.",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  }
                ]
              },
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const externalKmsConfigured = !String(pm.collectionVariables.get('externalAwsKmsProviderId') || '').startsWith('replace-with-') &&",
                      "  !String(pm.collectionVariables.get('externalAzureKmsProviderId') || '').startsWith('replace-with-');",
                      "if (!externalKmsConfigured) {",
                      "  pm.test('external KMS is not configured; certificate read is refused', () => pm.expect([400,404]).to.include(pm.response.code));",
                      "} else {",
                      "  pm.test('Azure provider-native leaf returned', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                      "}"
                    ]
                  }
                }
              ]
            },
            {
              "name": "06 Delete external key reference only",
              "disabled": true,
              "request": {
                "method": "DELETE",
                "url": "{{tenantKmsApiBaseUrl}}/keys/{{externalKeyAlias}}?providerId={{externalAwsKmsProviderId}}",
                "description": "Reference-only DELETE for an EXTERNALLY_MANAGED key. It removes the tenant-local EDK reference and must not delete or modify the existing provider key. Repeating DELETE after the local reference is removed is idempotent and returns 204. Enable only after the registration example has been verified.",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  }
                ]
              },
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const externalKmsConfigured = !String(pm.collectionVariables.get('externalAwsKmsProviderId') || '').startsWith('replace-with-') &&",
                      "  !String(pm.collectionVariables.get('externalAzureKmsProviderId') || '').startsWith('replace-with-');",
                      "if (!externalKmsConfigured) {",
                      "  pm.test('external KMS is not configured; key deletion is refused', () => pm.expect([400,404]).to.include(pm.response.code));",
                      "} else {",
                      "  pm.test('external key reference removal is idempotent', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(204));",
                      "}"
                    ]
                  }
                }
              ]
            },
            {
              "name": "07 Delete external certificate reference only",
              "disabled": true,
              "request": {
                "method": "DELETE",
                "url": "{{tenantKmsApiBaseUrl}}/certificate-chains/{{externalAwsCertificateAlias}}?providerId={{externalAwsKmsProviderId}}",
                "description": "Reference-only DELETE for an EXTERNALLY_MANAGED certificate reference. It removes the tenant-local EDK reference and leaves the AWS-linked public certificate material and KMS key untouched. Repeating DELETE after the local reference is removed is idempotent and returns 204.",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  }
                ]
              },
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "const externalKmsConfigured = !String(pm.collectionVariables.get('externalAwsKmsProviderId') || '').startsWith('replace-with-') &&",
                      "  !String(pm.collectionVariables.get('externalAzureKmsProviderId') || '').startsWith('replace-with-');",
                      "if (!externalKmsConfigured) {",
                      "  pm.test('external KMS is not configured; certificate deletion is refused', () => pm.expect([400,404]).to.include(pm.response.code));",
                      "} else {",
                      "  pm.test('external certificate reference removal is idempotent', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(204));",
                      "}"
                    ]
                  }
                }
              ]
            }
          ]
        },
        {
          "name": "03 Platform Azure Key Vault (operators, optional)",
          "description": "Explicit opt-in platform-operator checkpoint. Creates or reuses one exactly matching Azure Key Vault resource, attaches a write-only client secret only when needed, and does not offer, assign, or create keys.",
          "disabled": true,
          "item": [
            {
              "name": "01 Resolve exact platform Azure resource",
              "request": {
                "method": "GET",
                "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{platformTenantId}}/kms/resources",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ]
              },
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if(String(pm.variables.get('platformAzureProvisionEnabled')||'').toLowerCase()!=='true'){pm.execution.skipRequest();}",
                      "const defaults={platformAzureProviderId:'azure-shared-signing',platformAzureApplicationId:'edk-signing',platformAzureDisplayName:'Platform Azure Key Vault signing'}; for(const [name,value] of Object.entries(defaults)) if(!String(pm.variables.get(name)||'').trim()) pm.collectionVariables.set(name,value);",
                      "const required=['platformAccessToken','platformTenantId','platformAzureVaultUri','platformAzureTenantId','platformAzureClientId','platformAzureClientSecret','platformAzureProviderId','platformAzureApplicationId','platformAzureDisplayName']; for(const name of required){const value=String(pm.variables.get(name)||'').trim();if(!value||/^PASTE-/i.test(value)){pm.test('06c preflight rejects unresolved input',()=>{throw new Error('Required platform Azure input is unresolved: '+name);});pm.execution.skipRequest();}}",
                      "const token=String(pm.variables.get('platformAccessToken'));const parts=token.split('.');if(parts.length!==3){pm.execution.skipRequest();throw new Error('platformAccessToken must be a JWT');}let payload;try{const s=parts[1].replace(/-/g,'+').replace(/_/g,'/');payload=JSON.parse(CryptoJS.enc.Base64.parse(s+'='.repeat((4-s.length%4)%4)).toString(CryptoJS.enc.Utf8));}catch(_){pm.execution.skipRequest();throw new Error('platformAccessToken payload is invalid');}",
                      "const roles=payload.roles||(payload.realm_access&&payload.realm_access.roles)||[];const binding=payload.tenant_id||payload.tenantId||payload.tenant;if(!Array.isArray(roles)||!roles.includes('platform-admin')||binding!==pm.variables.get('platformTenantId')||binding!=='platform'){pm.execution.skipRequest();throw new Error('platformAccessToken is not bound to the platform-admin tenant');}",
                      "if(pm.variables.get('platformAzureApplicationId')===pm.variables.get('platformAzureClientId')){pm.execution.skipRequest();throw new Error('Azure applicationId must be distinct from OAuth clientId');}",
                      "['platformAzureResourceHandle','platformAzureResourceVersion','platformAzureResourceState','platformAzureCredentialSecretRef','platformAzureAlreadyConfigured','platformAzureLookupValidated','platformAzureConfigurationValidated'].forEach(name=>pm.collectionVariables.unset(name));"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.expect(pm.response.code,'platform resources status').to.eql(200);",
                      "const page=pm.response.json();pm.expect(page).to.be.an('object');pm.expect(page.resources,'resources').to.be.an('array');const matches=page.resources.filter(candidate=>candidate.providerId===pm.variables.get('platformAzureProviderId'));pm.expect(matches.length,'providerId must be unique').to.be.at.most(1);if(matches.length===1){const resource=matches[0];",
                      "pm.expect(resource.handle).to.match(/^krh_[A-Za-z0-9_-]+$/);pm.expect(resource.providerId).to.eql(pm.variables.get('platformAzureProviderId'));pm.expect(resource.kind).to.eql('AZURE_KEY_VAULT');pm.expect(resource.displayName).to.eql(pm.variables.get('platformAzureDisplayName'));pm.expect(resource.credentialOwnership).to.eql('PRODUCT_MANAGED');pm.expect(['CONFIGURED','DEGRADED']).to.include(resource.state);pm.expect(['UNKNOWN','HEALTHY','STALE','DEGRADED','FAILED']).to.include(resource.health);pm.expect(Number.isInteger(resource.resourceVersion)&&resource.resourceVersion>0,'positive integer resourceVersion').to.eql(true);pm.expect(resource.credentialConfigured).to.be.a('boolean');pm.expect(resource.credentialSecretRef).to.be.a('string').and.not.empty;pm.expect(resource.providerAssignmentId).to.be.oneOf([null,undefined]);",
                      "const responseText=pm.response.text();pm.expect(responseText,'response omits supplied credential').to.not.include(pm.variables.get('platformAzureClientSecret'));pm.expect(responseText,'response has no secret value field').to.not.match(/\"(?:clientSecret|password|secretValue)\"\\s*:/i);",
                      "pm.collectionVariables.set('platformAzureResourceHandle',resource.handle);pm.collectionVariables.set('platformAzureResourceVersion',String(resource.resourceVersion));pm.collectionVariables.set('platformAzureResourceState',resource.state);pm.collectionVariables.set('platformAzureCredentialSecretRef',resource.credentialSecretRef);pm.collectionVariables.set('platformAzureAlreadyConfigured',String(resource.credentialConfigured===true));",
                      "}else{['platformAzureResourceHandle','platformAzureResourceVersion','platformAzureResourceState','platformAzureCredentialSecretRef','platformAzureAlreadyConfigured'].forEach(name=>pm.collectionVariables.unset(name));}pm.collectionVariables.set('platformAzureLookupValidated','true');"
                    ]
                  }
                }
              ]
            },
            {
              "name": "02 Create platform Azure resource if absent",
              "request": {
                "method": "POST",
                "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{platformTenantId}}/kms/resources",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"providerId\": \"{{platformAzureProviderId}}\",\n  \"kind\": \"AZURE_KEY_VAULT\",\n  \"displayName\": \"{{platformAzureDisplayName}}\",\n  \"configuration\": {\n    \"vaultUri\": \"{{platformAzureVaultUri}}\",\n    \"applicationId\": \"{{platformAzureApplicationId}}\",\n    \"tenantId\": \"{{platformAzureTenantId}}\",\n    \"clientId\": \"{{platformAzureClientId}}\",\n    \"hsmType\": \"KEYVAULT\"\n  }\n}"
                }
              },
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if(String(pm.variables.get('platformAzureProvisionEnabled')||'').toLowerCase()!=='true'){pm.execution.skipRequest();}",
                      "const defaults={platformAzureProviderId:'azure-shared-signing',platformAzureApplicationId:'edk-signing',platformAzureDisplayName:'Platform Azure Key Vault signing'}; for(const [name,value] of Object.entries(defaults)) if(!String(pm.variables.get(name)||'').trim()) pm.collectionVariables.set(name,value);",
                      "const required=['platformAccessToken','platformTenantId','platformAzureVaultUri','platformAzureTenantId','platformAzureClientId','platformAzureClientSecret','platformAzureProviderId','platformAzureApplicationId','platformAzureDisplayName']; for(const name of required){const value=String(pm.variables.get(name)||'').trim();if(!value||/^PASTE-/i.test(value)){pm.test('06c preflight rejects unresolved input',()=>{throw new Error('Required platform Azure input is unresolved: '+name);});pm.execution.skipRequest();}}",
                      "const token=String(pm.variables.get('platformAccessToken'));const parts=token.split('.');if(parts.length!==3){pm.execution.skipRequest();throw new Error('platformAccessToken must be a JWT');}let payload;try{const s=parts[1].replace(/-/g,'+').replace(/_/g,'/');payload=JSON.parse(CryptoJS.enc.Base64.parse(s+'='.repeat((4-s.length%4)%4)).toString(CryptoJS.enc.Utf8));}catch(_){pm.execution.skipRequest();throw new Error('platformAccessToken payload is invalid');}",
                      "const roles=payload.roles||(payload.realm_access&&payload.realm_access.roles)||[];const binding=payload.tenant_id||payload.tenantId||payload.tenant;if(!Array.isArray(roles)||!roles.includes('platform-admin')||binding!==pm.variables.get('platformTenantId')||binding!=='platform'){pm.execution.skipRequest();throw new Error('platformAccessToken is not bound to the platform-admin tenant');}",
                      "if(pm.variables.get('platformAzureApplicationId')===pm.variables.get('platformAzureClientId')){pm.execution.skipRequest();throw new Error('Azure applicationId must be distinct from OAuth clientId');}",
                      "if(pm.collectionVariables.get('platformAzureLookupValidated')!=='true'){pm.test('06c lookup must validate before creation',()=>{throw new Error('Platform Azure lookup did not validate');});pm.execution.skipRequest();}",
                      "if(pm.collectionVariables.get('platformAzureResourceHandle'))pm.execution.skipRequest();"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.expect(pm.response.code,'platform Azure create status').to.eql(201);",
                      "const resource=pm.response.json();pm.expect(resource.state).to.eql('DEGRADED');pm.expect(resource.credentialConfigured).to.eql(false);pm.expect(resource.health).to.eql('UNKNOWN');",
                      "pm.expect(resource.handle).to.match(/^krh_[A-Za-z0-9_-]+$/);pm.expect(resource.providerId).to.eql(pm.variables.get('platformAzureProviderId'));pm.expect(resource.kind).to.eql('AZURE_KEY_VAULT');pm.expect(resource.displayName).to.eql(pm.variables.get('platformAzureDisplayName'));pm.expect(resource.credentialOwnership).to.eql('PRODUCT_MANAGED');pm.expect(['CONFIGURED','DEGRADED']).to.include(resource.state);pm.expect(['UNKNOWN','HEALTHY','STALE','DEGRADED','FAILED']).to.include(resource.health);pm.expect(Number.isInteger(resource.resourceVersion)&&resource.resourceVersion>0,'positive integer resourceVersion').to.eql(true);pm.expect(resource.credentialConfigured).to.be.a('boolean');pm.expect(resource.credentialSecretRef).to.be.a('string').and.not.empty;pm.expect(resource.providerAssignmentId).to.be.oneOf([null,undefined]);",
                      "const responseText=pm.response.text();pm.expect(responseText,'response omits supplied credential').to.not.include(pm.variables.get('platformAzureClientSecret'));pm.expect(responseText,'response has no secret value field').to.not.match(/\"(?:clientSecret|password|secretValue)\"\\s*:/i);",
                      "pm.collectionVariables.set('platformAzureResourceHandle',resource.handle);pm.collectionVariables.set('platformAzureResourceVersion',String(resource.resourceVersion));pm.collectionVariables.set('platformAzureResourceState',resource.state);pm.collectionVariables.set('platformAzureCredentialSecretRef',resource.credentialSecretRef);pm.collectionVariables.set('platformAzureAlreadyConfigured',String(resource.credentialConfigured===true));"
                    ]
                  }
                }
              ]
            },
            {
              "name": "03 Verify exact Azure configuration before credentials",
              "request": {
                "method": "GET",
                "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{platformTenantId}}/kms/resources/{{platformAzureResourceHandle}}",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ]
              },
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if(String(pm.variables.get('platformAzureProvisionEnabled')||'').toLowerCase()!=='true'){pm.execution.skipRequest();}",
                      "const defaults={platformAzureProviderId:'azure-shared-signing',platformAzureApplicationId:'edk-signing',platformAzureDisplayName:'Platform Azure Key Vault signing'}; for(const [name,value] of Object.entries(defaults)) if(!String(pm.variables.get(name)||'').trim()) pm.collectionVariables.set(name,value);",
                      "const required=['platformAccessToken','platformTenantId','platformAzureVaultUri','platformAzureTenantId','platformAzureClientId','platformAzureClientSecret','platformAzureProviderId','platformAzureApplicationId','platformAzureDisplayName']; for(const name of required){const value=String(pm.variables.get(name)||'').trim();if(!value||/^PASTE-/i.test(value)){pm.test('06c preflight rejects unresolved input',()=>{throw new Error('Required platform Azure input is unresolved: '+name);});pm.execution.skipRequest();}}",
                      "const token=String(pm.variables.get('platformAccessToken'));const parts=token.split('.');if(parts.length!==3){pm.execution.skipRequest();throw new Error('platformAccessToken must be a JWT');}let payload;try{const s=parts[1].replace(/-/g,'+').replace(/_/g,'/');payload=JSON.parse(CryptoJS.enc.Base64.parse(s+'='.repeat((4-s.length%4)%4)).toString(CryptoJS.enc.Utf8));}catch(_){pm.execution.skipRequest();throw new Error('platformAccessToken payload is invalid');}",
                      "const roles=payload.roles||(payload.realm_access&&payload.realm_access.roles)||[];const binding=payload.tenant_id||payload.tenantId||payload.tenant;if(!Array.isArray(roles)||!roles.includes('platform-admin')||binding!==pm.variables.get('platformTenantId')||binding!=='platform'){pm.execution.skipRequest();throw new Error('platformAccessToken is not bound to the platform-admin tenant');}",
                      "if(pm.variables.get('platformAzureApplicationId')===pm.variables.get('platformAzureClientId')){pm.execution.skipRequest();throw new Error('Azure applicationId must be distinct from OAuth clientId');}",
                      "if(pm.collectionVariables.get('platformAzureLookupValidated')!=='true'){pm.test('06c lookup must validate before creation',()=>{throw new Error('Platform Azure lookup did not validate');});pm.execution.skipRequest();}",
                      "for(const name of ['platformAzureResourceHandle','platformAzureResourceVersion','platformAzureCredentialSecretRef'])if(!String(pm.collectionVariables.get(name)||'').trim()){pm.test('06c requires response-derived resource identity',()=>{throw new Error('Required response-derived Azure resource value is unresolved: '+name);});pm.execution.skipRequest();}",
                      "pm.collectionVariables.unset('platformAzureConfigurationValidated');"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.expect(pm.response.code,'Azure detail status').to.eql(200);",
                      "const resource=pm.response.json();pm.expect(resource.handle).to.eql(pm.collectionVariables.get('platformAzureResourceHandle'));pm.expect(resource.providerId).to.eql(pm.variables.get('platformAzureProviderId'));pm.expect(resource.kind).to.eql('AZURE_KEY_VAULT');pm.expect(resource.credentialOwnership).to.eql('PRODUCT_MANAGED');pm.expect(['CONFIGURED','DEGRADED']).to.include(resource.state);pm.expect(['UNKNOWN','HEALTHY','STALE','DEGRADED','FAILED']).to.include(resource.health);pm.expect(resource.resourceVersion).to.eql(Number(pm.collectionVariables.get('platformAzureResourceVersion')));pm.expect(resource.credentialConfigured).to.eql(pm.collectionVariables.get('platformAzureAlreadyConfigured')==='true');pm.expect(resource.credentialSecretRef).to.be.oneOf([null,undefined]);",
                      "pm.expect(resource.configuration).to.deep.eql({vaultUri:pm.variables.get('platformAzureVaultUri'),applicationId:pm.variables.get('platformAzureApplicationId'),tenantId:pm.variables.get('platformAzureTenantId'),clientId:pm.variables.get('platformAzureClientId'),hsmType:'KEYVAULT'});",
                      "const responseText=pm.response.text();pm.expect(responseText,'response omits supplied credential').to.not.include(pm.variables.get('platformAzureClientSecret'));pm.expect(responseText,'response has no secret value field').to.not.match(/\"(?:clientSecret|password|secretValue)\"\\s*:/i);",
                      "pm.expect(resource.state).to.eql(pm.collectionVariables.get('platformAzureResourceState'));pm.collectionVariables.set('platformAzureConfigurationValidated','true');"
                    ]
                  }
                }
              ]
            },
            {
              "name": "04 Attach write-only Azure client secret if needed",
              "request": {
                "method": "PUT",
                "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{platformTenantId}}/kms/resources/{{platformAzureResourceHandle}}/credentials/azure-key-vault",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"expectedResourceVersion\": {{platformAzureResourceVersion}},\n  \"clientSecretRef\": \"{{platformAzureCredentialSecretRef}}\",\n  \"clientSecret\": \"{{platformAzureClientSecret}}\"\n}"
                }
              },
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if(String(pm.variables.get('platformAzureProvisionEnabled')||'').toLowerCase()!=='true'){pm.execution.skipRequest();}",
                      "const defaults={platformAzureProviderId:'azure-shared-signing',platformAzureApplicationId:'edk-signing',platformAzureDisplayName:'Platform Azure Key Vault signing'}; for(const [name,value] of Object.entries(defaults)) if(!String(pm.variables.get(name)||'').trim()) pm.collectionVariables.set(name,value);",
                      "const required=['platformAccessToken','platformTenantId','platformAzureVaultUri','platformAzureTenantId','platformAzureClientId','platformAzureClientSecret','platformAzureProviderId','platformAzureApplicationId','platformAzureDisplayName']; for(const name of required){const value=String(pm.variables.get(name)||'').trim();if(!value||/^PASTE-/i.test(value)){pm.test('06c preflight rejects unresolved input',()=>{throw new Error('Required platform Azure input is unresolved: '+name);});pm.execution.skipRequest();}}",
                      "const token=String(pm.variables.get('platformAccessToken'));const parts=token.split('.');if(parts.length!==3){pm.execution.skipRequest();throw new Error('platformAccessToken must be a JWT');}let payload;try{const s=parts[1].replace(/-/g,'+').replace(/_/g,'/');payload=JSON.parse(CryptoJS.enc.Base64.parse(s+'='.repeat((4-s.length%4)%4)).toString(CryptoJS.enc.Utf8));}catch(_){pm.execution.skipRequest();throw new Error('platformAccessToken payload is invalid');}",
                      "const roles=payload.roles||(payload.realm_access&&payload.realm_access.roles)||[];const binding=payload.tenant_id||payload.tenantId||payload.tenant;if(!Array.isArray(roles)||!roles.includes('platform-admin')||binding!==pm.variables.get('platformTenantId')||binding!=='platform'){pm.execution.skipRequest();throw new Error('platformAccessToken is not bound to the platform-admin tenant');}",
                      "if(pm.variables.get('platformAzureApplicationId')===pm.variables.get('platformAzureClientId')){pm.execution.skipRequest();throw new Error('Azure applicationId must be distinct from OAuth clientId');}",
                      "for(const name of ['platformAzureResourceHandle','platformAzureResourceVersion','platformAzureCredentialSecretRef'])if(!String(pm.collectionVariables.get(name)||'').trim()){pm.test('06c requires response-derived resource identity',()=>{throw new Error('Required response-derived Azure resource value is unresolved: '+name);});pm.execution.skipRequest();}",
                      "if(pm.collectionVariables.get('platformAzureConfigurationValidated')!=='true'){pm.test('06c configuration must validate before credential mutation',()=>{throw new Error('Platform Azure configuration did not validate');});pm.execution.skipRequest();}",
                      "pm.collectionVariables.unset('platformAzureCredentialWriteValidated');if(pm.collectionVariables.get('platformAzureAlreadyConfigured')==='true')pm.execution.skipRequest();"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.expect(pm.response.code,'Azure credential write status').to.eql(200);",
                      "const result=pm.response.json();pm.expect(result.handle).to.eql(pm.collectionVariables.get('platformAzureResourceHandle'));pm.expect(result.credentialConfigured).to.eql(true);pm.expect(result.secretRef).to.eql(pm.collectionVariables.get('platformAzureCredentialSecretRef'));pm.expect(Number.isInteger(result.resourceVersion)&&result.resourceVersion>Number(pm.collectionVariables.get('platformAzureResourceVersion')),'credential write advances version').to.eql(true);",
                      "const responseText=pm.response.text();pm.expect(responseText,'response omits supplied credential').to.not.include(pm.variables.get('platformAzureClientSecret'));pm.expect(responseText,'response has no secret value field').to.not.match(/\"(?:clientSecret|password|secretValue)\"\\s*:/i);",
                      "pm.collectionVariables.set('platformAzureResourceVersion',String(result.resourceVersion));pm.collectionVariables.set('platformAzureAlreadyConfigured','true');pm.collectionVariables.set('platformAzureCredentialWriteValidated','true');"
                    ]
                  }
                }
              ]
            },
            {
              "name": "05 Read configured platform Azure resource",
              "request": {
                "method": "GET",
                "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{platformTenantId}}/kms/resources/{{platformAzureResourceHandle}}",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ]
              },
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "if(String(pm.variables.get('platformAzureProvisionEnabled')||'').toLowerCase()!=='true'){pm.execution.skipRequest();}",
                      "const defaults={platformAzureProviderId:'azure-shared-signing',platformAzureApplicationId:'edk-signing',platformAzureDisplayName:'Platform Azure Key Vault signing'}; for(const [name,value] of Object.entries(defaults)) if(!String(pm.variables.get(name)||'').trim()) pm.collectionVariables.set(name,value);",
                      "const required=['platformAccessToken','platformTenantId','platformAzureVaultUri','platformAzureTenantId','platformAzureClientId','platformAzureClientSecret','platformAzureProviderId','platformAzureApplicationId','platformAzureDisplayName']; for(const name of required){const value=String(pm.variables.get(name)||'').trim();if(!value||/^PASTE-/i.test(value)){pm.test('06c preflight rejects unresolved input',()=>{throw new Error('Required platform Azure input is unresolved: '+name);});pm.execution.skipRequest();}}",
                      "const token=String(pm.variables.get('platformAccessToken'));const parts=token.split('.');if(parts.length!==3){pm.execution.skipRequest();throw new Error('platformAccessToken must be a JWT');}let payload;try{const s=parts[1].replace(/-/g,'+').replace(/_/g,'/');payload=JSON.parse(CryptoJS.enc.Base64.parse(s+'='.repeat((4-s.length%4)%4)).toString(CryptoJS.enc.Utf8));}catch(_){pm.execution.skipRequest();throw new Error('platformAccessToken payload is invalid');}",
                      "const roles=payload.roles||(payload.realm_access&&payload.realm_access.roles)||[];const binding=payload.tenant_id||payload.tenantId||payload.tenant;if(!Array.isArray(roles)||!roles.includes('platform-admin')||binding!==pm.variables.get('platformTenantId')||binding!=='platform'){pm.execution.skipRequest();throw new Error('platformAccessToken is not bound to the platform-admin tenant');}",
                      "if(pm.variables.get('platformAzureApplicationId')===pm.variables.get('platformAzureClientId')){pm.execution.skipRequest();throw new Error('Azure applicationId must be distinct from OAuth clientId');}",
                      "if(!(pm.collectionVariables.get('platformAzureConfigurationValidated')==='true'&&(pm.collectionVariables.get('platformAzureAlreadyConfigured')==='true'||pm.collectionVariables.get('platformAzureCredentialWriteValidated')==='true'))){pm.test('06c credential path must complete before final read',()=>{throw new Error('Platform Azure credential path did not complete');});pm.execution.skipRequest();}",
                      "for(const name of ['platformAzureResourceHandle','platformAzureResourceVersion','platformAzureCredentialSecretRef'])if(!String(pm.collectionVariables.get(name)||'').trim()){pm.test('06c requires response-derived resource identity',()=>{throw new Error('Required response-derived Azure resource value is unresolved: '+name);});pm.execution.skipRequest();}"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.expect(pm.response.code,'configured Azure detail status').to.eql(200);",
                      "const resource=pm.response.json();pm.expect(resource.handle).to.eql(pm.collectionVariables.get('platformAzureResourceHandle'));pm.expect(resource.providerId).to.eql(pm.variables.get('platformAzureProviderId'));pm.expect(resource.kind).to.eql('AZURE_KEY_VAULT');pm.expect(resource.credentialOwnership).to.eql('PRODUCT_MANAGED');pm.expect(['CONFIGURED','DEGRADED']).to.include(resource.state);pm.expect(['UNKNOWN','HEALTHY','STALE','DEGRADED','FAILED']).to.include(resource.health);pm.expect(resource.resourceVersion).to.eql(Number(pm.collectionVariables.get('platformAzureResourceVersion')));pm.expect(resource.credentialConfigured).to.eql(pm.collectionVariables.get('platformAzureAlreadyConfigured')==='true');pm.expect(resource.credentialSecretRef).to.be.oneOf([null,undefined]);",
                      "pm.expect(resource.configuration).to.deep.eql({vaultUri:pm.variables.get('platformAzureVaultUri'),applicationId:pm.variables.get('platformAzureApplicationId'),tenantId:pm.variables.get('platformAzureTenantId'),clientId:pm.variables.get('platformAzureClientId'),hsmType:'KEYVAULT'});",
                      "const responseText=pm.response.text();pm.expect(responseText,'response omits supplied credential').to.not.include(pm.variables.get('platformAzureClientSecret'));pm.expect(responseText,'response has no secret value field').to.not.match(/\"(?:clientSecret|password|secretValue)\"\\s*:/i);",
                      "pm.expect(resource.credentialConfigured).to.eql(true);"
                    ]
                  }
                }
              ]
            }
          ]
        }
      ],
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{platformAccessToken}}",
            "type": "string"
          }
        ]
      }
    },
    {
      "name": "08 KMS Provider Sharing",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "description": "How the platform offers one of its KMSes to a tenant and how the tenant enables it and selects its default provider. The runtime provider list at the end is where the platform configuration plane (opaque resource handles) meets the KMS runtime plane (provider ids).",
      "item": [
        {
          "name": "01 Create shareable platform KMS resource",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{platformAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "POST",
            "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{platformTenantId}}/kms/resources",
            "description": "Creates a MEMORY-backed SOFTWARE KMS owned by the platform tenant. It exists only to be offered to the walkthrough tenant and is detached and retired at the end of this folder.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"providerId\": \"{{platformKmsProviderId}}\",\n  \"kind\": \"SOFTWARE\",\n  \"displayName\": \"{{platformKmsDisplayName}}\",\n  \"configuration\": {\n    \"storageMode\": \"MEMORY\"\n  }\n}"
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "['platformKmsProviderId', 'platformKmsDisplayName', 'platformKmsResourceHandle', 'platformKmsResourceVersion'].forEach((key) => pm.collectionVariables.unset(key));",
                  "const suffix = pm.variables.replaceIn('{{$randomUUID}}').split('-')[0];",
                  "pm.collectionVariables.set('platformKmsProviderId', 'platform-shared-' + suffix);",
                  "pm.collectionVariables.set('platformKmsDisplayName', 'Platform shared SOFTWARE ' + suffix);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('platform-owned KMS resource created', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(201));",
                  "const j = pm.response.json();",
                  "pm.test('the platform KMS is addressable on both planes', () => {",
                  "  pm.expect(j.handle, 'opaque resource handle').to.match(/^krh_[A-Za-z0-9_-]+$/);",
                  "  pm.expect(j.providerId, 'provider id').to.eql(pm.collectionVariables.get('platformKmsProviderId'));",
                  "  pm.expect(j.kind, 'kind').to.eql('SOFTWARE');",
                  "  pm.expect(j.displayName, 'display name').to.eql(pm.collectionVariables.get('platformKmsDisplayName'));",
                  "  pm.expect(j.credentialOwnership, 'credential ownership').to.eql('PRODUCT_MANAGED');",
                  "  pm.expect(j.resourceVersion, 'initial resource version').to.be.a('number').and.eql(1);",
                  "});",
                  "pm.collectionVariables.set('platformKmsResourceHandle', j.handle);",
                  "pm.collectionVariables.set('platformKmsResourceVersion', String(j.resourceVersion));"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Offer the platform KMS as a shared instance",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{platformAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "PUT",
            "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{platformTenantId}}/kms/resources/{{platformKmsResourceHandle}}/sharing",
            "description": "Changes the offer from template fulfillment to shared-instance fulfillment. The tenant receives access to the platform-owned provider connection through its own entitlement, while tenant key and certificate references, aliases, listings, and audit identity remain tenant-scoped.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"fulfillment\": \"SHARED_INSTANCE\",\n  \"tenantIds\": [\n    \"{{tenantId}}\"\n  ],\n  \"suggestedDefault\": false,\n  \"expectedResourceVersion\": {{platformKmsResourceVersion}}\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('shared-instance offer accepted', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.test('the stored offer names shared-instance fulfillment and the offered tenant', () => {",
                  "  pm.expect(j.fulfillment, 'fulfillment').to.eql('SHARED_INSTANCE');",
                  "  pm.expect(j.tenantIds, 'offered tenants').to.include(pm.collectionVariables.get('tenantId'));",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "05 List platform KMSes offered to the tenant",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{tenantAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "GET",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/kms/shared-providers",
            "description": "Lists the platform KMSes this tenant may enable. The offer carries presentation and fulfillment only; the platform KMS configuration and credentials are never part of it.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('offered platform KMSes listed', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const providers = pm.response.json().providers || [];",
                  "const offered = providers.find((provider) => provider.providerId === pm.collectionVariables.get('platformKmsProviderId'));",
                  "pm.test('the platform offer reaches the tenant unenabled', () => {",
                  "  pm.expect(offered, 'offered platform KMS').to.be.an('object');",
                  "  pm.expect(offered.kind, 'kind').to.eql('SOFTWARE');",
                  "  pm.expect(offered.displayName, 'display name').to.eql(pm.collectionVariables.get('platformKmsDisplayName'));",
                  "  pm.expect(offered.fulfillment, 'fulfillment').to.eql('SHARED_INSTANCE');",
                  "  pm.expect(offered.enabled, 'enabled').to.eql(false);",
                  "  pm.expect(offered.suggestedDefault, 'suggested default').to.eql(false);",
                  "});",
                  "const text = pm.response.text();",
                  "['krh_', 'storageMode', 'credentialSecretRef', 'locator', 'permit'].forEach((shape) => pm.expect(text, 'offers hide ' + shape).to.not.include(shape));"
                ]
              }
            }
          ]
        },
        {
          "name": "06 Enable the offered platform KMS",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{tenantAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/kms/shared-providers/{{platformKmsProviderId}}:enable",
            "description": "The tenant takes up the shared-instance offer. The typed provider resource and credentials remain platform-owned; only this tenant's entitlement and tenant-scoped reference namespace become active.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('offered platform KMS enabled', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.test('the provider comes back enabled under the same provider id', () => {",
                  "  pm.expect(j.providerId, 'provider id').to.eql(pm.collectionVariables.get('platformKmsProviderId'));",
                  "  pm.expect(j.enabled, 'enabled').to.eql(true);",
                  "  pm.expect(j.fulfillment, 'fulfillment').to.eql('SHARED_INSTANCE');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "07 Read the tenant default KMS provider",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{tenantAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "GET",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/kms/default-provider",
            "description": "Reads the provider a key operation reaches when it names none. A tenant with no provider at all has no default, which answers 200 with an absent provider.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('default KMS provider readable', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "if (j.providerId === undefined || j.providerId === null) {",
                  "  pm.test('a tenant with no default reads as an absent value, not an error', () => {",
                  "    pm.expect(j.source, 'default source').to.be.oneOf([undefined, null]);",
                  "  });",
                  "} else {",
                  "  pm.test('an existing default names a provider and where it came from', () => {",
                  "    pm.expect(j.providerId, 'provider id').to.match(/^[a-z][a-z0-9-]{2,63}$/);",
                  "    pm.expect(['TENANT', 'PLATFORM_SUGGESTED'], 'default source').to.include(j.source);",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "08 Set the tenant default KMS provider",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{tenantAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "PUT",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/kms/default-provider",
            "description": "Selects the KMS created during tenant setup as the tenant default, addressed by the same provider id the runtime KMS REST plane uses.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"providerId\": \"{{kmsResourceProviderId}}\"\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant default KMS provider set', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.test('the tenant selection is recorded as the tenant\\'s own', () => {",
                  "  pm.expect(j.providerId, 'provider id').to.eql(pm.collectionVariables.get('kmsResourceProviderId'));",
                  "  pm.expect(j.source, 'default source').to.eql('TENANT');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "09 List runtime KMS providers",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{tenantAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "GET",
            "url": "{{tenantKmsApiBaseUrl}}/providers",
            "description": "Lists the providers the tenant reaches on the runtime KMS REST API. The admin console joins this list to the management-plane resource list on providerId, so the two planes must agree on that identifier exactly, and the runtime plane must never answer with the krh_ management handle.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('runtime KMS providers listed', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const providers = pm.response.json().providers || [];",
                  "pm.expect(providers, 'the tenant reaches at least one provider').to.have.length.of.at.least(1);",
                  "pm.test('every runtime provider carries the operator-facing projection', () => {",
                  "  providers.forEach((provider) => {",
                  "    pm.expect(provider.providerId, 'provider id').to.match(/^[a-z][a-z0-9-]{2,63}$/);",
                  "    pm.expect(provider.displayName, 'display name').to.be.a('string').and.not.empty;",
                  "    pm.expect(['PLATFORM', 'TENANT'], 'ownership').to.include(provider.ownership);",
                  "    pm.expect(provider.sharedFromPlatform, 'shared from platform').to.be.a('boolean');",
                  "    pm.expect(provider.isDefault, 'is default').to.be.a('boolean');",
                  "  });",
                  "});",
                  "const managementProviderId = pm.collectionVariables.get('kmsResourceProviderId');",
                  "const runtimeIds = providers.map((provider) => provider.providerId);",
                  "pm.test('the runtime plane addresses the tenant setup KMS by its management-plane provider id', () => {",
                  "  pm.expect(runtimeIds, 'runtime provider ids').to.include(managementProviderId);",
                  "  runtimeIds.forEach((providerId) => pm.expect(providerId, 'runtime provider id is not the management handle').to.not.match(/^krh_/));",
                  "  pm.expect(runtimeIds, 'the management handle is never a runtime provider id').to.not.include(pm.collectionVariables.get('kmsResourceHandle'));",
                  "});",
                  "pm.test('the provider selected as default reports itself as the default', () => {",
                  "  const selected = providers.find((provider) => provider.providerId === managementProviderId);",
                  "  pm.expect(selected.isDefault, 'is default').to.eql(true);",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "09 Authorization Server Configuration",
      "description": "Authorization-server configuration is intentionally separate from issuer configuration. This advanced tenant-scoped stage covers hosted and external resources, discovery, clients, identities, federation, activation, and the issuer binding bridge. It uses tenantAccessToken only. Configure the issuer in 10 Issuer Configuration after this stage.\n\nDocumentation: https://docs.sphereon.com/edk/admin-console/guides/authorization-server-configuration",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "09 Create hosted authorization server",
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/authorization-servers",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"slug\": \"{{authorizationServerSlug}}\",\n  \"displayName\": \"{{tenantName}} Hosted Authorization Server\",\n  \"issuer\": \"{{tenantGatewayUrl}}/as/{{authorizationServerSlug}}\",\n  \"deployment\": \"HOSTED\",\n  \"authenticationMode\": \"HYBRID\",\n  \"purposes\": [\n    \"GENERAL\",\n    \"CREDENTIAL_ISSUANCE\",\n    \"WALLET_LOGIN\"\n  ],\n  \"allowedGrantTypes\": [\n    \"authorization_code\",\n    \"refresh_token\"\n  ]\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('hosted authorization server created', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(201));",
                  "const j = pm.response.json();",
                  "pm.expect(j.id, 'resource UUID').to.match(/^[0-9a-f]{8}-[0-9a-f-]{27}$/i);",
                  "pm.collectionVariables.set('hostedAuthorizationServerId', j.id);",
                  "pm.collectionVariables.set('hostedAuthorizationServerRevision', String(j.revision));",
                  "pm.expect(pm.response.json().lifecycle).to.eql('ACTIVE');"
                ]
              }
            }
          ]
        },
        {
          "name": "10 Get hosted authorization server by UUID",
          "request": {
            "method": "GET",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/authorization-servers/{{hostedAuthorizationServerId}}",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('hosted authorization server read', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.id).to.eql(pm.collectionVariables.get('hostedAuthorizationServerId'));",
                  "pm.expect(j.deployment).to.eql('HOSTED');",
                  "pm.expect(j.capabilities).to.include.members(['OAUTH2', 'OIDC']);",
                  "pm.expect(j).to.include.all.keys('id','tenantId','slug','displayName','issuer','deployment','lifecycle','capabilities','expectedCapabilities','authenticationMode','purposes','usages','allowedGrantTypes','system','defaultForPurposes','revision','createdAt','updatedAt');"
                ]
              }
            }
          ]
        },
        {
          "name": "11 Replace hosted authorization server metadata",
          "request": {
            "method": "PUT",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/authorization-servers/{{hostedAuthorizationServerId}}",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"displayName\": \"{{tenantName}} Primary Hosted Authorization Server\",\n  \"expectedRevision\": \"{{hostedAuthorizationServerRevision}}\"\n}"
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.collectionVariables.set('hostedAuthorizationServerStaleRevision', pm.collectionVariables.get('hostedAuthorizationServerRevision'));"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('hosted authorization server replaced', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.collectionVariables.set('hostedAuthorizationServerRevision', String(j.revision));"
                ]
              }
            }
          ]
        },
        {
          "name": "15 Activate hosted authorization server",
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/authorization-servers/{{hostedAuthorizationServerId}}/activate",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"expectedRevision\": \"{{hostedAuthorizationServerRevision}}\"\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('hosted authorization server activated', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.collectionVariables.set('hostedAuthorizationServerRevision', String(j.revision));",
                  "pm.expect(pm.response.json().lifecycle).to.eql('ACTIVE');"
                ]
              }
            }
          ]
        },
        {
          "name": "16 Register public hosted client",
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/authorization-servers/{{hostedAuthorizationServerId}}/clients",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"clientId\": \"{{publicHostedClientId}}\",\n  \"clientName\": \"{{tenantName}} public wallet\",\n  \"clientType\": \"public\",\n  \"enabled\": true,\n  \"grantTypes\": [\n    \"authorization_code\",\n    \"refresh_token\"\n  ],\n  \"responseTypes\": [\n    \"code\"\n  ],\n  \"redirectUris\": [\n    \"{{tenantGatewayUrl}}/wallet/oauth/callback\"\n  ],\n  \"allowedScopes\": [\n    \"openid\",\n    \"profile\"\n  ],\n  \"tokenEndpointAuthMethod\": \"none\",\n  \"requirePkce\": true\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('public client registered', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(201));",
                  "const j = pm.response.json();",
                  "pm.expect(j.clientId).to.eql(pm.variables.replaceIn('{{publicHostedClientId}}'));",
                  "pm.expect(j.tokenEndpointAuthMethod).to.eql('none');"
                ]
              }
            }
          ]
        },
        {
          "name": "17 Register confidential hosted client with write-only secret",
          "disabled": true,
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/authorization-servers/{{hostedAuthorizationServerId}}/clients",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"clientId\": \"{{confidentialHostedClientId}}\",\n  \"clientName\": \"{{tenantName}} confidential issuer\",\n  \"clientType\": \"confidential\",\n  \"enabled\": true,\n  \"grantTypes\": [\n    \"authorization_code\"\n  ],\n  \"responseTypes\": [\n    \"code\"\n  ],\n  \"redirectUris\": [\n    \"{{tenantGatewayUrl}}/issuer/oauth/callback\"\n  ],\n  \"allowedScopes\": [\n    \"openid\"\n  ],\n  \"tokenEndpointAuthMethod\": \"client_secret_basic\",\n  \"clientCredential\": {\n    \"method\": \"client_secret_basic\",\n    \"clientId\": \"{{confidentialHostedClientId}}\",\n    \"secretValue\": \"{{hostedClientSecret}}\"\n  }\n}"
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.collectionVariables.get('hostedClientSecret')) pm.collectionVariables.set('hostedClientSecret', pm.variables.replaceIn('{{$randomUUID}}') + pm.variables.replaceIn('{{$randomUUID}}'));"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('confidential client registered', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(201));",
                  "const secret = pm.collectionVariables.get('hostedClientSecret');",
                  "const j = pm.response.json();",
                  "pm.expect(JSON.stringify(j)).not.to.include(secret);",
                  "pm.expect(j.clientCredential).not.to.have.property('secretValue');",
                  "pm.expect(j.clientCredential.secretReference.resourceHandle, 'opaque secret handle').to.be.a('string').and.not.empty;",
                  "pm.collectionVariables.unset('hostedClientSecret');"
                ]
              }
            }
          ]
        },
        {
          "name": "19 Create hosted identity",
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/authorization-servers/{{hostedAuthorizationServerId}}/identities",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"displayName\": \"{{tenantName}} Authorization Operator\",\n  \"email\": \"authorization-operator@{{tenantSubdomain}}.example\",\n  \"givenName\": \"Authorization\",\n  \"familyName\": \"Operator\",\n  \"roles\": [\n    \"tenant-admin\"\n  ]\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('hosted identity created', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(201));",
                  "const j = pm.response.json();",
                  "pm.expect(j.identity.identityId, 'identity UUID').to.match(/^[0-9a-f]{8}-[0-9a-f-]{27}$/i);",
                  "pm.collectionVariables.set('authorizationServerIdentityId', j.identity.identityId);",
                  "const activationLink = j.manualActivationLink;",
                  "pm.expect(activationLink, 'single-use manual identity activation link').to.be.a('string').and.not.empty;",
                  "const fragment = activationLink.split('#')[1] || '';",
                  "pm.expect(fragment, 'identity activation token fragment').to.be.a('string').and.not.empty;",
                  "pm.collectionVariables.set('asIdentityActivationToken', decodeURIComponent(fragment));"
                ]
              }
            }
          ]
        },
        {
          "name": "20a Resolve hosted identity activation",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{platformUrl}}/api/account-actions/v1/resolve",
              "host": [
                "{{platformUrl}}"
              ],
              "path": [
                "api",
                "account-actions",
                "v1",
                "resolve"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"token\": \"{{asIdentityActivationToken}}\"\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('hosted identity activation resolves', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.action, 'safe activation metadata').to.be.an('object');",
                  "pm.expect(j.action.action).to.eql('identity.activation');"
                ]
              }
            }
          ]
        },
        {
          "name": "20b Complete hosted identity activation",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{platformUrl}}/api/account-actions/v1/complete",
              "host": [
                "{{platformUrl}}"
              ],
              "path": [
                "api",
                "account-actions",
                "v1",
                "complete"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"token\": \"{{asIdentityActivationToken}}\",\n  \"password\": \"{{tenantOwnerPassword}}\"\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('hosted identity activation completed', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.action && j.action.outcomeRef, 'activation outcome').to.be.an('object');",
                  "pm.collectionVariables.unset('asIdentityActivationToken');"
                ]
              }
            }
          ]
        },
        {
          "name": "25 Create external authorization server from discovery",
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/authorization-servers",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"slug\": \"{{externalAuthorizationServerSlug}}\",\n  \"displayName\": \"Platform external authorization server\",\n  \"issuer\": \"{{platformUrl}}\",\n  \"deployment\": \"EXTERNAL\",\n  \"expectedCapabilities\": [\n    \"OAUTH2\",\n    \"OIDC\"\n  ],\n  \"purposes\": [\n    \"GENERAL\",\n    \"CREDENTIAL_ISSUANCE\",\n    \"WALLET_LOGIN\"\n  ],\n  \"usages\": [\n    \"OID4VCI_AUTHORIZATION_SERVER\",\n    \"HOSTED_LOGIN_UPSTREAM\"\n  ],\n  \"allowedGrantTypes\": [\n    \"authorization_code\",\n    \"refresh_token\"\n  ]\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('external authorization server created', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(201));",
                  "const j = pm.response.json();",
                  "pm.expect(j.id, 'resource UUID').to.match(/^[0-9a-f]{8}-[0-9a-f-]{27}$/i);",
                  "pm.collectionVariables.set('externalAuthorizationServerId', j.id);",
                  "pm.collectionVariables.set('externalAuthorizationServerRevision', String(j.revision));",
                  "pm.expect(j.expectedCapabilities).to.have.members(['OAUTH2','OIDC']);",
                  "pm.expect(j.capabilities).to.include.members(['OAUTH2','OIDC']);",
                  "pm.expect(j.discovery.digest).to.match(/^[a-f0-9]{64}$/);",
                  "pm.expect(j.discovery.userinfoEndpoint).to.match(/^https:\\/\\//);",
                  "pm.expect(j.discovery.tokenEndpointAuthMethodsSupported).to.be.an('array');"
                ]
              }
            }
          ]
        },
        {
          "name": "26 Validate external authorization server discovery",
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/authorization-servers/{{externalAuthorizationServerId}}/validate",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('external discovery validated', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.valid).to.eql(true);",
                  "pm.expect(j.snapshot.freshness).to.eql('CURRENT');",
                  "pm.expect(j.snapshot.digest).to.match(/^[a-f0-9]{64}$/);"
                ]
              }
            }
          ]
        },
        {
          "name": "27 Refresh external authorization server discovery",
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/authorization-servers/{{externalAuthorizationServerId}}/discovery/refresh",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('external discovery refreshed', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.collectionVariables.set('externalAuthorizationServerRevision', String(j.revision));",
                  "pm.expect(j.discovery.freshness).to.eql('CURRENT');",
                  "pm.expect(j.capabilities).to.include.members(j.expectedCapabilities);"
                ]
              }
            }
          ]
        },
        {
          "name": "29 Activate external authorization server",
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/authorization-servers/{{externalAuthorizationServerId}}/activate",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"expectedRevision\": \"{{externalAuthorizationServerRevision}}\"\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('external authorization server activated', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.collectionVariables.set('externalAuthorizationServerRevision', String(j.revision));",
                  "pm.expect(pm.response.json().lifecycle).to.eql('ACTIVE');"
                ]
              }
            }
          ]
        },
        {
          "name": "30 Create disabled federation binding",
          "disabled": true,
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/authorization-servers/{{hostedAuthorizationServerId}}/federation-bindings",
            "description": "Creates the disabled binding with an operator-provided client id and a write-once secret. The server persists only the typed OAUTH_CLIENT_SECRET reference and never returns raw secret material.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"externalAuthorizationServerId\": \"{{externalAuthorizationServerId}}\",\n  \"order\": 0,\n  \"enabled\": false,\n  \"scopes\": [\n    \"openid\",\n    \"profile\",\n    \"email\"\n  ],\n  \"claimsMapping\": {\n    \"subject\": \"sub\",\n    \"email\": \"email\",\n    \"displayName\": \"name\"\n  },\n  \"clientAuthentication\": {\n    \"method\": \"client_secret_basic\",\n    \"clientId\": \"{{federationClientId}}\",\n    \"secretValue\": \"{{federationClientSecret}}\"\n  }\n}"
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.collectionVariables.get('federationClientSecret')) pm.collectionVariables.set('federationClientSecret', pm.variables.replaceIn('{{$randomUUID}}') + pm.variables.replaceIn('{{$randomUUID}}'));"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('disabled federation binding created', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(201));",
                  "const secret = pm.collectionVariables.get('federationClientSecret');",
                  "const j = pm.response.json();",
                  "pm.expect(j.id, 'binding UUID').to.match(/^[0-9a-f]{8}-[0-9a-f-]{27}$/i);",
                  "pm.expect(j.status).to.eql('UNVALIDATED');",
                  "pm.expect(JSON.stringify(j)).not.to.include(secret);",
                  "pm.expect(JSON.stringify(j)).not.to.match(/secretValue|clientSecret/);",
                  "pm.expect(j.clientAuthentication).not.to.have.property('secretValue');",
                  "pm.expect(j.clientAuthentication.secretReference.purpose).to.eql('OAUTH_CLIENT_SECRET');",
                  "pm.collectionVariables.set('federationBindingId', j.id);",
                  "pm.collectionVariables.set('federationBindingRevision', String(j.revision));",
                  "pm.collectionVariables.unset('federationClientSecret');"
                ]
              }
            }
          ]
        },
        {
          "name": "31 Validate federation binding",
          "disabled": true,
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/authorization-servers/{{hostedAuthorizationServerId}}/federation-bindings/{{federationBindingId}}/validate",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('federation binding validated', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.status).to.eql('VALID');",
                  "pm.expect(j.lastValidatedAt).to.be.a('string');",
                  "pm.collectionVariables.set('federationBindingRevision', String(j.revision));"
                ]
              }
            }
          ]
        },
        {
          "name": "34 Enable validated federation binding",
          "disabled": true,
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/authorization-servers/{{hostedAuthorizationServerId}}/federation-bindings/{{federationBindingId}}/enable",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('federation binding enabled', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.enabled).to.eql(true);",
                  "pm.expect(j.status).to.eql('VALID');",
                  "pm.collectionVariables.set('federationBindingRevision', String(j.revision));"
                ]
              }
            }
          ]
        },
        {
          "name": "36 Create default issuer authorization server binding",
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/oid4vci/issuers/{{issuerId}}/authorization-server-bindings",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"authorizationServerId\": \"{{hostedAuthorizationServerId}}\",\n  \"enabled\": true,\n  \"default\": true,\n  \"allowedGrantTypes\": [\n    \"authorization_code\"\n  ]\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('default issuer AS binding created', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(201));",
                  "const j = pm.response.json();",
                  "pm.collectionVariables.set('issuerAuthorizationServerBindingId', j.id);",
                  "pm.collectionVariables.set('issuerAuthorizationServerBindingRevision', String(j.revision));",
                  "pm.expect(j.default).to.eql(true);",
                  "pm.expect(j.allowedGrantTypes).to.eql(['authorization_code']);"
                ]
              }
            }
          ]
        },
        {
          "name": "39 Apply credential authorization server override",
          "request": {
            "method": "PUT",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/oid4vci/issuers/{{issuerId}}/credential-configurations/EuPid/authorization-server",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"authorizationServerId\": \"{{hostedAuthorizationServerId}}\",\n  \"allowedGrantTypes\": [\n    \"authorization_code\"\n  ],\n  \"expectedRevision\": 0\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('credential AS override stored', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "pm.expect(pm.response.json().authorizationServerId).to.eql(pm.collectionVariables.get('hostedAuthorizationServerId'));",
                  "pm.expect(pm.response.json().revision).to.be.a('number');",
                  "pm.collectionVariables.set('credentialAuthorizationServerOverrideRevision', String(pm.response.json().revision));"
                ]
              }
            }
          ]
        },
        {
          "name": "44 Read issuer protocol profile",
          "request": {
            "method": "GET",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/oid4vci/issuers/{{issuerId}}/protocol-profile",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('issuer protocol profile read', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.collectionVariables.set('oid4vciProfileRevision', String(j.revision));",
                  "pm.expect(j.profile).to.be.oneOf(['OID4VCI_1_0_FINAL','OID4VCI_1_1_DRAFT_2A1F0513']);"
                ]
              }
            }
          ]
        },
        {
          "name": "45 Dry-run compatible OID4VCI protocol profile upgrade",
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/oid4vci/issuers/{{issuerId}}/protocol-profile/upgrade/dry-run",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"targetProfile\": \"{{oid4vciTargetProfile}}\",\n  \"expectedRevision\": \"{{oid4vciProfileRevision}}\"\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('profile upgrade dry-run completed', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.compatible, 'compatible fixture').to.eql(true);",
                  "pm.expect(j.issues).to.eql([]);"
                ]
              }
            }
          ]
        },
        {
          "name": "47 Apply OID4VCI protocol profile upgrade",
          "disabled": true,
          "request": {
            "method": "POST",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/oid4vci/issuers/{{issuerId}}/protocol-profile/upgrade/apply",
            "description": "",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"targetProfile\": \"{{oid4vciTargetProfile}}\",\n  \"expectedRevision\": \"{{oid4vciProfileRevision}}\",\n  \"reason\": \"Validated by the customer deployment authorization-server walkthrough\",\n  \"idempotencyKey\": \"{{oid4vciProfileUpgradeIdempotencyKey}}\"\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('profile upgrade applied', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.profile).to.eql(pm.variables.replaceIn('{{oid4vciTargetProfile}}'));",
                  "pm.expect(Number(j.revision)).to.be.greaterThan(Number(pm.collectionVariables.get('oid4vciProfileRevision')));"
                ]
              }
            }
          ]
        },
        {
          "name": "47a Refresh issuer authorization server bindings",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "url": {
              "raw": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/oid4vci/issuers/{{issuerId}}/authorization-server-bindings",
              "host": [
                "{{tenantPlatformConfigApiBaseUrl}}"
              ],
              "path": [
                "tenants",
                "{{tenantId}}",
                "oid4vci",
                "issuers",
                "{{issuerId}}",
                "authorization-server-bindings"
              ]
            },
            "description": "Lists the issuer bindings after the walkthrough server was made the default, and picks the binding to restore: the tenant's own hosted authorization server from onboarding. The walkthrough binding is remembered as the one to delete."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('issuer AS bindings refreshed', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const items = pm.response.json().items;",
                  "const seededAs = pm.collectionVariables.get('tenantHostedAuthorizationServerId');",
                  "const walkthrough = items.find((item) => item.authorizationServerId === pm.collectionVariables.get('hostedAuthorizationServerId'));",
                  "const seeded = items.find((item) => item.authorizationServerId === seededAs);",
                  "const restore = seeded || walkthrough;",
                  "pm.expect(restore, 'binding to carry the restored default').to.be.an('object');",
                  "pm.collectionVariables.set('restoreIssuerBindingId', restore.id);",
                  "pm.collectionVariables.set('restoreIssuerBindingRevision', String(restore.revision));",
                  "if (seeded && walkthrough) {",
                  "  pm.collectionVariables.set('strayIssuerBindingId', walkthrough.id);",
                  "} else {",
                  "  pm.collectionVariables.unset('strayIssuerBindingId');",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "47b Restore the issuer default to the seeded authorization server",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/oid4vci/issuers/{{issuerId}}/authorization-server-bindings/{{restoreIssuerBindingId}}",
              "host": [
                "{{tenantPlatformConfigApiBaseUrl}}"
              ],
              "path": [
                "tenants",
                "{{tenantId}}",
                "oid4vci",
                "issuers",
                "{{issuerId}}",
                "authorization-server-bindings",
                "{{restoreIssuerBindingId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"authorizationServerId\": \"{{tenantHostedAuthorizationServerId}}\",\n  \"enabled\": true,\n  \"default\": true,\n  \"expectedRevision\": \"{{restoreIssuerBindingRevision}}\"\n}"
            },
            "description": "Puts the issuer default back on the tenant's own hosted authorization server, which allows the pre-authorized code grant the issuance folders use. Leaving the default on the walkthrough server, bound to authorization_code only, would refuse every pre-authorized offer."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('issuer default restored to the seeded AS', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.default).to.eql(true);",
                  "pm.expect(j.authorizationServerId).to.eql(pm.collectionVariables.get('tenantHostedAuthorizationServerId'));"
                ]
              }
            }
          ]
        },
        {
          "name": "42 Delete credential authorization server override under revision CAS",
          "request": {
            "method": "DELETE",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/oid4vci/issuers/{{issuerId}}/credential-configurations/EuPid/authorization-server",
            "description": "Removes the EuPid override so that configuration follows the restored issuer default again.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"expectedRevision\": \"{{credentialAuthorizationServerOverrideRevision}}\"\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('credential AS override deleted under revision CAS', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(204));"
                ]
              }
            }
          ],
          "protocolProfileBehavior": {
            "disableBodyPruning": true
          }
        },
        {
          "name": "47d Delete leftover walkthrough issuer authorization server binding",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "url": {
              "raw": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/oid4vci/issuers/{{issuerId}}/authorization-server-bindings/{{strayIssuerBindingId}}",
              "host": [
                "{{tenantPlatformConfigApiBaseUrl}}"
              ],
              "path": [
                "tenants",
                "{{tenantId}}",
                "oid4vci",
                "issuers",
                "{{issuerId}}",
                "authorization-server-bindings",
                "{{strayIssuerBindingId}}"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.collectionVariables.get('strayIssuerBindingId')) {",
                  "  pm.test('no leftover walkthrough binding to delete', () => pm.expect([400, 404]).to.include(pm.response.code));",
                  "} else {",
                  "  pm.test('leftover walkthrough binding deleted', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(204));",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "10 Issuer Configuration",
      "description": "Issuer configuration is separate from authorization-server configuration. This stage creates the issuer design and display metadata; credential configurations, signing material, and status-list references follow in 11, 12, and 13.\n\nDocumentation: https://docs.sphereon.com/edk/admin-console/guides/issuer-configuration",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "01 Create issuer design",
          "request": {
            "method": "POST",
            "url": "{{tenantCredentialDesignApiBaseUrl}}/designs/issuers",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bindings\": [\n    {\n      \"issuerDid\": \"{{did}}\",\n      \"issuerId\": \"{{issuerId}}\",\n      \"issuerUri\": \"{{tenantGatewayUrl}}\"\n    }\n  ],\n  \"alias\": \"{{tenantSubdomain}}-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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "11 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.\n\nDocumentation: https://docs.sphereon.com/edk/admin-console/guides/credential-designs",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "01 Create EuPid SD-JWT design",
          "request": {
            "method": "POST",
            "url": "{{tenantCredentialDesignApiBaseUrl}}/designs/credentials",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('designs listed', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "12 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.\n\nDocumentation: https://docs.sphereon.com/edk/admin-console/guides/status-lists-and-revocation",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "00a Resolve EuPid DID signing selection",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{tenantAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuer/instances/{{issuerId}}/credential-config-settings/EuPid",
            "description": "Reads the provisioned EuPid signing configuration so status-list creation selects the exact KMS resource, key, and DID assertionMethod instead of a generic DID method."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('EuPid signing configuration returned', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const body = pm.response.json();",
                  "const j = body.data || body;",
                  "pm.test('EuPid carries an exact DID signing selection', () => {",
                  "  pm.expect(j.signingKeyMode).to.match(/^did:/);",
                  "  pm.expect(j.kmsResourceHandle).to.match(/^krh_[A-Za-z0-9_-]+$/);",
                  "  pm.expect(j.kmsKeyAlias).to.be.a('string').and.not.empty;",
                  "  pm.expect(j.signingVerificationMethodId).to.match(/^did:[^#]+#.+$/);",
                  "});",
                  "// W3C VCDM credentials and the bitstring status list must both be signed by the tenant's own",
                  "// did:web assertion key. This selection is read from the SD-JWT VC configuration, so pin it to",
                  "// the tenant DID: if that configuration ever moves to another key, fail here rather than sign",
                  "// the bitstring status list with an identity the VCDM credentials do not use.",
                  "pm.test('the DID signing selection belongs to the tenant did:web', () => {",
                  "  const tenantDid = pm.collectionVariables.get('did');",
                  "  pm.expect(tenantDid, 'the tenant DID must be resolved before the status lists').to.be.a('string').and.not.empty;",
                  "  pm.expect(String(j.signingVerificationMethodId).split('#')[0]).to.eql(tenantDid);",
                  "});",
                  "pm.collectionVariables.set('statusListDidKmsResourceHandle', j.kmsResourceHandle);",
                  "pm.collectionVariables.set('statusListDidKmsKeyAlias', j.kmsKeyAlias);",
                  "pm.collectionVariables.set('statusListDidVerificationMethodId', j.signingVerificationMethodId);"
                ]
              }
            }
          ]
        },
        {
          "name": "00b Resolve mDL X.509 signing selection",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{tenantAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuer/instances/{{issuerId}}/credential-config-settings/Mdl",
            "description": "Reads the provisioned mDL signing configuration and reuses its exact X.509-backed KMS key for the certificate-signed status-list verification case."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mDL signing configuration returned', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const body = pm.response.json();",
                  "const j = body.data || body;",
                  "pm.test('mDL carries an exact X.509 signing selection', () => {",
                  "  pm.expect(j.signingKeyMode).to.eql('x5c');",
                  "  pm.expect(j.kmsResourceHandle).to.match(/^krh_[A-Za-z0-9_-]+$/);",
                  "  pm.expect(j.kmsKeyAlias).to.be.a('string').and.not.empty;",
                  "  pm.expect(j.kmsResourceHandle, 'mDL uses the provisioned tenant KMS').to.eql(pm.collectionVariables.get('kmsResourceHandle'));",
                  "  pm.expect(pm.collectionVariables.get('kmsResourceKind'), 'mDL uses the Software KMS').to.eql('SOFTWARE');",
                  "});",
                  "pm.collectionVariables.set('statusListX509KmsResourceHandle', j.kmsResourceHandle);",
                  "pm.collectionVariables.set('statusListX509KmsKeyAlias', j.kmsKeyAlias);"
                ]
              }
            }
          ]
        },
        {
          "name": "01 Create token status list",
          "request": {
            "method": "POST",
            "url": "{{tenantStatusListApiBaseUrl}}/statuslists",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"correlationId\": \"walkthrough-revocation\",\n  \"spec\": \"token_status_list\",\n  \"proofFormat\": \"jwt\",\n  \"issuer\": \"{{did}}\",\n  \"signingKeyMode\": \"did:web\",\n  \"kmsResourceHandle\": \"{{statusListDidKmsResourceHandle}}\",\n  \"kmsKeyAlias\": \"{{statusListDidKmsKeyAlias}}\",\n  \"signingVerificationMethodId\": \"{{statusListDidVerificationMethodId}}\",\n  \"statusListUri\": \"{{tenantGatewayUrl}}/public/statuslists/walkthrough-revocation\",\n  \"purposes\": [\"revocation\"],\n  \"length\": 131072,\n  \"bitsPerStatus\": 1\n}"
            },
            "description": "Creates the IETF Token Status List referenced by the EuPid configuration with its exact provisioned KMS resource, key alias, and DID assertionMethod. The platform authority owns that selection; the issuer validates it and does not rewrite tenant configuration."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status list created', () => pm.expect([200, 201]).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', pm.variables.get('tenantGatewayUrl') + '/public/statuslists/walkthrough-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', () => {",
                  "  pm.expect(j.correlationId).to.eql('walkthrough-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/walkthrough-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(header.kid, 'absolute DID verification-method kid').to.be.a('string').and.not.empty;",
                  "    pm.expect(header.kid.startsWith(pm.variables.get('did') + '#')).to.eql(true);",
                  "    pm.expect(header).not.to.have.property('x5c');",
                  "    pm.expect(payload.iss).to.eql(pm.variables.get('did'));",
                  "    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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status lists returned', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(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) pm.collectionVariables.set('statusListUri', statusList.statusListUri || expectedUri);"
                ]
              }
            }
          ]
        },
        {
          "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status list returned', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "pm.expect(pm.response.text(), 'created status list id').to.include(pm.collectionVariables.get('statusListId'));"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Fetch hosted status list token",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ],
            "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.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(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(hostedHeader.kid, 'absolute DID verification-method kid').to.be.a('string').and.not.empty;",
                  "  pm.expect(hostedHeader.kid.startsWith(pm.variables.get('did') + '#'), 'kid ' + hostedHeader.kid + ' starts with did ' + pm.variables.get('did')).to.eql(true);",
                  "  pm.expect(hostedHeader).not.to.have.property('x5c');",
                  "  pm.expect(hostedPayload.iss).to.eql(pm.variables.get('did'));",
                  "  const subGatewayOrigin = String(pm.variables.get('tenantGatewayUrl') || '').replace(/\\/$/, '');",
                  "pm.expect(hostedPayload.sub, 'sub is the hosted list URI on the tenant gateway').to.be.a('string');",
                  "pm.expect(hostedPayload.sub.startsWith(subGatewayOrigin), 'sub ' + hostedPayload.sub + ' starts with ' + subGatewayOrigin).to.eql(true);",
                  "pm.expect(hostedPayload.sub.endsWith('/public/statuslists/eupid-revocation'), 'sub ' + hostedPayload.sub + ' names the eupid-revocation list').to.eql(true);",
                  "  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;",
                  "});",
                  "pm.collectionVariables.set('statusListSignerDid', hostedPayload.iss || '');"
                ]
              }
            }
          ]
        },
        {
          "name": "04a Create X.509 token status list",
          "request": {
            "method": "POST",
            "url": "{{tenantStatusListApiBaseUrl}}/statuslists",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"correlationId\": \"x509-revocation\",\n  \"spec\": \"token_status_list\",\n  \"proofFormat\": \"jwt\",\n  \"issuer\": \"{{tenantGatewayUrl}}\",\n  \"signingKeyMode\": \"x5c\",\n  \"kmsResourceHandle\": \"{{statusListX509KmsResourceHandle}}\",\n  \"kmsKeyAlias\": \"{{statusListX509KmsKeyAlias}}\",\n  \"statusListUri\": \"{{tenantGatewayUrl}}/public/statuslists/x509-revocation\",\n  \"purposes\": [\"revocation\"],\n  \"length\": 131072,\n  \"bitsPerStatus\": 1\n}"
            },
            "description": "Creates a second Token Status List using the X.509 public trust mechanism. The server-owned signing key must carry a certificate chain; the protected JWS header uses x5c and deliberately omits kid."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('X.509 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('x5cStatusListId', j.id);",
                  "if (j.statusListUri) pm.collectionVariables.set('x5cStatusListUri', j.statusListUri);",
                  "if (!pm.collectionVariables.get('x5cStatusListUri')) pm.collectionVariables.set('x5cStatusListUri', pm.variables.get('tenantGatewayUrl') + '/public/statuslists/x509-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));",
                  "};",
                  "if (j.signedToken) {",
                  "  const header = decodeJwtPart(j.signedToken, 0);",
                  "  const payload = decodeJwtPart(j.signedToken, 1);",
                  "  pm.test('X.509 status list response uses x5c without kid', () => {",
                  "    pm.expect(header.typ).to.eql('statuslist+jwt');",
                  "    pm.expect(header.x5c, 'certificate chain').to.be.an('array').and.not.empty;",
                  "    pm.expect(header.x5c[0]).to.be.a('string').and.not.empty;",
                  "    pm.expect(header).not.to.have.property('kid');",
                  "    pm.expect(payload.iss).to.eql(pm.variables.get('tenantGatewayUrl'));",
                  "    pm.expect(payload.sub).to.eql(pm.collectionVariables.get('x5cStatusListUri'));",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "04b Fetch hosted X.509 status list token",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ],
            "url": "{{x5cStatusListUri}}",
            "description": "Fetches the hosted X.509-signed status-list token and verifies its protected signer-identification header independently of the create response."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('X.509 status list hosted', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(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 token = pm.response.text().trim();",
                  "const header = decodeJwtPart(token, 0);",
                  "const payload = decodeJwtPart(token, 1);",
                  "pm.test('hosted X.509 status list uses x5c without kid', () => {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '', 'content type').to.include('application/statuslist+jwt');",
                  "  pm.expect(header.typ).to.eql('statuslist+jwt');",
                  "  pm.expect(header.x5c, 'certificate chain').to.be.an('array').and.not.empty;",
                  "  pm.expect(header.x5c[0]).to.be.a('string').and.not.empty;",
                  "  pm.expect(header).not.to.have.property('kid');",
                  "  pm.expect(payload.iss).to.eql(pm.variables.get('tenantGatewayUrl'));",
                  "  pm.expect(payload.sub).to.eql(pm.collectionVariables.get('x5cStatusListUri'));",
                  "  pm.expect(payload.status_list && payload.status_list.bits).to.eql(1);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Revoke a status entry",
          "request": {
            "method": "POST",
            "url": "{{tenantStatusListApiBaseUrl}}/statuslists/{{statusListId}}/status",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "06 Reactivate the status entry",
          "request": {
            "method": "POST",
            "url": "{{tenantStatusListApiBaseUrl}}/statuslists/{{statusListId}}/status",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status entry returned', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(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": "CWT mdoc",
          "description": "Creates the CWT-proofed token status list for mdoc credentials with X.509 signing, verifies the hosted representation is an untagged COSE_Sign1 served as application/statuslist+cwt, binds it to the Mdl credential configuration, and runs the revoke, refetch and reactivate cycle. The REST create body has no mdoc profile selector, which is recorded as a gap on the create request.",
          "item": [
            {
              "name": "01 Create the CWT token status list",
              "request": {
                "method": "POST",
                "url": "{{tenantStatusListApiBaseUrl}}/statuslists",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"correlationId\": \"mdoc-revocation\",\n  \"spec\": \"token_status_list\",\n  \"proofFormat\": \"cwt\",\n  \"hostingMode\": \"hosted\",\n  \"purposes\": [\n    \"revocation\"\n  ],\n  \"issuer\": \"{{tenantGatewayUrl}}\",\n  \"signingKeyMode\": \"x5c\",\n  \"kmsResourceHandle\": \"{{statusListX509KmsResourceHandle}}\",\n  \"kmsKeyAlias\": \"{{statusListX509KmsKeyAlias}}\",\n  \"statusListUri\": \"{{tenantGatewayUrl}}/public/statuslists/mdoc-revocation\",\n  \"length\": 131072,\n  \"bitsPerStatus\": 1,\n  \"mdocProfile\": \"status_list\",\n  \"validUntil\": \"{{cwtStatusListValidUntil}}\",\n  \"ttlSeconds\": 300\n}"
                },
                "description": "Creates the CWT-proofed Token Status List that mdoc credentials reference, signed with the certificate chain of the mDL signing key."
              },
              "event": [
                {
                  "listen": "prerequest",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// The ISO 18013-5 mdoc profile requires an expiry on the list; one year from now keeps the example valid.",
                      "const validUntil = new Date(); validUntil.setUTCFullYear(validUntil.getUTCFullYear() + 1);",
                      "pm.collectionVariables.set('cwtStatusListValidUntil', validUntil.toISOString());"
                    ]
                  }
                },
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('CWT status list created or already provisioned', () => pm.expect([200, 201, 409], 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.include(pm.response.code));",
                      "const j = pm.response.json();",
                      "if (j.id) pm.collectionVariables.set('cwtStatusListId', j.id);",
                      "if (j.statusListUri) pm.collectionVariables.set('cwtStatusListUri', j.statusListUri);",
                      "if (!pm.collectionVariables.get('cwtStatusListUri')) pm.collectionVariables.set('cwtStatusListUri', pm.variables.get('tenantGatewayUrl') + '/public/statuslists/mdoc-revocation');",
                      "pm.test('the CWT status list matches the mdoc 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, 'correlation id').to.eql('mdoc-revocation');",
                      "  pm.expect(j.spec, 'spec').to.eql('token_status_list');",
                      "  pm.expect(j.proofFormat, 'proof format').to.eql('cwt');",
                      "  pm.expect(j.bitsPerStatus, 'bits per status').to.eql(1);",
                      "  pm.expect(j.contentType, 'hosted content type').to.eql('application/statuslist+cwt');",
                      "});",
                      "pm.test('the list declares the ISO 18013-5 mdoc status_list profile', () => {",
                      "  if (pm.response.code === 409) return;",
                      "  pm.expect(j.mdocProfile, 'mdocProfile is echoed by the resource').to.eql('status_list');",
                      "});"
                    ]
                  }
                }
              ]
            },
            {
              "name": "02 Fetch the hosted CWT status list",
              "request": {
                "auth": {
                  "type": "noauth"
                },
                "method": "GET",
                "url": "{{cwtStatusListUri}}",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  }
                ],
                "description": "Fetches the hosted CWT status list from the unversioned public hosting surface. The body is an untagged COSE_Sign1, that is a bare four element CBOR array, so it starts with the CBOR major-type byte 0x84 and carries no COSE_Sign1 tag."
              },
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('CWT status list hosted', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 120)).to.eql(200));",
                      "const bytes = pm.response.stream ? Array.from(pm.response.stream.toJSON ? pm.response.stream.toJSON().data : pm.response.stream) : [];",
                      "pm.test('the hosted CWT list is an untagged COSE_Sign1 with the statuslist CWT media type', () => {",
                      "  pm.expect(pm.response.headers.get('Content-Type') || '', 'content type').to.include('application/statuslist+cwt');",
                      "  pm.expect(bytes.length, 'response bytes').to.be.greaterThan(0);",
                      "  pm.expect(bytes[0], 'first byte is the four element CBOR array header 0x84').to.eql(0x84);",
                      "  pm.expect(bytes[0], 'the list is not wrapped in CBOR tag 18').to.not.eql(0xd2);",
                      "});"
                    ]
                  }
                }
              ]
            }
          ]
        },
        {
          "name": "Bitstring VCDM",
          "description": "Creates the W3C bitstring status list the VCDM credential configurations bind to, with the VC-JWT proof format, both the revocation and the suspension purpose, and two bits per status. It then walks a single entry through suspended, valid and revoked, asserting every transition through the entry read.",
          "item": [
            {
              "name": "01 Create the bitstring status list",
              "request": {
                "method": "POST",
                "url": "{{tenantStatusListApiBaseUrl}}/statuslists",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"correlationId\": \"status-bitstring\",\n  \"spec\": \"bitstring_status_list\",\n  \"proofFormat\": \"vc+jwt\",\n  \"hostingMode\": \"hosted\",\n  \"purposes\": [\n    \"revocation\",\n    \"suspension\"\n  ],\n  \"issuer\": \"{{did}}\",\n  \"signingKeyMode\": \"did:web\",\n  \"kmsResourceHandle\": \"{{statusListDidKmsResourceHandle}}\",\n  \"kmsKeyAlias\": \"{{statusListDidKmsKeyAlias}}\",\n  \"signingVerificationMethodId\": \"{{statusListDidVerificationMethodId}}\",\n  \"statusListUri\": \"{{tenantGatewayUrl}}/public/statuslists/status-bitstring\",\n  \"length\": 131072,\n  \"bitsPerStatus\": 2,\n  \"ttlSeconds\": 300\n}"
                },
                "description": "Creates the W3C bitstring status list that the VCDM credential configurations reference. Two bits per status carry more than a single revoked flag, so the same list serves both the revocation and the suspension purpose. The hosted representation is a VC-JWT."
              },
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('bitstring status list created or already provisioned', () => pm.expect([200, 201, 409], 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.include(pm.response.code));",
                      "const j = pm.response.json();",
                      "if (j.id) pm.collectionVariables.set('bitstringStatusListId', j.id);",
                      "if (j.statusListUri) pm.collectionVariables.set('bitstringStatusListUri', j.statusListUri);",
                      "if (!pm.collectionVariables.get('bitstringStatusListUri')) pm.collectionVariables.set('bitstringStatusListUri', pm.variables.get('tenantGatewayUrl') + '/public/statuslists/status-bitstring');",
                      "pm.test('the bitstring list serves revocation and suspension with two bits per status', () => {",
                      "  if (pm.response.code === 409) {",
                      "    pm.expect(j.error && j.error.code).to.eql('STATUSLIST_DUPLICATE_CORRELATION_ID');",
                      "    return;",
                      "  }",
                      "  pm.expect(j.correlationId, 'correlation id').to.eql('status-bitstring');",
                      "  pm.expect(j.spec, 'spec').to.eql('bitstring_status_list');",
                      "  pm.expect(j.proofFormat, 'proof format').to.eql('vc+jwt');",
                      "  pm.expect(j.bitsPerStatus, 'bits per status').to.eql(2);",
                      "  pm.expect(j.purposes, 'purposes').to.have.members(['revocation', 'suspension']);",
                      "});"
                    ]
                  }
                }
              ]
            },
            {
              "name": "02 Get the bitstring status list",
              "request": {
                "method": "GET",
                "url": "{{tenantStatusListApiBaseUrl}}/statuslists/{{bitstringStatusListId}}",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  }
                ],
                "description": "Reads the list back through the management API before entries are written, so the entry updates that follow act on a known shape."
              },
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('bitstring status list returned', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                      "const j = pm.response.json();",
                      "pm.test('the persisted list keeps both purposes and two bits per status', () => {",
                      "  pm.expect(j.id, 'status list id').to.eql(pm.collectionVariables.get('bitstringStatusListId'));",
                      "  pm.expect(j.bitsPerStatus, 'bits per status').to.eql(2);",
                      "  pm.expect(j.purposes, 'purposes').to.have.members(['revocation', 'suspension']);",
                      "});"
                    ]
                  }
                }
              ]
            },
            {
              "name": "03 Suspend a bitstring status entry",
              "request": {
                "method": "POST",
                "url": "{{tenantStatusListApiBaseUrl}}/statuslists/{{bitstringStatusListId}}/status",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  }
                ],
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"statusListIndex\": 11,\n  \"value\": 2\n}"
                },
                "description": "Sets the two-bit entry to 2 (suspended). The update body carries the value and exactly one selector; the purpose is a property of the list, not of the update."
              },
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('entry suspended', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));"
                    ]
                  }
                }
              ]
            },
            {
              "name": "04 Read the suspended entry",
              "request": {
                "method": "GET",
                "url": "{{tenantStatusListApiBaseUrl}}/statuslists/{{bitstringStatusListId}}/entries/11",
                "header": [
                  {
                    "key": "Host",
                    "value": "{{tenantPublicAuthority}}",
                    "type": "text"
                  }
                ],
                "description": "Reads the entry and confirms the suspended value survived the write. A single-bit list could not represent this state."
              },
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "pm.test('bitstring status entry returned', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                      "const entry = pm.response.json();",
                      "pm.test('the entry reads back as suspended', () => {",
                      "  pm.expect(entry.statusListId, 'status list id').to.eql(pm.collectionVariables.get('bitstringStatusListId'));",
                      "  pm.expect(entry.statusListIndex, 'index').to.eql(11);",
                      "  pm.expect(entry.value, 'value').to.eql(2);",
                      "});"
                    ]
                  }
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "name": "13 Credential Configurations",
      "description": "How per-credential configuration is expressed on an issuer instance: the signing key (kmsResourceHandle and kmsKeyAlias), the public trust mechanism (signingKeyMode did:web or x5c), the validity period, the OAuth scope and the status list binding. Reads show the provisioned defaults; the registrations add VCDM 1.1 and 2.0 configurations; the last request binds the CWT status list to the mdoc configuration.\n\nDocumentation: https://docs.sphereon.com/edk/admin-console/console-reference/protocols/issuer/credentials",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "01 Read the EuPid credential configuration",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{tenantAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuer/instances/{{issuerId}}/credential-config-settings/EuPid",
            "description": "Reads the EuPid settings that already reference the shared list, so the second credential configuration is registered with the same signing selection rather than a new one."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('EuPid credential configuration returned', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const body = pm.response.json();",
                  "const j = body.data || body;",
                  "pm.test('EuPid already references the shared list', () => {",
                  "  pm.expect(j.status && j.status.statusListId, 'bound status list').to.eql('eupid-revocation');",
                  "});",
                  "pm.collectionVariables.set('sharedListScope', j.scope || 'eu.europa.ec.eudi.pid.1');"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Read the Mdl credential configuration",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{tenantAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuer/instances/{{issuerId}}/credential-config-settings/Mdl",
            "description": "Reads the mDL credential configuration before the CWT status list is bound to it. The immutable `resourceId` of the configuration is reused by the write so the binding updates the existing resource instead of creating a second one."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Mdl credential configuration returned', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const body = pm.response.json();",
                  "const j = body.data || body;",
                  "pm.expect(j.resourceId, 'immutable resource id').to.be.a('string').and.not.empty;",
                  "pm.collectionVariables.set('mdlSettingsResourceId', j.resourceId);",
                  "pm.collectionVariables.set('mdlSettingsScope', j.scope || 'org.iso.18013.5.1.mDL');"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Register the EmployeeBadge configuration (VCDM 1.1)",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{tenantAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "PUT",
            "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuer/instances/{{issuerId}}/credential-config-settings/EmployeeBadge",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"resourceId\": \"7f1c2d34-1111-4a2b-9c3d-0000000000b1\",\n  \"credentialConfigurationId\": \"EmployeeBadge\",\n  \"format\": \"jwt_vc_json\",\n  \"scope\": \"employeebadge\",\n  \"credentialDefinitionTypes\": [\n    \"VerifiableCredential\",\n    \"EmployeeBadgeCredential\"\n  ],\n  \"cryptographicBindingMethodsSupported\": [\n    \"jwk\"\n  ],\n  \"credentialSigningAlgValuesSupported\": [\n    \"ES256\"\n  ],\n  \"proofTypesSupported\": {\n    \"jwt\": {\n      \"proofSigningAlgValuesSupported\": [\n        \"ES256\"\n      ]\n    }\n  },\n  \"kmsResourceHandle\": \"{{statusListDidKmsResourceHandle}}\",\n  \"kmsKeyAlias\": \"{{statusListDidKmsKeyAlias}}\",\n  \"signingKeyMode\": \"did:web\",\n  \"signingVerificationMethodId\": \"{{statusListDidVerificationMethodId}}\",\n  \"validityPeriod\": \"P365D\",\n  \"status\": {\n    \"statusListId\": \"{{bitstringStatusListId}}\",\n    \"purpose\": \"revocation\",\n    \"revokeAtExpiry\": false\n  }\n}"
            },
            "description": "Registers the VCDM 1.1 credential configuration with the `jwt_vc_json` format and binds it to the bitstring status list created in folder 09c. `credentialDefinitionTypes` carries the W3C credential types; the signing selection is the same exact DID assertionMethod the status list uses."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('EmployeeBadge credential configuration registered', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.be.oneOf([200, 201]));",
                  "const body = pm.response.json();",
                  "const j = body.data || body;",
                  "pm.test('EmployeeBadge is a VCDM 1.1 configuration bound to the bitstring list', () => {",
                  "  pm.expect(j.credentialConfigurationId, 'credential configuration id').to.eql('EmployeeBadge');",
                  "  pm.expect(j.format, 'format').to.eql('jwt_vc_json');",
                  "  pm.expect(j.credentialDefinitionTypes, 'credential types').to.include('EmployeeBadgeCredential');",
                  "  pm.expect(j.status && j.status.statusListId, 'bound status list').to.eql(pm.collectionVariables.get('bitstringStatusListId'));",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Register the Membership configuration (VCDM 2.0)",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{tenantAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "PUT",
            "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuer/instances/{{issuerId}}/credential-config-settings/Membership",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"resourceId\": \"7f1c2d34-1111-4a2b-9c3d-0000000000b2\",\n  \"credentialConfigurationId\": \"Membership\",\n  \"format\": \"jwt_vc_json-ld\",\n  \"scope\": \"membership\",\n  \"credentialDefinitionTypes\": [\n    \"VerifiableCredential\",\n    \"MembershipCredential\"\n  ],\n  \"cryptographicBindingMethodsSupported\": [\n    \"jwk\"\n  ],\n  \"credentialSigningAlgValuesSupported\": [\n    \"ES256\"\n  ],\n  \"proofTypesSupported\": {\n    \"jwt\": {\n      \"proofSigningAlgValuesSupported\": [\n        \"ES256\"\n      ]\n    }\n  },\n  \"kmsResourceHandle\": \"{{statusListDidKmsResourceHandle}}\",\n  \"kmsKeyAlias\": \"{{statusListDidKmsKeyAlias}}\",\n  \"signingKeyMode\": \"did:web\",\n  \"signingVerificationMethodId\": \"{{statusListDidVerificationMethodId}}\",\n  \"validityPeriod\": \"P365D\",\n  \"status\": {\n    \"statusListId\": \"{{bitstringStatusListId}}\",\n    \"purpose\": \"revocation\",\n    \"revokeAtExpiry\": false\n  }\n}"
            },
            "description": "Registers the VCDM 2.0 credential configuration with the `jwt_vc_json-ld` format and binds it to the bitstring status list created in folder 09c. `credentialDefinitionTypes` carries the W3C credential types; the signing selection is the same exact DID assertionMethod the status list uses."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Membership credential configuration registered', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.be.oneOf([200, 201]));",
                  "const body = pm.response.json();",
                  "const j = body.data || body;",
                  "pm.test('Membership is a VCDM 2.0 configuration bound to the bitstring list', () => {",
                  "  pm.expect(j.credentialConfigurationId, 'credential configuration id').to.eql('Membership');",
                  "  pm.expect(j.format, 'format').to.eql('jwt_vc_json-ld');",
                  "  pm.expect(j.credentialDefinitionTypes, 'credential types').to.include('MembershipCredential');",
                  "  pm.expect(j.status && j.status.statusListId, 'bound status list').to.eql(pm.collectionVariables.get('bitstringStatusListId'));",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Bind the CWT status list to the Mdl configuration",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{tenantAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "PUT",
            "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{tenantId}}/oid4vci/issuer/instances/{{issuerId}}/credential-config-settings/Mdl",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"resourceId\": \"{{mdlSettingsResourceId}}\",\n  \"credentialConfigurationId\": \"Mdl\",\n  \"format\": \"mso_mdoc\",\n  \"scope\": \"{{mdlSettingsScope}}\",\n  \"docType\": \"org.iso.18013.5.1.mDL\",\n  \"cryptographicBindingMethodsSupported\": [\n    \"cose_key\"\n  ],\n  \"credentialSigningAlgValuesSupported\": [\n    \"ES256\"\n  ],\n  \"proofTypesSupported\": {\n    \"jwt\": {\n      \"proofSigningAlgValuesSupported\": [\n        \"ES256\"\n      ]\n    }\n  },\n  \"kmsResourceHandle\": \"{{statusListX509KmsResourceHandle}}\",\n  \"kmsKeyAlias\": \"{{statusListX509KmsKeyAlias}}\",\n  \"signingKeyMode\": \"x5c\",\n  \"validityPeriod\": \"P365D\",\n  \"status\": {\n    \"statusListId\": \"{{cwtStatusListId}}\",\n    \"purpose\": \"revocation\",\n    \"revokeAtExpiry\": false\n  }\n}"
            },
            "description": "Binds the CWT list to the mDL credential configuration. The binding references the list by its correlation id. Issued mdoc credentials do not yet carry a status entry, so this binding is configuration-visible while the issued MSO stays unchanged."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('CWT status list bound to Mdl', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.be.oneOf([200, 201]));",
                  "const body = pm.response.json();",
                  "const j = body.data || body;",
                  "pm.test('the mDL configuration references the CWT list by correlation id', () => {",
                  "  pm.expect(j.status, 'status binding').to.be.an('object');",
                  "  pm.expect(j.status.statusListId, 'bound status list').to.eql(pm.collectionVariables.get('cwtStatusListId'));",
                  "  pm.expect(j.status.purpose, 'status purpose').to.eql('revocation');",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "14 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": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "url": "{{tenantGatewayUrl}}/public/schema/vct/EuPid",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ],
            "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.collectionVariables.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 locales = displays.map((d) => d.locale);",
                  "  pm.expect(locales, 'display[].locale').to.include('en');",
                  "  pm.expect(locales, 'display[].locale').to.include('nl');",
                  "});",
                  "// Current SD-JWT VC Type Metadata uses `locale`; older drafts used `lang`.",
                  "pm.test('display entries use locale (not legacy lang)', () => {",
                  "  displays.forEach((d) => {",
                  "    pm.expect(d, 'display entry').to.have.property('locale');",
                  "    pm.expect(d, 'display entry').to.not.have.property('lang');",
                  "  });",
                  "});",
                  "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.locale === \"en\") || {}).rendering;",
                  "  const nlLogo = (displays.find((d) => d.locale === \"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 claimLocales = claimDisplays.map((d) => d.locale);",
                  "  pm.expect(claimLocales, 'claims[].display[].locale').to.include('en');",
                  "  pm.expect(claimLocales, 'claims[].display[].locale').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": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "url": "{{tenantGatewayUrl}}/.well-known/openid-credential-issuer",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ],
            "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": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "url": "{{tenantGatewayUrl}}/public/assets/design/{{eupidLogoHashLeaf}}",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ],
            "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.collectionVariables.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": "15 Issue SD-JWT VC and mdoc",
      "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. The folder pre-request script owns an ephemeral collection-local P-256 holder fixture; its private scalar remains in the runner sandbox and only the public JWK enters proof JWTs.\n\nDocumentation: https://docs.sphereon.com/edk/admin-console/guides/issue-credentials",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "event": [
        {
          "listen": "prerequest",
          "script": {
            "type": "text/javascript",
            "exec": [
              "// Minimal collection-local P-256 holder fixture for Postman/Newman.",
              "// The private scalar stays in a runtime collection variable and is never sent or logged.",
              "const CURVE_P = BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff');",
              "const CURVE_A = CURVE_P - 3n;",
              "const CURVE_N = BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551');",
              "const CURVE_G = {",
              "  x: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'),",
              "  y: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5')",
              "};",
              "const mod = (value, modulus) => {",
              "  const result = value % modulus;",
              "  return result >= 0n ? result : result + modulus;",
              "};",
              "const inverse = (value, modulus) => {",
              "  let low = mod(value, modulus);",
              "  let high = modulus;",
              "  let lowCoefficient = 1n;",
              "  let highCoefficient = 0n;",
              "  while (low > 1n) {",
              "    const ratio = high / low;",
              "    const next = high - low * ratio;",
              "    const nextCoefficient = highCoefficient - lowCoefficient * ratio;",
              "    high = low;",
              "    low = next;",
              "    highCoefficient = lowCoefficient;",
              "    lowCoefficient = nextCoefficient;",
              "  }",
              "  if (low !== 1n) throw new Error('P-256 modular inverse does not exist');",
              "  return mod(lowCoefficient, modulus);",
              "};",
              "const pointAdd = (left, right) => {",
              "  if (!left) return right;",
              "  if (!right) return left;",
              "  let slope;",
              "  if (left.x === right.x) {",
              "    if (mod(left.y + right.y, CURVE_P) === 0n) return null;",
              "    slope = mod((3n * left.x * left.x + CURVE_A) * inverse(2n * left.y, CURVE_P), CURVE_P);",
              "  } else {",
              "    slope = mod((right.y - left.y) * inverse(right.x - left.x, CURVE_P), CURVE_P);",
              "  }",
              "  const x = mod(slope * slope - left.x - right.x, CURVE_P);",
              "  return { x: x, y: mod(slope * (left.x - x) - left.y, CURVE_P) };",
              "};",
              "const scalarMultiply = (scalar, point) => {",
              "  let remaining = scalar;",
              "  let result = null;",
              "  let addend = point;",
              "  while (remaining > 0n) {",
              "    if ((remaining & 1n) === 1n) result = pointAdd(result, addend);",
              "    addend = pointAdd(addend, addend);",
              "    remaining >>= 1n;",
              "  }",
              "  return result;",
              "};",
              "const randomScalar = () => {",
              "  while (true) {",
              "    const candidateHex = CryptoJS.lib.WordArray.random(32).toString(CryptoJS.enc.Hex);",
              "    const candidate = BigInt('0x' + candidateHex);",
              "    if (candidate > 0n && candidate < CURVE_N) return candidate;",
              "  }",
              "};",
              "const hex32 = (value) => value.toString(16).padStart(64, '0');",
              "const base64Url = (wordArray) => CryptoJS.enc.Base64.stringify(wordArray).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
              "const base64UrlHex = (hex) => base64Url(CryptoJS.enc.Hex.parse(hex));",
              "const base64UrlJson = (value) => base64Url(CryptoJS.enc.Utf8.parse(JSON.stringify(value)));",
              "if (pm.info.requestName === '01 Create EuPid offer') {",
              "  pm.collectionVariables.unset('holderWalletPrivateScalar');",
              "  pm.collectionVariables.unset('holderWalletPublicJwk');",
              "  pm.collectionVariables.unset('proofJwt');",
              "}",
              "let holderPrivateHex = String(pm.collectionVariables.get('holderWalletPrivateScalar') || '');",
              "let holderPublicJwkText = String(pm.collectionVariables.get('holderWalletPublicJwk') || '');",
              "let holderPublicJwk;",
              "try { holderPublicJwk = JSON.parse(holderPublicJwkText); } catch (_) { holderPublicJwk = null; }",
              "if (!/^[0-9a-f]{64}$/.test(holderPrivateHex) || !holderPublicJwk || holderPublicJwk.kty !== 'EC' || holderPublicJwk.crv !== 'P-256') {",
              "  const holderPrivate = randomScalar();",
              "  const holderPublic = scalarMultiply(holderPrivate, CURVE_G);",
              "  holderPrivateHex = hex32(holderPrivate);",
              "  holderPublicJwk = { kty: 'EC', crv: 'P-256', x: base64UrlHex(hex32(holderPublic.x)), y: base64UrlHex(hex32(holderPublic.y)) };",
              "  holderPublicJwkText = JSON.stringify(holderPublicJwk);",
              "  pm.collectionVariables.set('holderWalletPrivateScalar', holderPrivateHex);",
              "  pm.collectionVariables.set('holderWalletPublicJwk', holderPublicJwkText);",
              "}",
              "const signEs256 = (signingInput) => {",
              "  const privateScalar = BigInt('0x' + holderPrivateHex);",
              "  const digest = BigInt('0x' + CryptoJS.SHA256(signingInput).toString(CryptoJS.enc.Hex));",
              "  while (true) {",
              "    const ephemeral = randomScalar();",
              "    const point = scalarMultiply(ephemeral, CURVE_G);",
              "    const r = mod(point.x, CURVE_N);",
              "    if (r === 0n) continue;",
              "    let s = mod(inverse(ephemeral, CURVE_N) * (digest + r * privateScalar), CURVE_N);",
              "    if (s === 0n) continue;",
              "    if (s > CURVE_N / 2n) s = CURVE_N - s;",
              "    return base64UrlHex(hex32(r) + hex32(s));",
              "  }",
              "};",
              "const buildHolderProof = (nonce) => {",
              "  const header = { alg: 'ES256', typ: 'openid4vci-proof+jwt', jwk: holderPublicJwk };",
              "  const payload = {",
              "    aud: pm.collectionVariables.get('credentialIssuer') || pm.variables.get('tenantGatewayUrl'),",
              "    iat: Math.floor(Date.now() / 1000)",
              "  };",
              "  if (nonce) payload.nonce = nonce;",
              "  const signingInput = base64UrlJson(header) + '.' + base64UrlJson(payload);",
              "  pm.collectionVariables.set('proofJwt', signingInput + '.' + signEs256(signingInput));",
              "};",
              "if (pm.info.requestName === '05 Request EuPid credential' || pm.info.requestName === '10 Request Mdl credential') {",
              "  const tokenNonce = pm.collectionVariables.get('cNonce');",
              "  const nonceEndpoint = pm.collectionVariables.get('nonceEndpoint');",
              "  if (tokenNonce) {",
              "    buildHolderProof(tokenNonce);",
              "  } else if (nonceEndpoint) {",
              "    pm.sendRequest({ url: nonceEndpoint, method: 'POST', header: { 'Host': pm.variables.get('tenantHost') } }, (error, response) => {",
              "      if (error) throw new Error('OID4VCI nonce fetch failed: ' + error.message);",
              "      const nonce = response.json().c_nonce;",
              "      if (!nonce) throw new Error('OID4VCI nonce response did not contain c_nonce');",
              "      buildHolderProof(nonce);",
              "    });",
              "  } else {",
              "    buildHolderProof(undefined);",
              "  }",
              "}"
            ]
          }
        }
      ],
      "item": [
        {
          "name": "01 Create EuPid offer",
          "request": {
            "method": "POST",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/credential/offers",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_configuration_ids\": [\"EuPid\"],\n  \"grants\": {\n    \"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": [
                  "console.log('DBG-offer-create', String(pm.response.code), String(pm.response.text() || '').slice(0, 900));",
                  "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": "02 Resolve credential offer",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ],
            "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": [
                  "console.log('DBG-offer-resolve', String(pm.response.code), String(pm.response.text() || '').slice(0, 900));",
                  "pm.test('offer resolved', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(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', () => {",
                  "  // Every hosted instance publishes under its own path, so the credential issuer identifier is the tenant origin plus that path, never the bare origin.",
                  "  const gw = trimBase(pm.variables.get('tenantGatewayUrl'));",
                  "  pm.expect(j.credential_issuer, 'credential issuer identifier').to.be.a('string').and.to.satisfy((v) => v === gw || v.indexOf(gw + '/') === 0);",
                  "  pm.expect(j.credential_configuration_ids).to.eql(['EuPid']);",
                  "});",
                  "const grant = j.grants && (j.grants['urn:ietf:params:oauth:grant-type:pre-authorized_code'] || j.grants.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": "03 Fetch OID4VCI metadata",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ],
            "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.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(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();",
                  "const configurations = j.credential_configurations_supported || {};",
                  "const eupidConfiguration = configurations.EuPid;",
                  "const mdlConfiguration = configurations.Mdl;",
                  "pm.test('sample credential metadata separates PID and mdoc contracts', () => {",
                  "  pm.expect(eupidConfiguration).to.be.an('object');",
                  "  pm.expect(eupidConfiguration.format).to.eql('dc+sd-jwt');",
                  "  pm.expect(mdlConfiguration).to.be.an('object');",
                  "  pm.expect(mdlConfiguration.format).to.eql('mso_mdoc');",
                  "  pm.expect(mdlConfiguration.cryptographic_binding_methods_supported).to.eql(['cose_key']);",
                  "  pm.expect(mdlConfiguration).not.to.have.property('status');",
                  "});",
                  "if (j.credential_endpoint) pm.collectionVariables.set('credentialEndpoint', rewritePublicUrl(j.credential_endpoint, pm.variables.get('tenantGatewayUrl')));",
                  "const advertisedAs = Array.isArray(j.authorization_servers) && j.authorization_servers.length ? j.authorization_servers[0] : null;",
                  "pm.expect(advertisedAs || j.token_endpoint, 'issuer metadata advertises an authorization server or token endpoint').to.be.a('string');",
                  "const tokenEndpoint = rewritePublicUrl(j.token_endpoint || (advertisedAs.replace(/\\/$/, '') + '/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": "04 Exchange pre-authorized code for token",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "url": "{{tokenEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": [
                  "console.log('DBG-token-exchange', String(pm.response.code), String(pm.response.text() || '').slice(0, 900));",
                  "pm.test('token issued', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "const credentialAuthorization = Array.isArray(j.authorization_details) ? j.authorization_details.find((detail) => detail && detail.credential_configuration_id === 'EuPid') : undefined;",
                  "const credentialIdentifiers = credentialAuthorization && credentialAuthorization.credential_identifiers;",
                  "pm.test('pre-authorized token returns an opaque EuPid credential identifier distinct from its configuration id', () => {",
                  "  pm.expect(credentialAuthorization).to.be.an('object');",
                  "  pm.expect(credentialAuthorization.type).to.eql('openid_credential');",
                  "  pm.expect(credentialIdentifiers).to.be.an('array').with.lengthOf(1);",
                  "  pm.expect(credentialIdentifiers[0]).to.be.a('string').and.not.empty;",
                  "  pm.expect(credentialIdentifiers[0]).not.to.eql(credentialAuthorization.credential_configuration_id);",
                  "});",
                  "pm.collectionVariables.set('credentialIdentifier', credentialIdentifiers ? credentialIdentifiers[0] : '');",
                  "if (j.access_token) pm.collectionVariables.set('walletAccessToken', j.access_token);",
                  "pm.collectionVariables.set('cNonce', j.c_nonce || '');"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Request EuPid credential",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{walletAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "POST",
            "url": "{{credentialEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_identifier\": \"{{credentialIdentifier}}\",\n  \"proofs\": {\n    \"jwt\": [\"{{proofJwt}}\"]\n  }\n}"
            },
            "description": "OpenID4VCI 1.0 Final credential request selecting the EuPid credential_identifier returned in the token response and carrying an ES256 proof of possession (typ openid4vci-proof+jwt) signed by the collection-local holder fixture. The private scalar remains in the Postman/Newman sandbox; only its public JWK is sent. The response carries the SD-JWT credential including the status claim that references the hosted status list."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('eupid credential issued', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(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 eupidHeader = decodeJwtPart(cred, 0);",
                  "const eupidPayload = decodeJwtPart(cred, 1);",
                  "pm.test('EuPid credential carries expected VCT and status-list reference', () => {",
                  "  const vctValue = String(eupidPayload.vct || '');",
                  "pm.expect(vctValue, 'vct actual=' + vctValue).to.eql(String(pm.variables.get('tenantGatewayUrl')).replace(new RegExp('/+$'), '') + '/public/schema/vct/EuPid');",
                  "  const statusUriValue = String((eupidPayload.status && eupidPayload.status.status_list && eupidPayload.status.status_list.uri) || '');",
                  "pm.expect(statusUriValue, 'status list actual=' + statusUriValue).to.eql(String(pm.variables.get('tenantGatewayUrl')).replace(new RegExp('/+$'), '') + '/public/statuslists/eupid-revocation');",
                  "  pm.expect(eupidPayload.status.status_list.idx).to.be.a('number');",
                  "});",
                  "pm.test('EuPid credential and status list use the same did:web signer identity', () => {",
                  "  pm.expect(eupidPayload.iss).to.eql(pm.variables.get('did'));",
                  "  pm.expect(eupidPayload.iss).to.eql(pm.collectionVariables.get('statusListSignerDid'));",
                  "  pm.expect(eupidHeader.kid, 'EuPid DID verification-method kid').to.be.a('string').and.not.empty;",
                  "  pm.expect(eupidHeader.kid.startsWith(eupidPayload.iss + '#')).to.eql(true);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "06 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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('session tracked', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "07 Create Mdl offer",
          "request": {
            "method": "POST",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/credential/offers",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_configuration_ids\": [\"Mdl\"],\n  \"grants\": {\n    \"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": "08 Resolve Mdl offer",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ],
            "url": "{{mdlCredentialOfferUri}}",
            "description": "Resolves the Mdl offer and extracts the pre-authorized code."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const trimBase = (base) => base && base.endsWith('/') ? base.slice(0, -1) : base;",
                  "pm.test('mdl offer resolved', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.test('resolved mDL offer carries exact credential configuration id', () => {",
                  "  // Every hosted instance publishes under its own path, so the credential issuer identifier is the tenant origin plus that path, never the bare origin.",
                  "  const gw = trimBase(pm.variables.get('tenantGatewayUrl'));",
                  "  pm.expect(j.credential_issuer, 'credential issuer identifier').to.be.a('string').and.to.satisfy((v) => v === gw || v.indexOf(gw + '/') === 0);",
                  "  pm.expect(j.credential_configuration_ids).to.eql(['Mdl']);",
                  "});",
                  "const grant = j.grants && (j.grants['urn:ietf:params:oauth:grant-type:pre-authorized_code'] || j.grants.pre_authorized_code);",
                  "if (grant && grant['pre-authorized_code']) pm.collectionVariables.set('mdlPreAuthCode', grant['pre-authorized_code']);"
                ]
              }
            }
          ]
        },
        {
          "name": "09 Exchange Mdl code for token",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "url": "{{tokenEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "const credentialAuthorization = Array.isArray(j.authorization_details) ? j.authorization_details.find((detail) => detail && detail.credential_configuration_id === 'Mdl') : undefined;",
                  "const credentialIdentifiers = credentialAuthorization && credentialAuthorization.credential_identifiers;",
                  "pm.test('pre-authorized token returns an opaque Mdl credential identifier distinct from its configuration id', () => {",
                  "  pm.expect(credentialAuthorization).to.be.an('object');",
                  "  pm.expect(credentialAuthorization.type).to.eql('openid_credential');",
                  "  pm.expect(credentialIdentifiers).to.be.an('array').with.lengthOf(1);",
                  "  pm.expect(credentialIdentifiers[0]).to.be.a('string').and.not.empty;",
                  "  pm.expect(credentialIdentifiers[0]).not.to.eql(credentialAuthorization.credential_configuration_id);",
                  "});",
                  "pm.collectionVariables.set('mdlCredentialIdentifier', credentialIdentifiers ? credentialIdentifiers[0] : '');",
                  "if (j.access_token) pm.collectionVariables.set('walletAccessToken', j.access_token);",
                  "pm.collectionVariables.set('cNonce', j.c_nonce || '');"
                ]
              }
            }
          ]
        },
        {
          "name": "10 Request Mdl credential",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{walletAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "POST",
            "url": "{{credentialEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_identifier\": \"{{mdlCredentialIdentifier}}\",\n  \"proofs\": {\n    \"jwt\": [\"{{proofJwt}}\"]\n  }\n}"
            },
            "description": "OpenID4VCI 1.0 Final credential request selecting the Mdl credential_identifier returned in the token response and using a proof signed by the same collection-local holder fixture. The response carries the base64url-encoded ISO 18013-5 mdoc bound to the holder public key (cose_key binding)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mdl credential issued', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "try {",
                  "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);",
                  "} finally {",
                  "pm.collectionVariables.unset('holderWalletPrivateScalar');",
                  "pm.collectionVariables.unset('holderWalletPublicJwk');",
                  "pm.collectionVariables.unset('proofJwt');",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "16 Issue with Transaction Code",
      "description": "Sequential customer proof of OID4VCI transaction-code issuance through the tenant gateway. Wrong transaction codes do not consume the pre-authorized code; the same correct code succeeds once, replay and expiry fail with OAuth errors, and the credential/status reference plus correlation identity are checked. Each protocol step is an explicit sequential request.",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "01 Create transaction-code EuPid offer",
          "request": {
            "method": "POST",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/credential/offers",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_configuration_ids\": [\"EuPid\"],\n  \"grants\": {\n    \"pre_authorized_code\": {\n      \"tx_code\": {\"input_mode\": \"numeric\", \"length\": 6}\n    }\n  },\n  \"ttl_seconds\": 120,\n  \"credential_subject_data\": {\"family_name\": \"Example\", \"given_name\": \"Transaction\", \"birth_date\": \"1964-08-12\", \"age_over_18\": true, \"nationality\": \"DE\", \"issuing_authority\": \"DE\", \"issuing_country\": \"DE\", \"document_number\": \"TX-EUPID-001\"},\n  \"correlation_id\": \"{{transactionCorrelationId}}\"\n}"
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const correlationId = 'tx-eupid-' + pm.variables.replaceIn('{{$randomUUID}}');",
                  "pm.collectionVariables.set('transactionCorrelationId', correlationId);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "console.log('DBG-tx-offer-create', String(pm.response.code), String(pm.response.text() || '').slice(0, 900));",
                  "pm.test('transaction-code offer created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "pm.expect(j.offer_uri, 'offer URI').to.be.a('string').and.not.empty;",
                  "pm.expect(j.tx_code, 'actual transaction code').to.match(/^\\d{6}$/);",
                  "pm.expect(j.session_id, 'actual session id').to.be.a('string').and.not.empty;",
                  "pm.expect(j.status_uri, 'actual status URI').to.be.a('string').and.not.empty;",
                  "const parseUrl = (value, relativeTo) => { const u = require('url'); const href = relativeTo ? u.resolve(String(relativeTo), String(value)) : String(value); const p = u.parse(href); const auth = p.auth ? String(p.auth).split(':') : []; return { protocol: p.protocol, host: p.host, origin: p.protocol + '//' + p.host, username: auth[0] || '', password: auth[1] || '', href, toString: () => href }; }; const tenantGatewayBaseUrl = String(pm.variables.get('tenantGatewayUrl') || '').trim().replace(/\\/+$/, '');",
                  "const base = parseUrl(tenantGatewayBaseUrl);",
                  "const requireTenantGatewayHttpsUrl = (value, label) => {",
                  "  const target = parseUrl(String(value));",
                  "  if (base.protocol !== 'https:' || base.username || base.password || target.protocol !== 'https:' || target.username || target.password || target.origin !== base.origin) throw new Error(`${label} must be an HTTPS URL on the exact tenant gateway origin`);",
                  "  return target.toString();",
                  "};",
                  "const gateway = base;",
                  "const offerMatch = String(j.offer_uri).match(/credential_offer_uri=([^&]+)/);",
                  "const offerUrl = parseUrl(decodeURIComponent(offerMatch ? offerMatch[1] : j.offer_uri), gateway.href);",
                  "const statusUrl = requireTenantGatewayHttpsUrl(j.status_uri, 'status_uri');",
                  "pm.expect(statusUrl, 'validated status URI').to.be.a('string').and.not.empty;",
                  "pm.expect(offerUrl.origin, 'offer endpoint origin').to.eql(gateway.origin);",
                  "pm.collectionVariables.set('transactionOfferUri', offerUrl.toString());",
                  "pm.collectionVariables.set('transactionCode', j.tx_code);",
                  "pm.collectionVariables.set('transactionSessionId', j.session_id);",
                  "pm.collectionVariables.set('transactionStatusUri', statusUrl);",
                  "pm.expect(j.correlation_id || j.correlationId).to.eql(pm.collectionVariables.get('transactionCorrelationId'));"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Resolve transaction-code EuPid offer",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "url": "{{transactionOfferUri}}",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "console.log('DBG-tx-offer-resolve', String(pm.response.code), String(pm.response.text() || '').slice(0, 900));",
                  "pm.test('transaction-code offer resolved', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "const gatewayOrigin = String(pm.variables.get('tenantGatewayUrl') || '').replace(/\\/$/, '');",
                  "pm.expect(j.credential_issuer, 'credential issuer sits on the tenant gateway origin').to.be.a('string');",
                  "pm.expect(j.credential_issuer.startsWith(gatewayOrigin)).to.eql(true);",
                  "pm.expect(j.credential_configuration_ids).to.eql(['EuPid']);",
                  "const grant = j.grants && (j.grants['urn:ietf:params:oauth:grant-type:pre-authorized_code'] || (j.grants['urn:ietf:params:oauth:grant-type:pre-authorized_code'] || j.grants.pre_authorized_code));",
                  "pm.expect(grant && grant['pre-authorized_code'], 'pre-authorized code').to.be.a('string').and.not.empty;",
                  "pm.expect(grant && grant.tx_code, 'transaction code challenge').to.be.an('object');",
                  "pm.expect(pm.collectionVariables.get('transactionCode'), 'actual transaction code from create response').to.match(/^\\d{6}$/);",
                  "const actual = pm.collectionVariables.get('transactionCode');",
                  "pm.collectionVariables.set('transactionWrongCode', actual === '000000' ? '000001' : '000000');",
                  "pm.collectionVariables.set('transactionPreAuthCode', grant['pre-authorized_code']);",
                  "pm.collectionVariables.set('transactionIssuerMetadataUrl', j.credential_issuer.replace(/\\/$/, '') + '/.well-known/openid-credential-issuer');"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Fetch transaction-code OID4VCI metadata",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "url": "{{transactionIssuerMetadataUrl}}",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('transaction-code metadata served', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "const parseUrl = (value, relativeTo) => { const u = require('url'); const href = relativeTo ? u.resolve(String(relativeTo), String(value)) : String(value); const p = u.parse(href); const auth = p.auth ? String(p.auth).split(':') : []; return { protocol: p.protocol, host: p.host, origin: p.protocol + '//' + p.host, username: auth[0] || '', password: auth[1] || '', href, toString: () => href }; }; const tenantGatewayBaseUrl = String(pm.variables.get('tenantGatewayUrl') || '').trim().replace(/\\/+$/, '');",
                  "const gateway = parseUrl(tenantGatewayBaseUrl);",
                  "const endpointOnGateway = (value, label) => { const endpoint = parseUrl(value, gateway.href); if (gateway.protocol !== 'https:' || gateway.username || gateway.password || endpoint.protocol !== 'https:' || endpoint.username || endpoint.password || endpoint.origin !== gateway.origin) throw new Error(label + ' must be an HTTPS URL on the exact tenant gateway origin'); return endpoint.toString(); };",
                  "pm.expect(j.credential_endpoint, 'credential endpoint').to.be.a('string').and.not.empty;",
                  "const txAdvertisedAs = Array.isArray(j.authorization_servers) && j.authorization_servers.length ? j.authorization_servers[0] : null;",
                  "const txTokenEndpoint = j.token_endpoint || (txAdvertisedAs && txAdvertisedAs.replace(new RegExp('/+$'), '') + '/token');",
                  "pm.expect(txTokenEndpoint, 'token endpoint').to.be.a('string').and.not.empty;",
                  "pm.collectionVariables.set('transactionTokenEndpoint', endpointOnGateway(txTokenEndpoint, 'token endpoint'));",
                  "pm.collectionVariables.set('transactionCredentialEndpoint', endpointOnGateway(j.credential_endpoint, 'credential endpoint'));",
                  "pm.expect(j.nonce_endpoint, 'nonce endpoint').to.be.a('string').and.not.empty;",
                  "pm.collectionVariables.set('transactionNonceEndpoint', endpointOnGateway(j.nonce_endpoint, 'nonce endpoint'));",
                  "pm.expect(pm.collectionVariables.get('transactionTokenEndpoint')).to.match(/\\/token(?:$|\\?)/);",
                  "pm.expect(pm.collectionVariables.get('transactionCredentialEndpoint')).to.match(/\\/credential(?:$|\\?)/);"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Exchange transaction code for token",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "url": "{{transactionTokenEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "urn:ietf:params:oauth:grant-type:pre-authorized_code"
                },
                {
                  "key": "pre-authorized_code",
                  "value": "{{transactionPreAuthCode}}"
                },
                {
                  "key": "tx_code",
                  "value": "{{transactionCode}}"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('correct transaction code succeeds after wrong attempt', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.access_token, 'access token').to.be.a('string').and.not.empty;",
                  "const auth = (j.authorization_details || []).find((d) => d.credential_configuration_id === 'EuPid');",
                  "pm.expect(auth && auth.credential_identifiers && auth.credential_identifiers[0], 'opaque credential_identifier').to.be.a('string').and.not.eql('EuPid');",
                  "pm.collectionVariables.set('walletAccessToken', j.access_token);",
                  "pm.collectionVariables.set('transactionCredentialIdentifier', auth.credential_identifiers[0]);"
                ]
              }
            }
          ]
        },
        {
          "name": "05a Fetch credential nonce",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{transactionNonceEndpoint}}",
              "host": [
                "{{transactionNonceEndpoint}}"
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('credential nonce issued', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.c_nonce, 'c_nonce').to.be.a('string').and.not.empty;",
                  "pm.collectionVariables.set('transactionNonce', j.c_nonce);"
                ]
              }
            }
          ]
        },
        {
          "name": "06 Issue transaction-code EuPid credential",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{walletAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "POST",
            "url": "{{transactionCredentialEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_identifier\": \"{{transactionCredentialIdentifier}}\",\n  \"proofs\": {\"jwt\": [\"{{proofJwt}}\"]}\n}"
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Minimal collection-local P-256 holder fixture for Postman/Newman.",
                  "// The private scalar stays in a runtime collection variable and is never sent or logged.",
                  "const CURVE_P = BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff');",
                  "const CURVE_A = CURVE_P - 3n;",
                  "const CURVE_N = BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551');",
                  "const CURVE_G = {",
                  "  x: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'),",
                  "  y: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5')",
                  "};",
                  "const mod = (value, modulus) => {",
                  "  const result = value % modulus;",
                  "  return result >= 0n ? result : result + modulus;",
                  "};",
                  "const inverse = (value, modulus) => {",
                  "  let low = mod(value, modulus);",
                  "  let high = modulus;",
                  "  let lowCoefficient = 1n;",
                  "  let highCoefficient = 0n;",
                  "  while (low > 1n) {",
                  "    const ratio = high / low;",
                  "    const next = high - low * ratio;",
                  "    const nextCoefficient = highCoefficient - lowCoefficient * ratio;",
                  "    high = low;",
                  "    low = next;",
                  "    highCoefficient = lowCoefficient;",
                  "    lowCoefficient = nextCoefficient;",
                  "  }",
                  "  if (low !== 1n) throw new Error('P-256 modular inverse does not exist');",
                  "  return mod(lowCoefficient, modulus);",
                  "};",
                  "const pointAdd = (left, right) => {",
                  "  if (!left) return right;",
                  "  if (!right) return left;",
                  "  let slope;",
                  "  if (left.x === right.x) {",
                  "    if (mod(left.y + right.y, CURVE_P) === 0n) return null;",
                  "    slope = mod((3n * left.x * left.x + CURVE_A) * inverse(2n * left.y, CURVE_P), CURVE_P);",
                  "  } else {",
                  "    slope = mod((right.y - left.y) * inverse(right.x - left.x, CURVE_P), CURVE_P);",
                  "  }",
                  "  const x = mod(slope * slope - left.x - right.x, CURVE_P);",
                  "  return { x: x, y: mod(slope * (left.x - x) - left.y, CURVE_P) };",
                  "};",
                  "const scalarMultiply = (scalar, point) => {",
                  "  let remaining = scalar;",
                  "  let result = null;",
                  "  let addend = point;",
                  "  while (remaining > 0n) {",
                  "    if ((remaining & 1n) === 1n) result = pointAdd(result, addend);",
                  "    addend = pointAdd(addend, addend);",
                  "    remaining >>= 1n;",
                  "  }",
                  "  return result;",
                  "};",
                  "const randomScalar = () => {",
                  "  while (true) {",
                  "    const candidateHex = CryptoJS.lib.WordArray.random(32).toString(CryptoJS.enc.Hex);",
                  "    const candidate = BigInt('0x' + candidateHex);",
                  "    if (candidate > 0n && candidate < CURVE_N) return candidate;",
                  "  }",
                  "};",
                  "const hex32 = (value) => value.toString(16).padStart(64, '0');",
                  "const base64Url = (wordArray) => CryptoJS.enc.Base64.stringify(wordArray).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "const base64UrlHex = (hex) => base64Url(CryptoJS.enc.Hex.parse(hex));",
                  "const base64UrlJson = (value) => base64Url(CryptoJS.enc.Utf8.parse(JSON.stringify(value)));",
                  "if (pm.info.requestName === '01 Create EuPid offer') {",
                  "  pm.collectionVariables.unset('holderWalletPrivateScalar');",
                  "  pm.collectionVariables.unset('holderWalletPublicJwk');",
                  "  pm.collectionVariables.unset('proofJwt');",
                  "}",
                  "let holderPrivateHex = String(pm.collectionVariables.get('holderWalletPrivateScalar') || '');",
                  "let holderPublicJwkText = String(pm.collectionVariables.get('holderWalletPublicJwk') || '');",
                  "let holderPublicJwk;",
                  "try { holderPublicJwk = JSON.parse(holderPublicJwkText); } catch (_) { holderPublicJwk = null; }",
                  "if (!/^[0-9a-f]{64}$/.test(holderPrivateHex) || !holderPublicJwk || holderPublicJwk.kty !== 'EC' || holderPublicJwk.crv !== 'P-256') {",
                  "  const holderPrivate = randomScalar();",
                  "  const holderPublic = scalarMultiply(holderPrivate, CURVE_G);",
                  "  holderPrivateHex = hex32(holderPrivate);",
                  "  holderPublicJwk = { kty: 'EC', crv: 'P-256', x: base64UrlHex(hex32(holderPublic.x)), y: base64UrlHex(hex32(holderPublic.y)) };",
                  "  holderPublicJwkText = JSON.stringify(holderPublicJwk);",
                  "  pm.collectionVariables.set('holderWalletPrivateScalar', holderPrivateHex);",
                  "  pm.collectionVariables.set('holderWalletPublicJwk', holderPublicJwkText);",
                  "}",
                  "const signEs256 = (signingInput) => {",
                  "  const privateScalar = BigInt('0x' + holderPrivateHex);",
                  "  const digest = BigInt('0x' + CryptoJS.SHA256(signingInput).toString(CryptoJS.enc.Hex));",
                  "  while (true) {",
                  "    const ephemeral = randomScalar();",
                  "    const point = scalarMultiply(ephemeral, CURVE_G);",
                  "    const r = mod(point.x, CURVE_N);",
                  "    if (r === 0n) continue;",
                  "    let s = mod(inverse(ephemeral, CURVE_N) * (digest + r * privateScalar), CURVE_N);",
                  "    if (s === 0n) continue;",
                  "    if (s > CURVE_N / 2n) s = CURVE_N - s;",
                  "    return base64UrlHex(hex32(r) + hex32(s));",
                  "  }",
                  "};",
                  "const buildHolderProof = (nonce) => {",
                  "  const header = { alg: 'ES256', typ: 'openid4vci-proof+jwt', jwk: holderPublicJwk };",
                  "  const payload = {",
                  "    aud: pm.collectionVariables.get('credentialIssuer') || pm.variables.get('tenantGatewayUrl'),",
                  "    iat: Math.floor(Date.now() / 1000)",
                  "  };",
                  "  if (nonce) payload.nonce = nonce;",
                  "  const signingInput = base64UrlJson(header) + '.' + base64UrlJson(payload);",
                  "  pm.collectionVariables.set('proofJwt', signingInput + '.' + signEs256(signingInput));",
                  "};",
                  "if (pm.info.requestName === '06 Issue transaction-code EuPid credential') {",
                  "  const nonce = pm.collectionVariables.get('transactionNonce');",
                  "  if (!nonce) throw new Error('transaction-code credential proof requires transactionNonce');",
                  "  buildHolderProof(nonce);",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('transaction-code credential issued', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "const credential = (j.credentials && j.credentials[0] && j.credentials[0].credential) || j.credential;",
                  "pm.expect(credential, 'credential').to.be.a('string').and.not.empty;",
                  "const payload = JSON.parse(atob(String(credential).split('~')[0].split('.')[1].replace(/-/g, '+').replace(/_/g, '/')));",
                  "const status = payload.status && payload.status.status_list;",
                  "pm.expect(status && status.uri, 'credential status reference').to.be.a('string').and.not.empty;",
                  "const parseUrl = (value, relativeTo) => { const u = require('url'); const href = relativeTo ? u.resolve(String(relativeTo), String(value)) : String(value); const p = u.parse(href); const auth = p.auth ? String(p.auth).split(':') : []; return { protocol: p.protocol, host: p.host, origin: p.protocol + '//' + p.host, username: auth[0] || '', password: auth[1] || '', href, toString: () => href }; }; const tenantGatewayBaseUrl = String(pm.variables.get('tenantGatewayUrl') || '').trim().replace(/\\/+$/, '');",
                  "const base = parseUrl(tenantGatewayBaseUrl);",
                  "const requireTenantGatewayHttpsUrl = (value, label) => {",
                  "  const target = parseUrl(String(value));",
                  "  if (base.protocol !== 'https:' || base.username || base.password || target.protocol !== 'https:' || target.username || target.password || target.origin !== base.origin) throw new Error(`${label} must be an HTTPS URL on the exact tenant gateway origin`);",
                  "  return target.toString();",
                  "};",
                  "const validatedStatusUri = requireTenantGatewayHttpsUrl(status.uri, 'credential status URI');",
                  "pm.collectionVariables.set('transactionCredentialStatusUri', validatedStatusUri);"
                ]
              }
            }
          ]
        },
        {
          "name": "07 Assert transaction-code offer status correlation",
          "request": {
            "method": "GET",
            "url": "{{transactionStatusUri}}",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ]
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const parseUrl = (value, relativeTo) => { const u = require('url'); const href = relativeTo ? u.resolve(String(relativeTo), String(value)) : String(value); const p = u.parse(href); const auth = p.auth ? String(p.auth).split(':') : []; return { protocol: p.protocol, host: p.host, origin: p.protocol + '//' + p.host, username: auth[0] || '', password: auth[1] || '', href, toString: () => href }; }; const tenantGatewayBaseUrl = String(pm.variables.get('tenantGatewayUrl') || '').trim().replace(/\\/+$/, '');",
                  "const base = parseUrl(tenantGatewayBaseUrl);",
                  "const target = parseUrl(String(pm.collectionVariables.get('transactionStatusUri') || ''));",
                  "if (base.protocol !== 'https:' || base.username || base.password || target.protocol !== 'https:' || target.username || target.password || target.origin !== base.origin) throw new Error('transaction status URI must be an HTTPS URL on the exact tenant gateway origin');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('transaction-code offer status is readable', () => pm.expect([200, 204]).to.include(pm.response.code));",
                  "pm.expect(pm.collectionVariables.get('transactionSessionId'), 'create response session id').to.be.a('string').and.not.empty;",
                  "if (pm.response.code === 200) { const j = pm.response.json(); pm.expect(j.correlation_id || j.correlationId, 'status/session correlation identity').to.eql(pm.collectionVariables.get('transactionCorrelationId')); }"
                ]
              }
            }
          ]
        },
        {
          "name": "09 Dereference transaction-code credential status",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "url": "{{transactionCredentialStatusUri}}",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}"
              }
            ]
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const parseUrl = (value, relativeTo) => { const u = require('url'); const href = relativeTo ? u.resolve(String(relativeTo), String(value)) : String(value); const p = u.parse(href); const auth = p.auth ? String(p.auth).split(':') : []; return { protocol: p.protocol, host: p.host, origin: p.protocol + '//' + p.host, username: auth[0] || '', password: auth[1] || '', href, toString: () => href }; }; const tenantGatewayBaseUrl = String(pm.variables.get('tenantGatewayUrl') || '').trim().replace(/\\/+$/, '');",
                  "const base = parseUrl(tenantGatewayBaseUrl);",
                  "const target = parseUrl(String(pm.collectionVariables.get('transactionCredentialStatusUri') || ''));",
                  "if (base.protocol !== 'https:' || base.username || base.password || target.protocol !== 'https:' || target.username || target.password || target.origin !== base.origin) throw new Error('credential status URI must be an HTTPS URL on the exact tenant gateway origin');"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('credential status reference dereferenced', () => pm.expect([200, 204]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "17 Issue W3C VCDM 1.1",
      "description": "Registers the EmployeeBadge credential configuration in the VCDM 1.1 JWT VC format, binds it to the bitstring status list of folder 09c, and issues one pre-authorized credential. The test decodes the returned JWT and asserts the VCDM 1.1 context, the credential type and a BitstringStatusListEntry status entry.",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "02 Create the EmployeeBadge offer",
          "request": {
            "method": "POST",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/credential/offers",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_configuration_ids\": [\n    \"EmployeeBadge\"\n  ],\n  \"grants\": {\n    \"pre_authorized_code\": {}\n  },\n  \"credential_subject_data\": {\n    \"id\": \"did:example:employee-0001\",\n    \"type\": \"EmployeeBadgeCredential\",\n    \"name\": \"Erika Mustermann\",\n    \"employeeId\": \"ACME-4711\",\n    \"jobTitle\": \"Field Engineer\",\n    \"department\": \"Operations\"\n  },\n  \"correlation_id\": \"e2e-employeebadge-001\"\n}"
            },
            "description": "Creates a pre-authorized offer for the VCDM 1.1 credential with the subject data supplied inline."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('EmployeeBadge offer created', () => pm.expect([200, 201], 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "const trimBase = (base) => base && base.endsWith('/') ? base.slice(0, -1) : base;",
                  "const rewritePublicUrl = (url) => {",
                  "  const publicHost = pm.variables.get('tenantHost');",
                  "  const gatewayBase = trimBase(pm.variables.get('tenantGatewayUrl'));",
                  "  const m = String(url || '').match(/^(https?:\\/\\/([^/]+))(.*)$/);",
                  "  if (m && publicHost && m[2] === publicHost && gatewayBase) return gatewayBase + (m[3] || '');",
                  "  return url;",
                  "};",
                  "pm.expect(j.offer_uri, 'offer uri').to.be.a('string').and.not.empty;",
                  "const m = String(j.offer_uri).match(/credential_offer_uri=([^&]+)/);",
                  "pm.expect(m, 'offer uri carries credential_offer_uri').to.not.eql(null);",
                  "pm.collectionVariables.set('employeeBadgeOfferUri', rewritePublicUrl(decodeURIComponent(m[1])));"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Resolve the EmployeeBadge offer",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "url": "{{employeeBadgeOfferUri}}",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "description": "Resolves the offer as a wallet does and reads the pre-authorized code."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('EmployeeBadge offer resolved', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "const trimBase = (base) => base && base.endsWith('/') ? base.slice(0, -1) : base;",
                  "const rewritePublicUrl = (url) => {",
                  "  const publicHost = pm.variables.get('tenantHost');",
                  "  const gatewayBase = trimBase(pm.variables.get('tenantGatewayUrl'));",
                  "  const m = String(url || '').match(/^(https?:\\/\\/([^/]+))(.*)$/);",
                  "  if (m && publicHost && m[2] === publicHost && gatewayBase) return gatewayBase + (m[3] || '');",
                  "  return url;",
                  "};",
                  "pm.expect(j.credential_configuration_ids, 'offered configurations').to.eql(['EmployeeBadge']);",
                  "const grant = j.grants && (j.grants['urn:ietf:params:oauth:grant-type:pre-authorized_code'] || j.grants.pre_authorized_code);",
                  "pm.expect(grant && grant['pre-authorized_code'], 'pre-authorized code').to.be.a('string').and.not.empty;",
                  "pm.collectionVariables.set('employeeBadgePreAuthCode', grant['pre-authorized_code']);",
                  "pm.collectionVariables.set('credentialIssuer', j.credential_issuer);",
                  "const parsed = String(j.credential_issuer).match(/^(https?:\\/\\/[^/]+)(\\/.*)?$/);",
                  "pm.collectionVariables.set('employeeBadgeMetadataUrl', rewritePublicUrl(parsed[1] + '/.well-known/openid-credential-issuer' + (parsed[2] || '')));"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Fetch the VCDM 1.1 OID4VCI metadata",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "url": "{{employeeBadgeMetadataUrl}}",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "description": "Reads the issuer metadata and confirms the VCDM 1.1 credential is advertised with the `jwt_vc_json` format."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('VCDM 1.1 metadata served', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "const trimBase = (base) => base && base.endsWith('/') ? base.slice(0, -1) : base;",
                  "const rewritePublicUrl = (url) => {",
                  "  const publicHost = pm.variables.get('tenantHost');",
                  "  const gatewayBase = trimBase(pm.variables.get('tenantGatewayUrl'));",
                  "  const m = String(url || '').match(/^(https?:\\/\\/([^/]+))(.*)$/);",
                  "  if (m && publicHost && m[2] === publicHost && gatewayBase) return gatewayBase + (m[3] || '');",
                  "  return url;",
                  "};",
                  "const configuration = (j.credential_configurations_supported || {})['EmployeeBadge'];",
                  "pm.test('the issuer advertises EmployeeBadge as jwt_vc_json', () => {",
                  "  pm.expect(configuration, 'credential configuration').to.be.an('object');",
                  "  pm.expect(configuration.format, 'format').to.eql('jwt_vc_json');",
                  "});",
                  "pm.collectionVariables.set('employeeBadgeCredentialEndpoint', rewritePublicUrl(j.credential_endpoint));",
                  "const advertisedAs = Array.isArray(j.authorization_servers) && j.authorization_servers.length ? j.authorization_servers[0] : null;",
                  "pm.collectionVariables.set('employeeBadgeTokenEndpoint', rewritePublicUrl(j.token_endpoint || (String(advertisedAs).replace(/\\/$/, '') + '/token')));",
                  "if (j.nonce_endpoint) pm.collectionVariables.set('employeeBadgeNonceEndpoint', rewritePublicUrl(j.nonce_endpoint));"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Exchange the EmployeeBadge pre-authorized code",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "url": "{{employeeBadgeTokenEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "urn:ietf:params:oauth:grant-type:pre-authorized_code"
                },
                {
                  "key": "pre-authorized_code",
                  "value": "{{employeeBadgePreAuthCode}}"
                }
              ]
            },
            "description": "Exchanges the pre-authorized code for the wallet access token and the opaque credential identifier."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('EmployeeBadge token issued', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "const authorization = Array.isArray(j.authorization_details) ? j.authorization_details.find((detail) => detail && detail.credential_configuration_id === 'EmployeeBadge') : undefined;",
                  "const identifiers = authorization && authorization.credential_identifiers;",
                  "pm.test('the token returns an opaque EmployeeBadge credential identifier', () => {",
                  "  pm.expect(identifiers, 'credential identifiers').to.be.an('array').with.lengthOf(1);",
                  "  pm.expect(identifiers[0], 'credential identifier').to.not.eql('EmployeeBadge');",
                  "});",
                  "pm.collectionVariables.set('employeeBadgeCredentialIdentifier', identifiers ? identifiers[0] : '');",
                  "pm.collectionVariables.set('walletAccessToken', j.access_token);",
                  "pm.collectionVariables.set('employeeBadgeNonce', j.c_nonce || '');"
                ]
              }
            }
          ]
        },
        {
          "name": "06 Request the EmployeeBadge credential",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{walletAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "POST",
            "url": "{{employeeBadgeCredentialEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_identifier\": \"{{employeeBadgeCredentialIdentifier}}\",\n  \"proofs\": {\n    \"jwt\": [\"{{proofJwt}}\"]\n  }\n}"
            },
            "description": "Issues the VCDM 1.1 credential as a VC-JWT with an ES256 proof of possession. The test decodes the returned JWT and checks the VCDM context, the credential type and the status entry. `credentialStatus.type` is `BitstringStatusListEntry` for VCDM 1.1 as well as VCDM 2.0."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Minimal collection-local P-256 holder fixture for Postman/Newman.",
                  "// The private scalar stays in a runtime collection variable and is never sent or logged.",
                  "const CURVE_P = BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff');",
                  "const CURVE_A = CURVE_P - 3n;",
                  "const CURVE_N = BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551');",
                  "const CURVE_G = {",
                  "  x: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'),",
                  "  y: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5')",
                  "};",
                  "const mod = (value, modulus) => {",
                  "  const result = value % modulus;",
                  "  return result >= 0n ? result : result + modulus;",
                  "};",
                  "const inverse = (value, modulus) => {",
                  "  let low = mod(value, modulus);",
                  "  let high = modulus;",
                  "  let lowCoefficient = 1n;",
                  "  let highCoefficient = 0n;",
                  "  while (low > 1n) {",
                  "    const ratio = high / low;",
                  "    const next = high - low * ratio;",
                  "    const nextCoefficient = highCoefficient - lowCoefficient * ratio;",
                  "    high = low;",
                  "    low = next;",
                  "    highCoefficient = lowCoefficient;",
                  "    lowCoefficient = nextCoefficient;",
                  "  }",
                  "  if (low !== 1n) throw new Error('P-256 modular inverse does not exist');",
                  "  return mod(lowCoefficient, modulus);",
                  "};",
                  "const pointAdd = (left, right) => {",
                  "  if (!left) return right;",
                  "  if (!right) return left;",
                  "  let slope;",
                  "  if (left.x === right.x) {",
                  "    if (mod(left.y + right.y, CURVE_P) === 0n) return null;",
                  "    slope = mod((3n * left.x * left.x + CURVE_A) * inverse(2n * left.y, CURVE_P), CURVE_P);",
                  "  } else {",
                  "    slope = mod((right.y - left.y) * inverse(right.x - left.x, CURVE_P), CURVE_P);",
                  "  }",
                  "  const x = mod(slope * slope - left.x - right.x, CURVE_P);",
                  "  return { x: x, y: mod(slope * (left.x - x) - left.y, CURVE_P) };",
                  "};",
                  "const scalarMultiply = (scalar, point) => {",
                  "  let remaining = scalar;",
                  "  let result = null;",
                  "  let addend = point;",
                  "  while (remaining > 0n) {",
                  "    if ((remaining & 1n) === 1n) result = pointAdd(result, addend);",
                  "    addend = pointAdd(addend, addend);",
                  "    remaining >>= 1n;",
                  "  }",
                  "  return result;",
                  "};",
                  "const randomScalar = () => {",
                  "  while (true) {",
                  "    const candidateHex = CryptoJS.lib.WordArray.random(32).toString(CryptoJS.enc.Hex);",
                  "    const candidate = BigInt('0x' + candidateHex);",
                  "    if (candidate > 0n && candidate < CURVE_N) return candidate;",
                  "  }",
                  "};",
                  "const hex32 = (value) => value.toString(16).padStart(64, '0');",
                  "const base64Url = (wordArray) => CryptoJS.enc.Base64.stringify(wordArray).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "const base64UrlHex = (hex) => base64Url(CryptoJS.enc.Hex.parse(hex));",
                  "const base64UrlJson = (value) => base64Url(CryptoJS.enc.Utf8.parse(JSON.stringify(value)));",
                  "if (pm.info.requestName === '02 Create the EmployeeBadge offer') {",
                  "  pm.collectionVariables.unset('holderWalletPrivateScalar');",
                  "  pm.collectionVariables.unset('holderWalletPublicJwk');",
                  "  pm.collectionVariables.unset('proofJwt');",
                  "}",
                  "let holderPrivateHex = String(pm.collectionVariables.get('holderWalletPrivateScalar') || '');",
                  "let holderPublicJwkText = String(pm.collectionVariables.get('holderWalletPublicJwk') || '');",
                  "let holderPublicJwk;",
                  "try { holderPublicJwk = JSON.parse(holderPublicJwkText); } catch (_) { holderPublicJwk = null; }",
                  "if (!/^[0-9a-f]{64}$/.test(holderPrivateHex) || !holderPublicJwk || holderPublicJwk.kty !== 'EC' || holderPublicJwk.crv !== 'P-256') {",
                  "  const holderPrivate = randomScalar();",
                  "  const holderPublic = scalarMultiply(holderPrivate, CURVE_G);",
                  "  holderPrivateHex = hex32(holderPrivate);",
                  "  holderPublicJwk = { kty: 'EC', crv: 'P-256', x: base64UrlHex(hex32(holderPublic.x)), y: base64UrlHex(hex32(holderPublic.y)) };",
                  "  holderPublicJwkText = JSON.stringify(holderPublicJwk);",
                  "  pm.collectionVariables.set('holderWalletPrivateScalar', holderPrivateHex);",
                  "  pm.collectionVariables.set('holderWalletPublicJwk', holderPublicJwkText);",
                  "}",
                  "const signEs256 = (signingInput) => {",
                  "  const privateScalar = BigInt('0x' + holderPrivateHex);",
                  "  const digest = BigInt('0x' + CryptoJS.SHA256(signingInput).toString(CryptoJS.enc.Hex));",
                  "  while (true) {",
                  "    const ephemeral = randomScalar();",
                  "    const point = scalarMultiply(ephemeral, CURVE_G);",
                  "    const r = mod(point.x, CURVE_N);",
                  "    if (r === 0n) continue;",
                  "    let s = mod(inverse(ephemeral, CURVE_N) * (digest + r * privateScalar), CURVE_N);",
                  "    if (s === 0n) continue;",
                  "    if (s > CURVE_N / 2n) s = CURVE_N - s;",
                  "    return base64UrlHex(hex32(r) + hex32(s));",
                  "  }",
                  "};",
                  "const buildHolderProof = (nonce) => {",
                  "  const header = { alg: 'ES256', typ: 'openid4vci-proof+jwt', jwk: holderPublicJwk };",
                  "  const payload = {",
                  "    aud: pm.collectionVariables.get('credentialIssuer') || pm.variables.get('tenantGatewayUrl'),",
                  "    iat: Math.floor(Date.now() / 1000)",
                  "  };",
                  "  if (nonce) payload.nonce = nonce;",
                  "  const signingInput = base64UrlJson(header) + '.' + base64UrlJson(payload);",
                  "  pm.collectionVariables.set('proofJwt', signingInput + '.' + signEs256(signingInput));",
                  "};",
                  "if (pm.info.requestName === '06 Request the EmployeeBadge credential') {",
                  "  const nonce = pm.collectionVariables.get('employeeBadgeNonce');",
                  "  const nonceEndpoint = pm.collectionVariables.get('employeeBadgeNonceEndpoint');",
                  "  if (nonce) {",
                  "    buildHolderProof(nonce);",
                  "  } else if (nonceEndpoint) {",
                  "    // The token response carries no c_nonce on this profile; the issuer hands one out at its nonce endpoint.",
                  "    pm.sendRequest({ url: nonceEndpoint, method: 'POST', header: { Host: pm.variables.replaceIn('{{tenantPublicAuthority}}') } }, (error, response) => {",
                  "      if (error) throw new Error('OID4VCI nonce fetch failed: ' + error.message);",
                  "      const fresh = response.json().c_nonce;",
                  "      if (!fresh) throw new Error('OID4VCI nonce response did not contain c_nonce');",
                  "      buildHolderProof(fresh);",
                  "    });",
                  "  } else {",
                  "    buildHolderProof(undefined);",
                  "  }",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "try {",
                  "  pm.test('EmployeeBadge credential issued', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "  const j = pm.response.json();",
                  "  const credential = (j.credentials && j.credentials[0] && j.credentials[0].credential) || j.credential;",
                  "  pm.expect(credential, 'credential').to.be.a('string').and.not.empty;",
                  "  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 payload = decodeJwtPart(credential, 1);",
                  "  // VCDM 1.1 VC-JWT nests the credential under 'vc'; the VCDM 2.0 JOSE form carries it at the top.",
                  "  const vc = payload.vc || payload;",
                  "  const contexts = [].concat(vc['@context'] || []);",
                  "  const types = [].concat(vc.type || []);",
                  "  const credentialStatus = [].concat(vc.credentialStatus || [])[0] || {};",
                  "  pm.test('the credential is a VCDM 1.1 verifiable credential of type EmployeeBadgeCredential', () => {",
                  "    pm.expect(contexts, 'JSON-LD context').to.include('https://www.w3.org/2018/credentials/v1');",
                  "    pm.expect(types, 'credential types').to.include('VerifiableCredential');",
                  "    pm.expect(types, 'credential types').to.include('EmployeeBadgeCredential');",
                  "  });",
                  "  pm.test('the credential status entry is a BitstringStatusListEntry on the bitstring list', () => {",
                  "    pm.expect(credentialStatus.type, 'credentialStatus.type').to.eql('BitstringStatusListEntry');",
                  "    pm.expect(String(credentialStatus.statusListCredential || credentialStatus.id || ''), 'status list reference').to.contain('/public/statuslists/status-bitstring');",
                  "    pm.expect(String(credentialStatus.statusListIndex || ''), 'allocated index').to.not.be.empty;",
                  "  });",
                  "  pm.collectionVariables.set('employeeBadgeStatusIndex', String(credentialStatus.statusListIndex || ''));",
                  "} finally {",
                  "  pm.collectionVariables.unset('holderWalletPrivateScalar');",
                  "  pm.collectionVariables.unset('holderWalletPublicJwk');",
                  "  pm.collectionVariables.unset('proofJwt');",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "07 Check the EmployeeBadge offer status",
          "request": {
            "method": "GET",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/credential/offers/e2e-employeebadge-001",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "description": "Reads the backend session by its business key. The lifecycle ends in credential_issued."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('EmployeeBadge session tracked', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "08 Read the EmployeeBadge status entry",
          "request": {
            "method": "GET",
            "url": "{{tenantStatusListApiBaseUrl}}/statuslists/{{bitstringStatusListId}}/entries/{{employeeBadgeStatusIndex}}",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "description": "Reads the bitstring entry the issued credential points at. A freshly issued credential is valid, so the entry reads 0."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('EmployeeBadge status entry returned', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const entry = pm.response.json();",
                  "pm.test('the freshly issued VCDM 1.1 credential is valid', () => {",
                  "  pm.expect(entry.statusListIndex, 'index').to.eql(Number(pm.collectionVariables.get('employeeBadgeStatusIndex')));",
                  "  pm.expect(entry.value, 'value').to.eql(0);",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "18 Issue W3C VCDM 2.0",
      "description": "Registers the Membership credential configuration in the VCDM 2.0 JSON-LD JWT VC format, binds it to the same bitstring status list, and issues one pre-authorized credential. The status entry type is BitstringStatusListEntry here too: VCDM 2.0 changes the context and the envelope, not the status entry type this deployment writes.",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "02 Create the Membership offer",
          "request": {
            "method": "POST",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/credential/offers",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_configuration_ids\": [\n    \"Membership\"\n  ],\n  \"grants\": {\n    \"pre_authorized_code\": {}\n  },\n  \"credential_subject_data\": {\n    \"id\": \"did:example:member-0001\",\n    \"type\": \"MembershipCredential\",\n    \"name\": \"Erika Mustermann\",\n    \"membershipNumber\": \"ACME-M-2026-0042\",\n    \"membershipLevel\": \"gold\",\n    \"validFrom\": \"2026-01-01\"\n  },\n  \"correlation_id\": \"e2e-membership-001\"\n}"
            },
            "description": "Creates a pre-authorized offer for the VCDM 2.0 credential with the subject data supplied inline."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Membership offer created', () => pm.expect([200, 201], 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "const trimBase = (base) => base && base.endsWith('/') ? base.slice(0, -1) : base;",
                  "const rewritePublicUrl = (url) => {",
                  "  const publicHost = pm.variables.get('tenantHost');",
                  "  const gatewayBase = trimBase(pm.variables.get('tenantGatewayUrl'));",
                  "  const m = String(url || '').match(/^(https?:\\/\\/([^/]+))(.*)$/);",
                  "  if (m && publicHost && m[2] === publicHost && gatewayBase) return gatewayBase + (m[3] || '');",
                  "  return url;",
                  "};",
                  "pm.expect(j.offer_uri, 'offer uri').to.be.a('string').and.not.empty;",
                  "const m = String(j.offer_uri).match(/credential_offer_uri=([^&]+)/);",
                  "pm.expect(m, 'offer uri carries credential_offer_uri').to.not.eql(null);",
                  "pm.collectionVariables.set('membershipOfferUri', rewritePublicUrl(decodeURIComponent(m[1])));"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Resolve the Membership offer",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "url": "{{membershipOfferUri}}",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "description": "Resolves the offer as a wallet does and reads the pre-authorized code."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Membership offer resolved', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "const trimBase = (base) => base && base.endsWith('/') ? base.slice(0, -1) : base;",
                  "const rewritePublicUrl = (url) => {",
                  "  const publicHost = pm.variables.get('tenantHost');",
                  "  const gatewayBase = trimBase(pm.variables.get('tenantGatewayUrl'));",
                  "  const m = String(url || '').match(/^(https?:\\/\\/([^/]+))(.*)$/);",
                  "  if (m && publicHost && m[2] === publicHost && gatewayBase) return gatewayBase + (m[3] || '');",
                  "  return url;",
                  "};",
                  "pm.expect(j.credential_configuration_ids, 'offered configurations').to.eql(['Membership']);",
                  "const grant = j.grants && (j.grants['urn:ietf:params:oauth:grant-type:pre-authorized_code'] || j.grants.pre_authorized_code);",
                  "pm.expect(grant && grant['pre-authorized_code'], 'pre-authorized code').to.be.a('string').and.not.empty;",
                  "pm.collectionVariables.set('membershipPreAuthCode', grant['pre-authorized_code']);",
                  "pm.collectionVariables.set('credentialIssuer', j.credential_issuer);",
                  "const parsed = String(j.credential_issuer).match(/^(https?:\\/\\/[^/]+)(\\/.*)?$/);",
                  "pm.collectionVariables.set('membershipMetadataUrl', rewritePublicUrl(parsed[1] + '/.well-known/openid-credential-issuer' + (parsed[2] || '')));"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Fetch the VCDM 2.0 OID4VCI metadata",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "url": "{{membershipMetadataUrl}}",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "description": "Reads the issuer metadata and confirms the VCDM 2.0 credential is advertised with the `jwt_vc_json-ld` format."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('VCDM 2.0 metadata served', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "const trimBase = (base) => base && base.endsWith('/') ? base.slice(0, -1) : base;",
                  "const rewritePublicUrl = (url) => {",
                  "  const publicHost = pm.variables.get('tenantHost');",
                  "  const gatewayBase = trimBase(pm.variables.get('tenantGatewayUrl'));",
                  "  const m = String(url || '').match(/^(https?:\\/\\/([^/]+))(.*)$/);",
                  "  if (m && publicHost && m[2] === publicHost && gatewayBase) return gatewayBase + (m[3] || '');",
                  "  return url;",
                  "};",
                  "const configuration = (j.credential_configurations_supported || {})['Membership'];",
                  "pm.test('the issuer advertises Membership as jwt_vc_json-ld', () => {",
                  "  pm.expect(configuration, 'credential configuration').to.be.an('object');",
                  "  pm.expect(configuration.format, 'format').to.eql('jwt_vc_json-ld');",
                  "});",
                  "pm.collectionVariables.set('membershipCredentialEndpoint', rewritePublicUrl(j.credential_endpoint));",
                  "const advertisedAs = Array.isArray(j.authorization_servers) && j.authorization_servers.length ? j.authorization_servers[0] : null;",
                  "pm.collectionVariables.set('membershipTokenEndpoint', rewritePublicUrl(j.token_endpoint || (String(advertisedAs).replace(/\\/$/, '') + '/token')));",
                  "if (j.nonce_endpoint) pm.collectionVariables.set('membershipNonceEndpoint', rewritePublicUrl(j.nonce_endpoint));"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Exchange the Membership pre-authorized code",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "url": "{{membershipTokenEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "urn:ietf:params:oauth:grant-type:pre-authorized_code"
                },
                {
                  "key": "pre-authorized_code",
                  "value": "{{membershipPreAuthCode}}"
                }
              ]
            },
            "description": "Exchanges the pre-authorized code for the wallet access token and the opaque credential identifier."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Membership token issued', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const j = pm.response.json();",
                  "const authorization = Array.isArray(j.authorization_details) ? j.authorization_details.find((detail) => detail && detail.credential_configuration_id === 'Membership') : undefined;",
                  "const identifiers = authorization && authorization.credential_identifiers;",
                  "pm.test('the token returns an opaque Membership credential identifier', () => {",
                  "  pm.expect(identifiers, 'credential identifiers').to.be.an('array').with.lengthOf(1);",
                  "  pm.expect(identifiers[0], 'credential identifier').to.not.eql('Membership');",
                  "});",
                  "pm.collectionVariables.set('membershipCredentialIdentifier', identifiers ? identifiers[0] : '');",
                  "pm.collectionVariables.set('walletAccessToken', j.access_token);",
                  "pm.collectionVariables.set('membershipNonce', j.c_nonce || '');"
                ]
              }
            }
          ]
        },
        {
          "name": "06 Request the Membership credential",
          "request": {
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{walletAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "method": "POST",
            "url": "{{membershipCredentialEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_identifier\": \"{{membershipCredentialIdentifier}}\",\n  \"proofs\": {\n    \"jwt\": [\"{{proofJwt}}\"]\n  }\n}"
            },
            "description": "Issues the VCDM 2.0 credential as a VC-JWT with an ES256 proof of possession. The test decodes the returned JWT and checks the VCDM context, the credential type and the status entry. `credentialStatus.type` is `BitstringStatusListEntry` for VCDM 1.1 as well as VCDM 2.0."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Minimal collection-local P-256 holder fixture for Postman/Newman.",
                  "// The private scalar stays in a runtime collection variable and is never sent or logged.",
                  "const CURVE_P = BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff');",
                  "const CURVE_A = CURVE_P - 3n;",
                  "const CURVE_N = BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551');",
                  "const CURVE_G = {",
                  "  x: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'),",
                  "  y: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5')",
                  "};",
                  "const mod = (value, modulus) => {",
                  "  const result = value % modulus;",
                  "  return result >= 0n ? result : result + modulus;",
                  "};",
                  "const inverse = (value, modulus) => {",
                  "  let low = mod(value, modulus);",
                  "  let high = modulus;",
                  "  let lowCoefficient = 1n;",
                  "  let highCoefficient = 0n;",
                  "  while (low > 1n) {",
                  "    const ratio = high / low;",
                  "    const next = high - low * ratio;",
                  "    const nextCoefficient = highCoefficient - lowCoefficient * ratio;",
                  "    high = low;",
                  "    low = next;",
                  "    highCoefficient = lowCoefficient;",
                  "    lowCoefficient = nextCoefficient;",
                  "  }",
                  "  if (low !== 1n) throw new Error('P-256 modular inverse does not exist');",
                  "  return mod(lowCoefficient, modulus);",
                  "};",
                  "const pointAdd = (left, right) => {",
                  "  if (!left) return right;",
                  "  if (!right) return left;",
                  "  let slope;",
                  "  if (left.x === right.x) {",
                  "    if (mod(left.y + right.y, CURVE_P) === 0n) return null;",
                  "    slope = mod((3n * left.x * left.x + CURVE_A) * inverse(2n * left.y, CURVE_P), CURVE_P);",
                  "  } else {",
                  "    slope = mod((right.y - left.y) * inverse(right.x - left.x, CURVE_P), CURVE_P);",
                  "  }",
                  "  const x = mod(slope * slope - left.x - right.x, CURVE_P);",
                  "  return { x: x, y: mod(slope * (left.x - x) - left.y, CURVE_P) };",
                  "};",
                  "const scalarMultiply = (scalar, point) => {",
                  "  let remaining = scalar;",
                  "  let result = null;",
                  "  let addend = point;",
                  "  while (remaining > 0n) {",
                  "    if ((remaining & 1n) === 1n) result = pointAdd(result, addend);",
                  "    addend = pointAdd(addend, addend);",
                  "    remaining >>= 1n;",
                  "  }",
                  "  return result;",
                  "};",
                  "const randomScalar = () => {",
                  "  while (true) {",
                  "    const candidateHex = CryptoJS.lib.WordArray.random(32).toString(CryptoJS.enc.Hex);",
                  "    const candidate = BigInt('0x' + candidateHex);",
                  "    if (candidate > 0n && candidate < CURVE_N) return candidate;",
                  "  }",
                  "};",
                  "const hex32 = (value) => value.toString(16).padStart(64, '0');",
                  "const base64Url = (wordArray) => CryptoJS.enc.Base64.stringify(wordArray).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "const base64UrlHex = (hex) => base64Url(CryptoJS.enc.Hex.parse(hex));",
                  "const base64UrlJson = (value) => base64Url(CryptoJS.enc.Utf8.parse(JSON.stringify(value)));",
                  "if (pm.info.requestName === '02 Create the Membership offer') {",
                  "  pm.collectionVariables.unset('holderWalletPrivateScalar');",
                  "  pm.collectionVariables.unset('holderWalletPublicJwk');",
                  "  pm.collectionVariables.unset('proofJwt');",
                  "}",
                  "let holderPrivateHex = String(pm.collectionVariables.get('holderWalletPrivateScalar') || '');",
                  "let holderPublicJwkText = String(pm.collectionVariables.get('holderWalletPublicJwk') || '');",
                  "let holderPublicJwk;",
                  "try { holderPublicJwk = JSON.parse(holderPublicJwkText); } catch (_) { holderPublicJwk = null; }",
                  "if (!/^[0-9a-f]{64}$/.test(holderPrivateHex) || !holderPublicJwk || holderPublicJwk.kty !== 'EC' || holderPublicJwk.crv !== 'P-256') {",
                  "  const holderPrivate = randomScalar();",
                  "  const holderPublic = scalarMultiply(holderPrivate, CURVE_G);",
                  "  holderPrivateHex = hex32(holderPrivate);",
                  "  holderPublicJwk = { kty: 'EC', crv: 'P-256', x: base64UrlHex(hex32(holderPublic.x)), y: base64UrlHex(hex32(holderPublic.y)) };",
                  "  holderPublicJwkText = JSON.stringify(holderPublicJwk);",
                  "  pm.collectionVariables.set('holderWalletPrivateScalar', holderPrivateHex);",
                  "  pm.collectionVariables.set('holderWalletPublicJwk', holderPublicJwkText);",
                  "}",
                  "const signEs256 = (signingInput) => {",
                  "  const privateScalar = BigInt('0x' + holderPrivateHex);",
                  "  const digest = BigInt('0x' + CryptoJS.SHA256(signingInput).toString(CryptoJS.enc.Hex));",
                  "  while (true) {",
                  "    const ephemeral = randomScalar();",
                  "    const point = scalarMultiply(ephemeral, CURVE_G);",
                  "    const r = mod(point.x, CURVE_N);",
                  "    if (r === 0n) continue;",
                  "    let s = mod(inverse(ephemeral, CURVE_N) * (digest + r * privateScalar), CURVE_N);",
                  "    if (s === 0n) continue;",
                  "    if (s > CURVE_N / 2n) s = CURVE_N - s;",
                  "    return base64UrlHex(hex32(r) + hex32(s));",
                  "  }",
                  "};",
                  "const buildHolderProof = (nonce) => {",
                  "  const header = { alg: 'ES256', typ: 'openid4vci-proof+jwt', jwk: holderPublicJwk };",
                  "  const payload = {",
                  "    aud: pm.collectionVariables.get('credentialIssuer') || pm.variables.get('tenantGatewayUrl'),",
                  "    iat: Math.floor(Date.now() / 1000)",
                  "  };",
                  "  if (nonce) payload.nonce = nonce;",
                  "  const signingInput = base64UrlJson(header) + '.' + base64UrlJson(payload);",
                  "  pm.collectionVariables.set('proofJwt', signingInput + '.' + signEs256(signingInput));",
                  "};",
                  "if (pm.info.requestName === '06 Request the Membership credential') {",
                  "  const nonce = pm.collectionVariables.get('membershipNonce');",
                  "  const nonceEndpoint = pm.collectionVariables.get('membershipNonceEndpoint');",
                  "  if (nonce) {",
                  "    buildHolderProof(nonce);",
                  "  } else if (nonceEndpoint) {",
                  "    // The token response carries no c_nonce on this profile; the issuer hands one out at its nonce endpoint.",
                  "    pm.sendRequest({ url: nonceEndpoint, method: 'POST', header: { Host: pm.variables.replaceIn('{{tenantPublicAuthority}}') } }, (error, response) => {",
                  "      if (error) throw new Error('OID4VCI nonce fetch failed: ' + error.message);",
                  "      const fresh = response.json().c_nonce;",
                  "      if (!fresh) throw new Error('OID4VCI nonce response did not contain c_nonce');",
                  "      buildHolderProof(fresh);",
                  "    });",
                  "  } else {",
                  "    buildHolderProof(undefined);",
                  "  }",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "try {",
                  "  pm.test('Membership credential issued', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "  const j = pm.response.json();",
                  "  const credential = (j.credentials && j.credentials[0] && j.credentials[0].credential) || j.credential;",
                  "  pm.expect(credential, 'credential').to.be.a('string').and.not.empty;",
                  "  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 payload = decodeJwtPart(credential, 1);",
                  "  // VCDM 1.1 VC-JWT nests the credential under 'vc'; the VCDM 2.0 JOSE form carries it at the top.",
                  "  const vc = payload.vc || payload;",
                  "  const contexts = [].concat(vc['@context'] || []);",
                  "  const types = [].concat(vc.type || []);",
                  "  const credentialStatus = [].concat(vc.credentialStatus || [])[0] || {};",
                  "  pm.test('the credential is a VCDM 2.0 verifiable credential of type MembershipCredential', () => {",
                  "    pm.expect(contexts, 'JSON-LD context').to.include('https://www.w3.org/ns/credentials/v2');",
                  "    pm.expect(types, 'credential types').to.include('VerifiableCredential');",
                  "    pm.expect(types, 'credential types').to.include('MembershipCredential');",
                  "  });",
                  "  pm.test('the credential status entry is a BitstringStatusListEntry on the bitstring list', () => {",
                  "    pm.expect(credentialStatus.type, 'credentialStatus.type').to.eql('BitstringStatusListEntry');",
                  "    pm.expect(String(credentialStatus.statusListCredential || credentialStatus.id || ''), 'status list reference').to.contain('/public/statuslists/status-bitstring');",
                  "    pm.expect(String(credentialStatus.statusListIndex || ''), 'allocated index').to.not.be.empty;",
                  "  });",
                  "  pm.collectionVariables.set('membershipStatusIndex', String(credentialStatus.statusListIndex || ''));",
                  "} finally {",
                  "  pm.collectionVariables.unset('holderWalletPrivateScalar');",
                  "  pm.collectionVariables.unset('holderWalletPublicJwk');",
                  "  pm.collectionVariables.unset('proofJwt');",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "07 Check the Membership offer status",
          "request": {
            "method": "GET",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/credential/offers/e2e-membership-001",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "description": "Reads the backend session by its business key. The lifecycle ends in credential_issued."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Membership session tracked', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "08 Read the Membership status entry",
          "request": {
            "method": "GET",
            "url": "{{tenantStatusListApiBaseUrl}}/statuslists/{{bitstringStatusListId}}/entries/{{membershipStatusIndex}}",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "description": "Reads the bitstring entry the issued credential points at. A freshly issued credential is valid, so the entry reads 0."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Membership status entry returned', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.eql(200));",
                  "const entry = pm.response.json();",
                  "pm.test('the freshly issued VCDM 2.0 credential is valid', () => {",
                  "  pm.expect(entry.statusListIndex, 'index').to.eql(Number(pm.collectionVariables.get('membershipStatusIndex')));",
                  "  pm.expect(entry.value, 'value').to.eql(0);",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "19 Issue Through a Pipeline",
      "description": "Exercises the pipeline issuance API where attributes are contributed into a session before approval releases issuance.",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "01 Initialize pipeline session",
          "request": {
            "method": "POST",
            "url": "{{tenantIssuerApiBaseUrl}}/backend/sessions",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "20 Authorization Code Issuance",
      "description": "Creates an OID4VCI offer that uses the authorization code grant and fetches authorization server metadata for the wallet flow.",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": {
            "auth": {
              "type": "noauth"
            },
            "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": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('as metadata served', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "21 DCQL Queries",
      "description": "Creates and lists the DCQL query definitions used by the verifier, including a combined query that requests EuPid and Mdl together.\n\nDocumentation: https://docs.sphereon.com/edk/admin-console/guides/verify-credentials#define-the-dcql-query",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "01 Create EuPid query",
          "request": {
            "method": "POST",
            "url": "{{tenantDcqlApiBaseUrl}}/queries",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"queryId\": \"walkthrough-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', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const q = pm.response.json();",
                  "pm.test('EuPid DCQL query content is versioned and enabled', () => {",
                  "  pm.expect(q.queryId).to.eql('walkthrough-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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"queryId\": \"walkthrough-mdl-mdoc\",\n  \"name\": \"Driving licence check\",\n  \"description\": \"Requests the holder name 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\", \"given_name\"] }\n        ]\n      }\n    ]\n  }\n}"
            },
            "description": "Selects family_name and given_name from the ISO 18013-5 namespace."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mdl query stored', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const q = pm.response.json();",
                  "pm.test('mDL DCQL query content is versioned and enabled', () => {",
                  "  pm.expect(q.queryId).to.eql('walkthrough-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');",
                  "  pm.expect(credential.claims.map((claim) => claim.path)).to.eql([['org.iso.18013.5.1', 'family_name'], ['org.iso.18013.5.1', 'given_name']]);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Create combined query",
          "request": {
            "method": "POST",
            "url": "{{tenantDcqlApiBaseUrl}}/queries",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"queryId\": \"walkthrough-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\", \"family_name\"] },\n          { \"path\": [\"org.iso.18013.5.1\", \"given_name\"] }\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', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const q = pm.response.json();",
                  "pm.test('combined DCQL query contains EuPid and mDL credentials', () => {",
                  "  pm.expect(q.queryId).to.eql('walkthrough-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');",
                  "  pm.expect(byId.mdl.claims.map((claim) => claim.path)).to.eql([['org.iso.18013.5.1', 'family_name'], ['org.iso.18013.5.1', 'given_name']]);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "04 List queries",
          "request": {
            "method": "GET",
            "url": "{{tenantDcqlApiBaseUrl}}/queries",
            "description": "Lists the tenant's stored DCQL query configurations.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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[0]).to.match(/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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "22 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.\n\nDocumentation: https://docs.sphereon.com/edk/admin-console/guides/verify-credentials",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "01 Create verification request",
          "request": {
            "method": "POST",
            "url": "{{tenantVerifierBackendBaseUrl}}/auth/requests",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"query_id\": \"eupid-and-mdl\",\n  \"verifier_id\": \"{{verifierId}}\",\n  \"client_id\": \"decentralized_identifier:{{did}}\",\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();",
                  "pm.expect(j.request_uri, 'wallet authorization request URI').to.be.a('string').and.not.empty;",
                  "const requestObjectMatch = j.request_uri.match(/[?&]request_uri=([^&]+)/);",
                  "pm.expect(requestObjectMatch, 'authorization request contains request_uri').to.not.eql(null);",
                  "pm.collectionVariables.set('requestObjectUri', decodeURIComponent(requestObjectMatch[1]));",
                  "pm.collectionVariables.set('verifyCorrelationId', j.correlation_id || 'e2e-verify-001');"
                ]
              }
            }
          ]
        },
        {
          "name": "01a Fetch signed verification request object",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "url": "{{requestObjectUri}}",
            "header": [
              {
                "key": "Accept",
                "value": "application/oauth-authz-req+jwt, application/jwt"
              }
            ],
            "description": "Fetches the signed verifier request object and proves its issuer and signing key both identify the activation-created tenant DID."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('signed verification request object returned', () => pm.response.to.have.status(200));",
                  "const compact = pm.response.text().trim();",
                  "const parts = compact.split('.');",
                  "pm.expect(parts, 'compact request-object JWT').to.have.length(3);",
                  "const decodeJwtPart = (index) => {",
                  "  const normalized = parts[index].replace(/-/g, '+').replace(/_/g, '/');",
                  "  const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);",
                  "  return JSON.parse(atob(padded));",
                  "};",
                  "const header = decodeJwtPart(0);",
                  "const payload = decodeJwtPart(1);",
                  "pm.test('verifier request object issuer and kid use the same DID', () => {",
                  "  pm.expect(payload.iss).to.eql(pm.collectionVariables.get('did'));",
                  "  pm.expect(header.kid).to.eql(pm.collectionVariables.get('verifierDidVerificationMethodId'));",
                  "  pm.expect(header.kid.startsWith(payload.iss + '#')).to.eql(true);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "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": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('session removed', () => pm.expect([200, 204]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "23 Trust Domains and Trust Lists",
      "description": "Trust Domains V2: domains and anchors as evidence, admission classes as what that evidence may answer, attachments as who uses it, eligibility grants as the governance cap, and the ISO 18013-5 Annex C VICAL path. Runs after tenant onboarding because it starts from the seeded issuer trust domain. The trust list and LoTE source requests show how a list you publish yourself, in ETSI TS 119 612 or TS 119 602 form, is registered as a source the domain consumes.\n\nDocumentation: https://docs.sphereon.com/edk/admin-console/guides/trust-domains",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "item": [
        {
          "name": "01 List trust domains",
          "request": {
            "method": "GET",
            "url": "{{tenantTrustDomainApiBaseUrl}}/domains",
            "description": "Reads the tenant trust-domain inventory. Onboarding seeds one issuer trust domain, so an empty list here means the sample-data seeder did not run or failed."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('trust domains listed', () => pm.response.to.have.status(200));",
                  "const items = pm.response.json().items || [];",
                  "pm.test('the onboarding-seeded issuer trust domain exists', () => pm.expect(items.length).to.be.above(0));",
                  "const seeded = items.find((d) => /issuer trust/i.test(d.displayName)) || items[0];",
                  "pm.collectionVariables.set('trustDomainId', seeded.domainId);",
                  "pm.collectionVariables.set('trustDomainVersion', String(seeded.version));",
                  "pm.test('seeded domain is active', () => pm.expect(seeded.status).to.eql('ACTIVE'));"
                ]
              }
            }
          ]
        },
        {
          "name": "02 List anchors of the seeded domain",
          "request": {
            "method": "GET",
            "url": "{{tenantTrustDomainApiBaseUrl}}/domains/{{trustDomainId}}/anchors",
            "description": "Each entry pairs the stored anchor with an enforcement-safe identifier summary. The seeder creates one anchor per evidence mechanism from the issuer key material."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('anchors listed', () => pm.response.to.have.status(200));",
                  "const items = pm.response.json().items || [];",
                  "pm.test('seeded anchors present', () => pm.expect(items.length).to.be.above(0));",
                  "const mechanisms = items.map((i) => i.anchor.evidenceMechanism);",
                  "pm.test('issuer DID anchor seeded', () => pm.expect(mechanisms).to.include('DID'));",
                  "const did = items.find((i) => i.anchor.evidenceMechanism === 'DID');",
                  "pm.collectionVariables.set('trustAnchorId', did.anchor.anchorId);",
                  "pm.collectionVariables.set('trustAnchorVersion', String(did.anchor.version));",
                  "pm.collectionVariables.set('trustIdentityIdentifierId', did.anchor.identityIdentifierId);",
                  "pm.test('seeded anchors are tenant-produced material', () => pm.expect(did.anchor.origin).to.eql('TENANT_PUBLIC'));",
                  "pm.test('issuer X.509 anchor seeded', () => pm.expect(mechanisms).to.include('X509'));",
                  "const x509 = items.find((i) => i.anchor.evidenceMechanism === 'X509');",
                  "pm.collectionVariables.set('trustX509IdentityIdentifierId', x509.anchor.identityIdentifierId);"
                ]
              }
            }
          ]
        },
        {
          "name": "03 List admissions of the issuer anchor",
          "request": {
            "method": "GET",
            "url": "{{tenantTrustDomainApiBaseUrl}}/domains/{{trustDomainId}}/anchors/{{trustAnchorId}}/admissions",
            "description": "Membership in a domain is not admission. An anchor answers a usage only when it holds that usage admission class, so an empty list here would mean the anchor is inert."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('admissions listed', () => pm.response.to.have.status(200));",
                  "const items = pm.response.json().items || [];",
                  "pm.test('issuer anchor is admitted as CREDENTIAL_ISSUER', () =>",
                  "  pm.expect(items.map((a) => a.admissionClass)).to.include('CREDENTIAL_ISSUER'));"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Read the tenant issuer-trust attachment",
          "request": {
            "method": "GET",
            "url": "{{tenantTrustDomainApiBaseUrl}}/attachments/TENANT/{{tenantId}}/CREDENTIAL_ISSUER_TRUST",
            "description": "The tenant fallback is an ordinary attachment. A tenant with none fails closed on every credential-issuer decision, which is the state a brand-new tenant starts in."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant attachment returned', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('tenant issuer trust is fail closed', () => pm.expect(body.attachment.policy.mode).to.eql('FAIL_CLOSED'));",
                  "pm.test('tenant attachment selects the seeded domain', () =>",
                  "  pm.expect(body.domains.map((d) => d.domainId)).to.include(pm.collectionVariables.get('trustDomainId')));",
                  "pm.test('domain ordinals are contiguous from zero', () =>",
                  "  pm.expect(body.domains.map((d) => d.order !== undefined ? d.order : d.ordinal)).to.eql(body.domains.map((_, i) => i)));"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Read the verifier eligibility grant",
          "request": {
            "method": "GET",
            "url": "{{tenantTrustDomainApiBaseUrl}}/eligibility/OID4VP_VERIFIER/CREDENTIAL_ISSUER_TRUST",
            "description": "The grant caps which domains a verifier may select. It is governance, not selection, and is checked only against non-tenant attachments."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('eligibility grant returned', () => pm.response.to.have.status(200));",
                  "const items = pm.response.json().items || [];",
                  "const eligible = items.flatMap((g) => g.eligibleDomainIds || []);",
                  "pm.test('seeded domain is eligible for verifiers', () =>",
                  "  pm.expect(eligible).to.include(pm.collectionVariables.get('trustDomainId')));",
                  "pm.collectionVariables.set('trustEligibilityVersion', String(items[0] ? items[0].version : 1));",
                  "pm.collectionVariables.set('trustEligibilityGrantId', items[0] ? items[0].grantId : 'grant-verifier-issuer-trust');",
                  "pm.collectionVariables.set('trustEligibleDomainIds', JSON.stringify(eligible));"
                ]
              }
            }
          ]
        },
        {
          "name": "06 List consumers of the seeded domain",
          "request": {
            "method": "GET",
            "url": "{{tenantTrustDomainApiBaseUrl}}/domains/{{trustDomainId}}/consumers",
            "description": "Reverse lookup used before disabling or deleting a domain. There is no pointer stored on the domain; this is a query over attachments."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const detail = () => pm.response.code + ' ' + pm.response.text();",
                  "pm.test('consumers listed', () => pm.expect(pm.response.code, detail()).to.eql(200));",
                  "const items = pm.response.json().items || [];",
                  "pm.test('the tenant consumes the seeded domain', () =>",
                  "  pm.expect(items.map((c) => c.consumerKind)).to.include('TENANT'));"
                ]
              }
            }
          ]
        },
        {
          "name": "07 Create a second trust domain",
          "request": {
            "method": "POST",
            "url": "{{tenantTrustDomainApiBaseUrl}}/domains",
            "description": "New domains start as DRAFT. A draft domain can be attached but never resolves, because resolution requires ACTIVE.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"displayName\": \"Postman mdoc VICAL domain\",\n  \"description\": \"Created by the customer release gate to exercise attachments, eligibility and mdoc VICAL.\"\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('domain created', () => pm.expect(pm.response.code).to.be.oneOf([200, 201]));",
                  "const d = pm.response.json();",
                  "pm.collectionVariables.set('vicalDomainId', d.domainId);",
                  "pm.collectionVariables.set('vicalDomainVersion', String(d.version));",
                  "pm.test('new domains start as DRAFT', () => pm.expect(d.status).to.eql('DRAFT'));"
                ]
              }
            }
          ]
        },
        {
          "name": "08 Activate the second trust domain",
          "request": {
            "method": "PUT",
            "url": "{{tenantTrustDomainApiBaseUrl}}/domains/{{vicalDomainId}}",
            "description": "Mutations are optimistically concurrent: the current version goes in If-Match and a stale value returns 412.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "If-Match",
                "value": "\"{{vicalDomainVersion}}\""
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"domainId\": \"{{vicalDomainId}}\",\n  \"displayName\": \"Postman mdoc VICAL domain\",\n  \"status\": \"ACTIVE\",\n  \"version\": {{vicalDomainVersion}}\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('domain activated', () => pm.response.to.have.status(200));",
                  "const d = pm.response.json();",
                  "pm.test('status is ACTIVE', () => pm.expect(d.status).to.eql('ACTIVE'));",
                  "pm.collectionVariables.set('vicalDomainVersion', String(d.version));"
                ]
              }
            }
          ]
        },
        {
          "name": "11 Create a VICAL signer anchor",
          "request": {
            "method": "POST",
            "url": "{{tenantTrustDomainApiBaseUrl}}/domains/{{vicalDomainId}}/anchors",
            "description": "An anchor references public evidence by identity-identifier id rather than carrying raw material. This reuses the identifier the onboarding seeder already materialized. A VICAL is a COSE_Sign1 over an X.509 chain, so its signer anchor carries X.509 evidence; the seeded issuer certificate identifier stands in for the VICAL provider here.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"domainId\": \"{{vicalDomainId}}\",\n  \"identityIdentifierId\": \"{{trustX509IdentityIdentifierId}}\",\n  \"evidenceMechanism\": \"X509\",\n  \"origin\": \"IMPORTED\",\n  \"status\": \"ACTIVE\",\n  \"metadata\": {\n    \"source\": \"customer-release-gate\",\n    \"productRole\": \"mdoc-vical-signer\"\n  }\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('anchor created', () => pm.expect(pm.response.code).to.be.oneOf([200, 201]));",
                  "const a = pm.response.json();",
                  "pm.collectionVariables.set('vicalAnchorId', a.anchorId);",
                  "pm.collectionVariables.set('vicalAnchorVersion', String(a.version));"
                ]
              }
            }
          ]
        },
        {
          "name": "12 Refuse a VICAL whose signer anchor is not admitted",
          "request": {
            "method": "PUT",
            "url": "{{tenantTrustDomainApiBaseUrl}}/domains/{{vicalDomainId}}/anchors/{{vicalAnchorId}}/mdoc-vical",
            "description": "Negative check on the ISO 18013-5 Annex C path. The anchor exists and is ACTIVE but holds no MDOC_VICAL_SIGNER admission, so it must not be usable to vouch for a whole certificate list.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://vical.example.com/vical.cbor\",\n  \"signerAnchorIds\": [\n    \"{{vicalAnchorId}}\"\n  ],\n  \"issuerAnchorIds\": [],\n  \"requiredCertificateProfiles\": [],\n  \"enabled\": true\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('an unadmitted VICAL signer is refused', () => pm.expect(pm.response.code).to.be.within(400, 499));",
                  "pm.test('the refusal names the missing admission', () =>",
                  "  pm.expect(pm.response.text()).to.include('MDOC_VICAL_SIGNER'));"
                ]
              }
            }
          ]
        },
        {
          "name": "13 Admit the anchor as MDOC_VICAL_SIGNER",
          "request": {
            "method": "PUT",
            "url": "{{tenantTrustDomainApiBaseUrl}}/domains/{{vicalDomainId}}/anchors/{{vicalAnchorId}}/admissions/MDOC_VICAL_SIGNER",
            "description": "Admission is a separate decision from membership. MDOC_VICAL_SIGNER only lets the anchor verify a VICAL signature; it does not make it a credential issuer.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "If-Match",
                "value": "\"{{vicalAnchorVersion}}\""
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"anchorId\": \"{{vicalAnchorId}}\",\n  \"admissionClass\": \"MDOC_VICAL_SIGNER\"\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const detail = () => pm.response.code + ' ' + pm.response.text();",
                  "pm.test('admission granted', () => pm.expect(pm.response.code, detail()).to.eql(200));",
                  "pm.test('admission class echoed', () => pm.expect(pm.response.json().admissionClass).to.eql('MDOC_VICAL_SIGNER'));"
                ]
              }
            }
          ]
        },
        {
          "name": "15 Configure the VICAL source",
          "request": {
            "method": "PUT",
            "url": "{{tenantTrustDomainApiBaseUrl}}/domains/{{vicalDomainId}}/anchors/{{vicalAnchorId}}/mdoc-vical",
            "description": "The signer anchor now holds MDOC_VICAL_SIGNER, so the same body that was refused in step 12 is accepted.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://vical.example.com/vical.cbor\",\n  \"signerAnchorIds\": [\n    \"{{vicalAnchorId}}\"\n  ],\n  \"issuerAnchorIds\": [],\n  \"requiredCertificateProfiles\": [\n    \"iso18013-5-iaca\"\n  ],\n  \"enabled\": true\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const detail = () => pm.response.code + ' ' + pm.response.text();",
                  "pm.test('VICAL configured', () => pm.expect(pm.response.code, detail()).to.eql(200));",
                  "const v = pm.response.json();",
                  "pm.test('the source round-trips', () => {",
                  "  pm.expect(v.source.url).to.eql('https://vical.example.com/vical.cbor');",
                  "  pm.expect(v.source.signerAnchorIds).to.include(pm.collectionVariables.get('vicalAnchorId'));",
                  "  pm.expect(v.source.enabled).to.be.true;",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "17 Remove the VICAL configuration",
          "request": {
            "method": "DELETE",
            "url": "{{tenantTrustDomainApiBaseUrl}}/domains/{{vicalDomainId}}/anchors/{{vicalAnchorId}}/mdoc-vical",
            "description": "Removing the VICAL leaves the anchor and its admission in place; only the source configuration goes away."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const detail = () => pm.response.code + ' ' + pm.response.text();",
                  "pm.test('VICAL removed', () => pm.expect(pm.response.code, detail()).to.eql(200));",
                  "pm.test('the anchor reads as unconfigured again', () => {",
                  "  const v = pm.response.json();",
                  "  pm.expect(v.source === null || v.source === undefined).to.be.true;",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "17a Register a published trust list as a trust source",
          "request": {
            "method": "PUT",
            "url": "{{tenantTrustDomainApiBaseUrl}}/domains/{{vicalDomainId}}/trust-sources/{{customerTrustListSourceId}}",
            "description": "Registers a trust list you publish yourself (ETSI TS 119 612 structure, signed by your trust-list signing certificate) as a source of the second trust domain. signerAnchorIds names the anchor holding the certificate that signs the list; egressPolicy restricts where the platform may fetch from and how large the artifact may be. If-Match \"*\" registers a source that does not exist yet.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              },
              {
                "key": "If-Match",
                "value": "*",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"{{customerTrustListUrl}}\",\n  \"format\": \"application/xml\",\n  \"schemeIdentity\": \"{{customerTrustListSchemeIdentity}}\",\n  \"signerAnchorIds\": [\n    \"{{vicalAnchorId}}\"\n  ],\n  \"egressPolicy\": {\n    \"allowedHosts\": [\n      \"{{customerTrustListHost}}\"\n    ],\n    \"maxArtifactBytes\": 5242880\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const url = String(pm.variables.get('customerTrustListUrl') || '');",
                  "pm.collectionVariables.set('customerTrustListHost', url.replace(/^https:\\/\\//, '').split('/')[0]);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('trust list source registered', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.be.oneOf([200,201]));",
                  "const source = pm.response.json();",
                  "pm.test('the source belongs to the domain and is a custom TS 119 612 list', () => {",
                  "  pm.expect(source.sourceId, 'source id').to.eql(pm.variables.get('customerTrustListSourceId'));",
                  "  pm.expect(source.domainId, 'domain id').to.eql(pm.collectionVariables.get('vicalDomainId'));",
                  "  pm.expect(source.kind, 'kind').to.eql('ETSI_119612_CUSTOM_LOTL');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "17b Read the trust list source",
          "request": {
            "method": "GET",
            "url": "{{tenantTrustDomainApiBaseUrl}}/domains/{{vicalDomainId}}/trust-sources/{{customerTrustListSourceId}}",
            "description": "Reads the registered source. activeRevision stays empty until a fetched revision has been validated and activated.",
            "header": []
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('trust list source read', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.be.oneOf([200]));",
                  "const source = pm.response.json();",
                  "pm.test('the source reads back as registered', () => {",
                  "  pm.expect(source.sourceId, 'source id').to.eql(pm.variables.get('customerTrustListSourceId'));",
                  "  pm.expect(source.enabled, 'enabled').to.be.a('boolean');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "17c Register a list of trusted entities (LoTE) source",
          "request": {
            "method": "PUT",
            "url": "{{tenantTrustDomainApiBaseUrl}}/domains/{{vicalDomainId}}/lote-sources/{{customerLoteSourceId}}",
            "description": "Registers a published list of trusted entities (TS 119 602 LoTE) as a remote source of the domain. The call creates the first candidate revision of the source and returns that revision; the source itself, with the etag later mutations must present, is read in the next request.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              },
              {
                "key": "If-Match",
                "value": "*",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sourceKind\": \"REMOTE\",\n  \"profile\": \"PUB_EAA_PROVIDER\",\n  \"url\": \"{{customerLoteUrl}}\",\n  \"verificationTrustAnchorIds\": [\n    \"{{vicalAnchorId}}\"\n  ],\n  \"allowedHosts\": [\n    \"{{customerLoteHost}}\"\n  ],\n  \"maxArtifactBytes\": 5242880,\n  \"enabled\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const url = String(pm.variables.get('customerLoteUrl') || '');",
                  "pm.collectionVariables.set('customerLoteHost', url.replace(/^https:\\/\\//, '').split('/')[0]);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('LoTE source registered', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.be.oneOf([200,201]));",
                  "const revision = pm.response.json();",
                  "pm.test('the registration returns the first candidate revision of the source', () => {",
                  "  pm.expect(revision.sourceId, 'source id').to.eql(pm.variables.get('customerLoteSourceId'));",
                  "  pm.expect(revision.domainId, 'domain id').to.eql(pm.collectionVariables.get('vicalDomainId'));",
                  "  pm.expect(revision.profile, 'profile').to.eql('PUB_EAA_PROVIDER');",
                  "  pm.expect(revision.revision, 'revision number').to.eql(1);",
                  "  pm.expect(revision.etag, 'revision etag').to.be.a('string').and.not.empty;",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "17d Read the LoTE source",
          "request": {
            "method": "GET",
            "url": "{{tenantTrustDomainApiBaseUrl}}/domains/{{vicalDomainId}}/lote-sources/{{customerLoteSourceId}}",
            "description": "Reads the LoTE source. Its etag is the If-Match value for the next change to the source (enable, disable, replace); revisions carry their own etags.",
            "header": []
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('LoTE source read', () => pm.expect(pm.response.code, 'body: ' + String(pm.response.text() || '').slice(0, 300)).to.be.oneOf([200]));",
                  "const source = pm.response.json();",
                  "pm.test('the source carries the registered profile and an etag for the next mutation', () => {",
                  "  pm.expect(source.sourceId, 'source id').to.eql(pm.variables.get('customerLoteSourceId'));",
                  "  pm.expect(source.profile, 'profile').to.eql('PUB_EAA_PROVIDER');",
                  "  pm.expect(source.etag, 'source etag').to.be.a('string').and.not.empty;",
                  "});",
                  "pm.collectionVariables.set('customerLoteEtag', source.etag);"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "24 KMS Runtime API",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "description": "The runtime KMS surface a tenant integrates against: discover providers, then generate, read, use and remove a key by provider id and alias. Keys are addressed by the alias the tenant chose, never by a server-issued key id, so the same request works across deployments. Requests run under the tenant service token (folder 04); the tenant KMS API rejects the platform operator token because its audience is the platform, not the tenant KMS.\n\nDocumentation: https://docs.sphereon.com/edk/admin-console/guides/keys-and-did",
      "item": [
        {
          "name": "00 List platform secret offerings",
          "request": {
            "method": "GET",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{platformAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "url": "{{platformUrl}}/api/platform/admin/v1/application/secrets/offerings"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('platform secret offerings returned', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "00 Get platform secret storage",
          "request": {
            "method": "GET",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{platformAccessToken}}",
                  "type": "string"
                }
              ]
            },
            "url": "{{platformUrl}}/api/platform/admin/v1/application/secrets/storage"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('platform secret storage returned', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "00 Get tenant secret options",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{tenantId}}/secrets/options"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant secret options returned', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "00 Get tenant secret assignment",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ],
            "url": "{{platformUrl}}/api/platform/config/v1/tenants/{{tenantId}}/secrets/assignment"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant secret assignment returned', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "01 List runtime providers",
          "request": {
            "method": "GET",
            "url": "{{tenantKmsApiBaseUrl}}/providers",
            "description": "Lists the KMS providers this tenant may address at runtime. The provider id returned here is the one used in every later path segment.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('runtime providers listed', () => pm.response.to.have.status(200));",
                  "const providers = pm.response.json().providers || pm.response.json().data || [];",
                  "pm.expect(providers, 'at least one runtime provider is addressable').to.not.be.empty;",
                  "const preferred = providers.find((p) => (p.id || p.providerId) === \"default\") || providers[0];",
                  "pm.collectionVariables.set(\"kmsRuntimeProviderId\", preferred.id || preferred.providerId);",
                  "// Fixed aliases: the snapshot must be byte-stable across runs, and each run onboards a fresh",
                  "// tenant, so there is nothing to collide with. Requests 07 and 16 remove them again.",
                  "pm.collectionVariables.set(\"kmsRuntimeKeyAlias\", \"customer-runtime-signing-key\");",
                  "pm.collectionVariables.set(\"kmsTenantKeyAlias\", \"customer-tenant-signing-key\");",
                  "pm.collectionVariables.set(\"kmsTenantCertificateAlias\", \"customer-tenant-signing-chain\");"
                ]
              }
            }
          ]
        },
        {
          "name": "01a Import externally supplied public key",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"keyInfo\": {\n    \"key\": {\n      \"kty\": \"EC\",\n      \"crv\": \"P-256\",\n      \"x\": \"HFL67WWh6PYWKOy1mzt9Y2ANs-CWFIyVtouR-Jx_mAM\",\n      \"y\": \"9f_1x7fwUuEbEwxSNTYE3jQF-zForWpKkEMpiUp1MNI\"\n    },\n    \"alias\": \"customer-evaluation-imported-key\",\n    \"providerId\": \"{{kmsRuntimeProviderId}}\",\n    \"signatureAlgorithm\": \"ECDSA_SHA256\",\n    \"keyVisibility\": \"PUBLIC\",\n    \"keyEncoding\": \"JOSE\",\n    \"keyType\": \"EC\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{tenantKmsApiBaseUrl}}/keys/import"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('public key imported', () => pm.response.to.have.status(201));",
                  "const body = pm.response.json();",
                  "pm.test('import response identifies the imported key', () => { pm.expect(body.keyInfo, 'keyInfo').to.be.an('object'); pm.expect(body.keyInfo.alias, 'alias').to.eql('customer-evaluation-imported-key'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "02 List keys by providerId",
          "request": {
            "method": "GET",
            "url": "{{tenantKmsApiBaseUrl}}/providers/{{kmsRuntimeProviderId}}/keys",
            "description": "Lists the keys the tenant holds in one provider.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('keys listed for the provider', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Generate key with providerId and alias",
          "request": {
            "method": "POST",
            "url": "{{tenantKmsApiBaseUrl}}/providers/{{kmsRuntimeProviderId}}/keys",
            "description": "Generates a signing key under a tenant-chosen alias. The alias is the durable handle for every later call; the response also carries the public JWK.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"alias\": \"{{kmsRuntimeKeyAlias}}\",\n  \"use\": \"sig\",\n  \"alg\": \"ECDSA_SHA256\",\n  \"keyOperations\": [\n    \"sign\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('key generated', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const body = pm.response.json();",
                  "const pair = body.keyPair || body;",
                  "pm.expect(pair.alias, 'the response echoes the requested alias').to.eql(pm.collectionVariables.get('kmsRuntimeKeyAlias'));",
                  "// The software provider mints a self-signed certificate for a generated signing key.",
                  "// Requests 11 onwards register that chain, so carry it forward when it is present.",
                  "const x5c = pair.jose && pair.jose.publicJwk && pair.jose.publicJwk.x5c;",
                  "pm.collectionVariables.set(\"kmsRuntimeKeyChain\", Array.isArray(x5c) ? JSON.stringify(x5c) : \"\");"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Get key by providerId and alias",
          "request": {
            "method": "GET",
            "url": "{{tenantKmsApiBaseUrl}}/providers/{{kmsRuntimeProviderId}}/keys/{{kmsRuntimeKeyAlias}}",
            "description": "Reads one key by alias, including its public JWK.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('key read by alias', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Create raw signature with providerId and alias",
          "request": {
            "method": "POST",
            "url": "{{tenantKmsApiBaseUrl}}/signatures/raw/create",
            "description": "Signs an opaque payload with the named key. Raw signing is the primitive behind the higher level JWS and credential signing surfaces.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"keyInfo\": {\n    \"providerId\": \"{{kmsRuntimeProviderId}}\",\n    \"alias\": \"{{kmsRuntimeKeyAlias}}\"\n  },\n  \"input\": \"aGVsbG8=\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('raw signature created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const signature = pm.response.json().signature;",
                  "pm.expect(signature, 'a signature value is returned').to.be.a('string');",
                  "pm.collectionVariables.set(\"kmsRuntimeSignature\", signature);"
                ]
              }
            }
          ]
        },
        {
          "name": "06 Verify raw signature with providerId and alias",
          "request": {
            "method": "POST",
            "url": "{{tenantKmsApiBaseUrl}}/signatures/raw/verify",
            "description": "Verifies a signature produced by the same key, closing the sign/verify loop.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"keyInfo\": {\n    \"providerId\": \"{{kmsRuntimeProviderId}}\",\n    \"alias\": \"{{kmsRuntimeKeyAlias}}\"\n  },\n  \"input\": \"aGVsbG8=\",\n  \"signature\": \"{{kmsRuntimeSignature}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('raw signature verified', () => pm.response.to.have.status(200));",
                  "pm.expect(pm.response.json().isValid, 'the signature verifies against its own key').to.eql(true);"
                ]
              }
            }
          ]
        },
        {
          "name": "07 Delete generated key by providerId and alias",
          "request": {
            "method": "DELETE",
            "url": "{{tenantKmsApiBaseUrl}}/providers/{{kmsRuntimeProviderId}}/keys/{{kmsRuntimeKeyAlias}}",
            "description": "Removes the key created by this walkthrough so the run leaves no residue.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('generated key deleted', () => pm.expect([200, 204]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "08 List keys across every provider",
          "request": {
            "method": "GET",
            "url": "{{tenantKmsApiBaseUrl}}/keys",
            "description": "Lists key metadata across every provider the tenant has, rather than one provider at a time. Each entry names its own providerId. Add ?providerId= to narrow it to a single provider.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant-wide key list returned', () => pm.response.to.have.status(200));",
                  "pm.expect(pm.response.json().keyInfos, 'the response uses the keyInfos envelope').to.be.an('array');"
                ]
              }
            }
          ]
        },
        {
          "name": "09 Generate a key without naming a provider in the path",
          "request": {
            "method": "POST",
            "url": "{{tenantKmsApiBaseUrl}}/keys",
            "description": "Generates a key through the tenant-wide route. providerId travels in the body here instead of the path; omitting it would use the tenant default provider. The response carries the public JWK and, where the provider mints one, its self-signed certificate.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"alias\": \"{{kmsTenantKeyAlias}}\",\n  \"use\": \"sig\",\n  \"alg\": \"ECDSA_SHA256\",\n  \"keyOperations\": [\n    \"sign\"\n  ],\n  \"providerId\": \"{{kmsRuntimeProviderId}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('key generated through the tenant-wide route', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const pair = pm.response.json().keyPair;",
                  "pm.expect(pair.alias, 'the response echoes the requested alias').to.eql(pm.collectionVariables.get('kmsTenantKeyAlias'));",
                  "const x5c = pair.jose && pair.jose.publicJwk && pair.jose.publicJwk.x5c;",
                  "pm.expect(x5c, 'the provider mints a certificate for a generated signing key').to.be.an('array').that.is.not.empty;",
                  "pm.collectionVariables.set(\"kmsTenantKeyChain\", JSON.stringify(x5c));"
                ]
              }
            }
          ]
        },
        {
          "name": "10 Read the key by alias",
          "request": {
            "method": "GET",
            "url": "{{tenantKmsApiBaseUrl}}/keys/{{kmsTenantKeyAlias}}?providerId={{kmsRuntimeProviderId}}",
            "description": "Reads one key by alias or kid across the tenant. providerId is optional and only needed to disambiguate an alias that exists in more than one provider.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('key read by alias', () => pm.response.to.have.status(200));",
                  "pm.expect(pm.response.json().keyInfo.alias).to.eql(pm.collectionVariables.get('kmsTenantKeyAlias'));"
                ]
              }
            }
          ]
        },
        {
          "name": "11 Register the key certificate chain",
          "request": {
            "method": "POST",
            "url": "{{tenantKmsApiBaseUrl}}/certificates/register",
            "description": "Registers public certificate material against the key generated above. stored_public_material carries the chain in the request and needs no provider certificate API, so it works against the software provider. The service checks that the leaf public key matches the linked key before storing the reference.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"providerId\": \"{{kmsRuntimeProviderId}}\",\n  \"alias\": \"{{kmsTenantCertificateAlias}}\",\n  \"kind\": \"key_certificate_chain\",\n  \"source\": \"stored_public_material\",\n  \"linkedKeyAlias\": \"{{kmsTenantKeyAlias}}\",\n  \"certificateChain\": {{kmsTenantKeyChain}}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('certificate reference registered', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const created = pm.response.json();",
                  "const id = created.id || (created.reference && created.reference.id);",
                  "pm.expect(id, 'the response carries the reference id').to.be.a('string');",
                  "pm.collectionVariables.set(\"kmsTenantCertificateReferenceId\", id);"
                ]
              }
            }
          ]
        },
        {
          "name": "12 List certificate references",
          "request": {
            "method": "GET",
            "url": "{{tenantKmsApiBaseUrl}}/certificate-references",
            "description": "Lists the tenant's own certificate references. It never enumerates a provider's certificate inventory, and returns public metadata only. Filter with ?providerId=, ?kind= or ?source=.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('certificate references listed', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "13 Read one certificate reference",
          "request": {
            "method": "GET",
            "url": "{{tenantKmsApiBaseUrl}}/certificate-references/{{kmsTenantCertificateReferenceId}}",
            "description": "Reads the public metadata projection for one reference: provider, alias, kind, source, control mode, the linked key alias and kid, and the public fingerprints. No certificate bytes, no provider credentials.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('certificate reference read', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "14 Read the registered chain",
          "request": {
            "method": "GET",
            "url": "{{tenantKmsApiBaseUrl}}/certificate-chains/{{kmsTenantCertificateAlias}}?providerId={{kmsRuntimeProviderId}}",
            "description": "Reads the stored chain back by alias. The certificates array is ordered leaf to root, so the first entry is the one whose public key must match the linked key.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('registered chain read', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "15 Delete the certificate reference",
          "request": {
            "method": "DELETE",
            "url": "{{tenantKmsApiBaseUrl}}/certificate-chains/{{kmsTenantCertificateAlias}}?providerId={{kmsRuntimeProviderId}}",
            "description": "Removes the EDK reference only. For an externally managed reference this never touches the provider certificate; here it removes material this walkthrough itself stored.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('certificate reference deleted', () => pm.expect([200, 204]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "16 Delete the tenant-wide key",
          "request": {
            "method": "DELETE",
            "url": "{{tenantKmsApiBaseUrl}}/keys/{{kmsTenantKeyAlias}}?providerId={{kmsRuntimeProviderId}}",
            "description": "Removes the key created by request 09 so the run leaves no residue.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant-wide key deleted', () => pm.expect([200, 204]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "25 Developer Console Settings",
      "auth": {
        "type": "bearer",
        "bearer": [
          {
            "key": "token",
            "value": "{{tenantAccessToken}}",
            "type": "string"
          }
        ]
      },
      "description": "The tenant-scoped Developer Console policy: which console surfaces a tenant exposes and where they are published.",
      "item": [
        {
          "name": "01 Read current console policy",
          "request": {
            "method": "GET",
            "url": "{{tenantPlatformConfigApiBaseUrl}}/tenants/{{tenantId}}/developer-console",
            "description": "Reads the effective Developer Console policy for this tenant.",
            "header": [
              {
                "key": "Host",
                "value": "{{tenantPublicAuthority}}",
                "type": "text"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('developer console policy read', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    }
  ],
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Every URL in this collection follows from two values: baseDomain and tenantSubdomain. The platform is",
          "// platform.<baseDomain>, a tenant is <tenantSubdomain>.<baseDomain>, and every API base is a path under",
          "// one of those two origins. This script recomputes all of them before every request and keeps the",
          "// results in collection scope only, so the environment holds nothing but what you typed and a",
          "// change to baseDomain or tenantSubdomain takes effect on the next request.",
          "const read = (key, fallback) => {",
          "  const value = String(pm.environment.get(key) || pm.collectionVariables.get(key) || fallback || '').trim();",
          "  return value;",
          "};",
          "const derive = (key, value) => {",
          "  if (value !== undefined && value !== null && String(value).trim()) {",
          "    pm.collectionVariables.set(key, String(value).trim().replace(/\\/+$/, ''));",
          "  }",
          "};",
          "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 didAuthority = parts.shift();",
          "  if (!didAuthority) return '';",
          "  const gatewayMatch = String(gatewayUrl || '').match(/^([a-z][a-z0-9+.-]*):\\/\\/([^/?#]+)/i);",
          "  const gatewayAuthority = gatewayMatch ? gatewayMatch[2] : '';",
          "  const useGatewayAuthority = gatewayAuthority.replace(/:\\d+$/, '') === didAuthority.replace(/:\\d+$/, '') || gatewayAuthority === didAuthority;",
          "  const authority = useGatewayAuthority ? gatewayAuthority : didAuthority;",
          "  const documentPath = parts.length ? '/' + parts.map((part) => encodeURIComponent(part)).join('/') + '/did.json' : '/.well-known/did.json';",
          "  return 'https://' + authority + documentPath;",
          "};",
          "let baseDomain = read('baseDomain').replace(/^https?:\\/\\//i, '').replace(/\\/+$/, '');",
          "let publicPort = '';",
          "const basePort = baseDomain.match(/:(\\d+)$/);",
          "if (basePort) {",
          "  publicPort = ':' + basePort[1];",
          "  baseDomain = baseDomain.slice(0, -basePort[0].length);",
          "}",
          "const tenantSubdomain = read('tenantSubdomain', 'acme').toLowerCase();",
          "const tenantName = read('tenantName', 'Acme Corporation');",
          "derive('tenantSubdomain', tenantSubdomain);",
          "derive('tenantName', tenantName);",
          "if (baseDomain) {",
          "  const platformUrl = 'https://platform.' + baseDomain + publicPort;",
          "  const tenantHost = tenantSubdomain + '.' + baseDomain;",
          "  const tenantPublicAuthority = tenantHost + publicPort;",
          "  const tenantGatewayUrl = 'https://' + tenantPublicAuthority;",
          "  derive('baseDomain', baseDomain);",
          "  derive('publicPort', publicPort);",
          "  derive('platformUrl', platformUrl);",
          "  derive('adminConsoleUrl', platformUrl + '/admin-console');",
          "  derive('operatorRedirectUri', platformUrl + '/admin-console/callback');",
          "  derive('tenantHost', tenantHost);",
          "  derive('tenantPublicAuthority', tenantPublicAuthority);",
          "  derive('tenantGatewayUrl', tenantGatewayUrl);",
          "  derive('tenantIssuerOrigin', tenantGatewayUrl);",
          "  derive('tenantVerifierOrigin', tenantGatewayUrl);",
          "  derive('tenantAuthorizationServerOrigin', tenantGatewayUrl);",
          "  derive('tenantPlatformConfigApiBaseUrl', tenantGatewayUrl + '/api/platform/config/v1');",
          "  derive('tenantDidApiBaseUrl', tenantGatewayUrl + '/api/did/v1');",
          "  derive('tenantCredentialDesignApiBaseUrl', tenantGatewayUrl + '/api/credential-design/v1');",
          "  derive('tenantStatusListApiBaseUrl', tenantGatewayUrl + '/api/statuslist/v1');",
          "  derive('tenantIssuerApiBaseUrl', tenantGatewayUrl + '/api/oid4vci/v1');",
          "  derive('tenantDcqlApiBaseUrl', tenantGatewayUrl + '/api/dcql/v1');",
          "  derive('tenantVerifierBackendBaseUrl', tenantGatewayUrl + '/oid4vp/backend');",
          "  derive('tenantTrustDomainApiBaseUrl', tenantGatewayUrl + '/api/trust-domain/v1');",
          "  derive('tenantKmsApiBaseUrl', tenantGatewayUrl + '/api/kms/v1');",
          "  const did = 'did:web:' + tenantPublicAuthority.replace(/:/g, '%3A');",
          "  derive('did', did);",
          "  derive('didEncoded', encodeURIComponent(did));",
          "  derive('didJsonUrl', didWebDocumentUrl(did, tenantGatewayUrl));",
          "}"
        ]
      }
    }
  ],
  "auth": {
    "type": "noauth"
  }
}
