{
  "info": {
    "_postman_id": "4d75fa20-e95f-4b17-93a7-f47ac84be76e",
    "name": "DS2 Data Sharing - Vast and Stream",
    "description": "Postman collection for DS2 data sharing demos: Vast Data Transfer and Stream Data Extension.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    {
      "key": "vastProviderBaseUrl",
      "value": "https://localhost/vast-provider"
    },
    {
      "key": "vastConsumerBaseUrl",
      "value": "https://localhost/vast-consumer"
    },
    {
      "key": "streamProviderBaseUrl",
      "value": "http://localhost:18092"
    },
    {
      "key": "streamConsumerBaseUrl",
      "value": "http://localhost:18093"
    },
    {
      "key": "sourceTopic",
      "value": "stream.source"
    },
    {
      "key": "sinkTopic",
      "value": "stream.sink"
    },
    {
      "key": "providerManagementUrl",
      "value": "http://localhost:18081/api/management"
    },
    {
      "key": "consumerManagementUrl",
      "value": "http://localhost:19081/api/management"
    },
    {
      "key": "providerDspUrl",
      "value": "http://edc-provider:8082/api/protocol/2025-1"
    },
    {
      "key": "streamDataplaneBaseUrl",
      "value": "http://localhost:18090"
    },
    {
      "key": "kafkaBrokers",
      "value": "stream-redpanda:9092"
    },
    {
      "key": "assetId",
      "value": "stream-sensor-events"
    },
    {
      "key": "offerId",
      "value": "REPLACE_FROM_REQUEST_4"
    },
    {
      "key": "negotiationId",
      "value": "REPLACE_FROM_REQUEST_5"
    },
    {
      "key": "agreementId",
      "value": "REPLACE_FROM_REQUEST_6"
    },
    {
      "key": "transferProcessId",
      "value": "REPLACE_FROM_REQUEST_7"
    },
    {
      "key": "vastAssetId",
      "value": "vast-demo-file"
    },
    {
      "key": "vastSourceName",
      "value": "demo.bin"
    },
    {
      "key": "vastDestName",
      "value": "demo-output.bin"
    },
    {
      "key": "vastFlowId",
      "value": "REPLACE_FROM_REQUEST_9"
    },
    {
      "key": "auditBridgeBaseUrl",
      "value": "http://localhost:18085"
    },
    {
      "key": "vastUploadToken",
      "value": "ds2-vast-upload-secret",
      "type": "string"
    },
    {
      "key": "vastUploadId",
      "value": "",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "Vast Data Extension (EDC-governed)",
      "description": "A large file moving under an Eclipse EDC contract. Run 1-8 in order; everything else is observation. The transfer is driven entirely from the CONSUMER's Management API — nothing here pokes a data plane to make bytes move.\n\nTwo things that catch people out:\n  * The consumer and the provider mint DIFFERENT ids for the same transfer, for both the transfer process and the agreement. To find the provider-side flow, filter its /transfers by the destinationPath you asked for (request 9).\n  * sourcePath and destinationPath are resolved inside VAST_SOURCE_ROOT / VAST_OUTPUT_ROOT and may not escape them.\n\nScale: this module is designed for 100 GB and has been tested at 10 GB. See the module README.",
      "item": [
        {
          "name": "1. Seed policy definition (always-true)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{providerManagementUrl}}/v3/policydefinitions"
            },
            "description": "Always-true by design (V8). Governance is proven by request 13, not by policy content.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\",\n    \"odrl\": \"http://www.w3.org/ns/odrl/2/\"\n  },\n  \"@id\": \"always-true\",\n  \"policy\": {\n    \"@type\": \"odrl:Set\",\n    \"odrl:permission\": [],\n    \"odrl:prohibition\": [],\n    \"odrl:obligation\": []\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "2. Seed asset (VastData source)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{providerManagementUrl}}/v3/assets"
            },
            "description": "The source DataAddress. `sourcePath` is resolved inside the provider data plane's VAST_SOURCE_ROOT — see DPS_CONTRACT.md §7. Note the Management API form uses a bare `type`, while the DSP form of the same address uses `endpointType`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\"\n  },\n  \"@id\": \"{{vastAssetId}}\",\n  \"properties\": {\n    \"name\": \"DS2 vast demo file\",\n    \"contenttype\": \"application/octet-stream\"\n  },\n  \"dataAddress\": {\n    \"@type\": \"DataAddress\",\n    \"type\": \"VastData\",\n    \"sourcePath\": \"/{{vastSourceName}}\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "3. Seed contract definition",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{providerManagementUrl}}/v3/contractdefinitions"
            },
            "description": "Binds the policy to every asset.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\"\n  },\n  \"@id\": \"vast-contract\",\n  \"accessPolicyId\": \"always-true\",\n  \"contractPolicyId\": \"always-true\",\n  \"assetsSelector\": []\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "4. Consumer requests provider catalog (DSP)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/catalog/request"
            },
            "description": "The counterPartyAddress MUST carry the /2025-1 version segment, and must be resolvable from inside the consumer container. A dataset shows `distribution: []` until a data plane advertising the transfer type is registered — expected, not a defect.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\"\n  },\n  \"@type\": \"CatalogRequest\",\n  \"counterPartyAddress\": \"{{providerDspUrl}}\",\n  \"counterPartyId\": \"provider\",\n  \"protocol\": \"dataspace-protocol-http:2025-1\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "5. Start contract negotiation",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/contractnegotiations"
            },
            "description": "Take the offer @id from request 4 and put it in `policy.@id`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\",\n    \"odrl\": \"http://www.w3.org/ns/odrl/2/\"\n  },\n  \"@type\": \"ContractRequest\",\n  \"counterPartyAddress\": \"{{providerDspUrl}}\",\n  \"counterPartyId\": \"provider\",\n  \"protocol\": \"dataspace-protocol-http:2025-1\",\n  \"policy\": {\n    \"@context\": \"http://www.w3.org/ns/odrl.jsonld\",\n    \"@id\": \"{{offerId}}\",\n    \"@type\": \"odrl:Offer\",\n    \"assigner\": \"provider\",\n    \"target\": \"{{vastAssetId}}\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "6. Poll negotiation until FINALIZED",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/contractnegotiations/{{negotiationId}}"
            },
            "description": "Take `contractAgreementId` when state is FINALIZED. Note it will NOT equal the agreement id the provider data plane reports."
          },
          "response": []
        },
        {
          "name": "7. Start transfer (VastData-PUSH)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/transferprocesses"
            },
            "description": "The dataDestination carries both ends plus the tuning knobs — DPS gives a flow exactly one DataAddress and a PUSH provider needs both. Omit partSizeBytes/windowSize/codecPolicy to take the data plane's environment defaults.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\"\n  },\n  \"@type\": \"TransferRequest\",\n  \"counterPartyAddress\": \"{{providerDspUrl}}\",\n  \"contractId\": \"{{agreementId}}\",\n  \"assetId\": \"{{vastAssetId}}\",\n  \"protocol\": \"dataspace-protocol-http:2025-1\",\n  \"transferType\": \"VastData-PUSH\",\n  \"dataDestination\": {\n    \"@type\": \"DataAddress\",\n    \"type\": \"VastData\",\n    \"endpoint\": \"http://vast-consumer-dataplane:8080\",\n    \"sourcePath\": \"/{{vastSourceName}}\",\n    \"destinationPath\": \"/{{vastDestName}}\",\n    \"partSizeBytes\": \"16777216\",\n    \"windowSize\": \"4\",\n    \"codecPolicy\": \"adaptive\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "8. Poll transfer until STARTED",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/transferprocesses/{{transferProcessId}}"
            },
            "description": "STARTED means the provider data plane accepted the flow. The bytes move in the background — a 10 GB transfer takes minutes and DPS models that deliberately."
          },
          "response": []
        },
        {
          "name": "9. Find the PROVIDER-side flow by destination path",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{vastProviderBaseUrl}}/transfers?destinationPath=/{{vastDestName}}"
            },
            "description": "The consumer's transfer process id is NOT the provider's, and neither is the agreement id — each participant mints its own. The destinationPath you asked for is what crosses the boundary, so it is the correlation key. Take `transferId` from the response for requests 10-12."
          },
          "response": []
        },
        {
          "name": "10. Provider: live progress",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{vastProviderBaseUrl}}/transfers/{{vastFlowId}}"
            },
            "description": "parts done/total, in-flight window occupancy, bytes, throughput, ETA, codec decisions, re-fetch and retry counters (V-FR-14)."
          },
          "response": []
        },
        {
          "name": "11. Consumer: resume state",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{vastConsumerBaseUrl}}/ingest/{{vastFlowId}}/state"
            },
            "description": "The set of parts whose bytes are fsynced AND verified. This is the entire resume state — kill the consumer data plane and re-run this to see it survive."
          },
          "response": []
        },
        {
          "name": "12. Suspend transfer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/transferprocesses/{{transferProcessId}}/suspend"
            },
            "description": "Stops the provider refilling its window. Parts already in flight are allowed to land, so expect up to `windowSize` more to complete.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\"\n  },\n  \"reason\": \"postman suspend\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "13. Resume transfer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/transferprocesses/{{transferProcessId}}/resume"
            },
            "description": "Continues from the same verified-part set. Check request 11 before and after: the count must go up, never back to zero.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\"\n  },\n  \"reason\": \"postman resume\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "14. Terminate transfer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/transferprocesses/{{transferProcessId}}/terminate"
            },
            "description": "Also discards the provider's flow descriptor, so a terminated transfer is not resurrected by a provider restart.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\"\n  },\n  \"reason\": \"postman terminate\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "15. GOVERNANCE PROOF: transfer with no agreement",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/transferprocesses"
            },
            "description": "V-FR-6. Must be refused, or accepted and then terminated without moving a byte. This is the request that distinguishes an EDC data plane from an HTTP file copier.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\"\n  },\n  \"@type\": \"TransferRequest\",\n  \"counterPartyAddress\": \"{{providerDspUrl}}\",\n  \"contractId\": \"agreement-that-was-never-negotiated\",\n  \"assetId\": \"{{vastAssetId}}\",\n  \"protocol\": \"dataspace-protocol-http:2025-1\",\n  \"transferType\": \"VastData-PUSH\",\n  \"dataDestination\": {\n    \"@type\": \"DataAddress\",\n    \"type\": \"VastData\",\n    \"endpoint\": \"http://vast-consumer-dataplane:8080\",\n    \"sourcePath\": \"/{{vastSourceName}}\",\n    \"destinationPath\": \"/stolen.bin\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "16. Provider data plane: readiness",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{vastProviderBaseUrl}}/ready"
            },
            "description": "503 until registered with every configured control plane (H-2)."
          },
          "response": []
        },
        {
          "name": "17. Consumer data plane: readiness",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{vastConsumerBaseUrl}}/ready"
            },
            "description": "503 until registered."
          },
          "response": []
        },
        {
          "name": "18. Provider data plane: configuration",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{vastProviderBaseUrl}}/config"
            },
            "description": "Resolved roots, tuning defaults and whether the source root is readable."
          },
          "response": []
        },
        {
          "name": "19. Provider data plane: audit trail",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{vastProviderBaseUrl}}/audit"
            },
            "description": "Local mirror plus delivery stats. Control-plane events are NOT here — they go to the module-neutral edc-audit-bridge (request 21)."
          },
          "response": []
        },
        {
          "name": "20. Consumer data plane: audit trail",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{vastConsumerBaseUrl}}/audit"
            },
            "description": "Ingest lifecycle: transfers opened, parts rejected, transfers completed."
          },
          "response": []
        },
        {
          "name": "21. EDC audit bridge: control-plane events",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{auditBridgeBaseUrl}}/audit"
            },
            "description": "Contract negotiation and transfer-process transitions from BOTH participants, logged as componentName 'edc control plane'. Deliberately not on any data plane: the control planes are shared by every transfer type in the repo."
          },
          "response": []
        },
        {
          "name": "22. Provider data plane: memory",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{vastProviderBaseUrl}}/metrics"
            },
            "description": "heapUsed is the live set and is what a memory claim should be judged on; RSS includes freed pages the allocator has not returned and is what a container limit enforces. Measured at 10 GB: ~11 MiB live, 360 MiB RSS on the provider."
          },
          "response": []
        },
        {
          "name": "U1. List source files (public)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{vastProviderBaseUrl}}/source/files",
              "host": [
                "{{vastProviderBaseUrl}}"
              ],
              "path": [
                "source",
                "files"
              ]
            },
            "description": "Source files a transfer can name, plus any half-finished uploads. No token needed."
          }
        },
        {
          "name": "U2. Begin (or rejoin) an upload",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{vastUploadToken}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              }
            ],
            "url": {
              "raw": "{{vastProviderBaseUrl}}/source/uploads",
              "host": [
                "{{vastProviderBaseUrl}}"
              ],
              "path": [
                "source",
                "uploads"
              ]
            },
            "description": "Set sizeBytes to the file's exact size. The response id is derived from (name, size), so re-sending this for an interrupted upload rejoins it and lists the chunks already banked. Save `id` into {{vastUploadId}}.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"{{vastSourceName}}\",\n  \"sizeBytes\": 0\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "U3. Put one chunk",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{vastUploadToken}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/octet-stream",
                "type": "text"
              }
            ],
            "url": {
              "raw": "{{vastProviderBaseUrl}}/source/uploads/{{vastUploadId}}/chunks/0",
              "host": [
                "{{vastProviderBaseUrl}}"
              ],
              "path": [
                "source",
                "uploads",
                "{{vastUploadId}}",
                "chunks",
                "0"
              ]
            },
            "description": "Body: the raw bytes of chunk 0 (bytes 0..chunkBytes-1). Repeat with /chunks/1, /chunks/2, … For a file no larger than chunkBytes (8 MiB by default) the whole file is chunk 0.",
            "body": {
              "mode": "file",
              "file": {
                "src": ""
              }
            }
          }
        },
        {
          "name": "U4. Complete",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{vastUploadToken}}",
                "type": "text"
              },
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              }
            ],
            "url": {
              "raw": "{{vastProviderBaseUrl}}/source/uploads/{{vastUploadId}}/complete",
              "host": [
                "{{vastProviderBaseUrl}}"
              ],
              "path": [
                "source",
                "uploads",
                "{{vastUploadId}}",
                "complete"
              ]
            },
            "description": "Fails with the list of missing chunks if any are absent. If sha256 is supplied it is verified server-side before the file is renamed into the source root.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sha256\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "U5. Inspect an upload",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{vastUploadToken}}",
                "type": "text"
              }
            ],
            "url": {
              "raw": "{{vastProviderBaseUrl}}/source/uploads/{{vastUploadId}}",
              "host": [
                "{{vastProviderBaseUrl}}"
              ],
              "path": [
                "source",
                "uploads",
                "{{vastUploadId}}"
              ]
            },
            "description": "Current chunk state of one upload."
          }
        },
        {
          "name": "U6. Abandon an upload",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{vastUploadToken}}",
                "type": "text"
              }
            ],
            "url": {
              "raw": "{{vastProviderBaseUrl}}/source/uploads/{{vastUploadId}}",
              "host": [
                "{{vastProviderBaseUrl}}"
              ],
              "path": [
                "source",
                "uploads",
                "{{vastUploadId}}"
              ]
            },
            "description": "Removes the part file and its journal."
          }
        },
        {
          "name": "U7. Delete a source file",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{vastUploadToken}}",
                "type": "text"
              }
            ],
            "url": {
              "raw": "{{vastProviderBaseUrl}}/source/files/{{vastSourceName}}",
              "host": [
                "{{vastProviderBaseUrl}}"
              ],
              "path": [
                "source",
                "files",
                "{{vastSourceName}}"
              ]
            },
            "description": "Removes a completed source file from the provider."
          }
        }
      ]
    },
    {
      "name": "Stream Data Extension (EDC-governed)",
      "description": "Streaming transfer under Eclipse EDC v0.18.0 governance.\n\nRun in order: 1-3 seed the provider, 4 discovers the offer, 5-6 negotiate a contract, 7-8 start the transfer, 9-11 control it, 12+ observe.\n\nSet `offerId` from step 4, `agreementId` from step 6, and `transferProcessId` from step 7.\n\nThe old /transfers/* data plane API is gone: that was a simulation with no contract behind it. The data plane is now driven by the control plane over Data Plane Signaling and is not called directly.",
      "item": [
        {
          "name": "1. Seed policy definition (always-true)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\",\n    \"odrl\": \"http://www.w3.org/ns/odrl/2/\"\n  },\n  \"@id\": \"always-true\",\n  \"policy\": {\n    \"@type\": \"odrl:Set\",\n    \"odrl:permission\": [],\n    \"odrl:prohibition\": [],\n    \"odrl:obligation\": []\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{providerManagementUrl}}/v3/policydefinitions"
            },
            "description": "Always-true by design (D6). Governance is proven by step 13, not by policy evaluation."
          },
          "response": []
        },
        {
          "name": "2. Seed asset (Kafka source)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\"\n  },\n  \"@id\": \"{{assetId}}\",\n  \"properties\": {\n    \"name\": \"DS2 synthetic sensor event stream\",\n    \"contenttype\": \"application/json\"\n  },\n  \"dataAddress\": {\n    \"@type\": \"DataAddress\",\n    \"type\": \"Kafka\",\n    \"bootstrapServers\": \"{{kafkaBrokers}}\",\n    \"topic\": \"{{sourceTopic}}\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{providerManagementUrl}}/v3/assets"
            }
          },
          "response": []
        },
        {
          "name": "3. Seed contract definition",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\"\n  },\n  \"@id\": \"stream-contract\",\n  \"accessPolicyId\": \"always-true\",\n  \"contractPolicyId\": \"always-true\",\n  \"assetsSelector\": []\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{providerManagementUrl}}/v3/contractdefinitions"
            }
          },
          "response": []
        },
        {
          "name": "4. Consumer requests provider catalog (DSP)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\"\n  },\n  \"@type\": \"CatalogRequest\",\n  \"counterPartyAddress\": \"{{providerDspUrl}}\",\n  \"counterPartyId\": \"provider\",\n  \"protocol\": \"dataspace-protocol-http:2025-1\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/catalog/request"
            },
            "description": "Copy dataset[0].hasPolicy[0][\"@id\"] into the `offerId` variable. The distribution should advertise format Kafka-PUSH — it only appears once a data plane has registered."
          },
          "response": []
        },
        {
          "name": "5. Start contract negotiation",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\",\n    \"odrl\": \"http://www.w3.org/ns/odrl/2/\"\n  },\n  \"@type\": \"ContractRequest\",\n  \"counterPartyAddress\": \"{{providerDspUrl}}\",\n  \"counterPartyId\": \"provider\",\n  \"protocol\": \"dataspace-protocol-http:2025-1\",\n  \"policy\": {\n    \"@context\": \"http://www.w3.org/ns/odrl.jsonld\",\n    \"@id\": \"{{offerId}}\",\n    \"@type\": \"odrl:Offer\",\n    \"assigner\": \"provider\",\n    \"target\": \"{{assetId}}\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/contractnegotiations"
            }
          },
          "response": []
        },
        {
          "name": "6. Poll negotiation until FINALIZED",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/contractnegotiations/{{negotiationId}}"
            },
            "description": "INITIAL -> REQUESTED -> FINALIZED. Copy contractAgreementId into `agreementId`."
          },
          "response": []
        },
        {
          "name": "7. Start transfer (Kafka-PUSH)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\"\n  },\n  \"@type\": \"TransferRequest\",\n  \"counterPartyAddress\": \"{{providerDspUrl}}\",\n  \"contractId\": \"{{agreementId}}\",\n  \"assetId\": \"{{assetId}}\",\n  \"protocol\": \"dataspace-protocol-http:2025-1\",\n  \"transferType\": \"Kafka-PUSH\",\n  \"dataDestination\": {\n    \"@type\": \"DataAddress\",\n    \"type\": \"Kafka\",\n    \"topic\": \"{{sinkTopic}}\",\n    \"sourceTopic\": \"{{sourceTopic}}\",\n    \"bootstrapServers\": \"{{kafkaBrokers}}\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/transferprocesses"
            },
            "description": "Copy the returned @id into `transferProcessId`."
          },
          "response": []
        },
        {
          "name": "8. Poll transfer until STARTED",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/transferprocesses/{{transferProcessId}}"
            }
          },
          "response": []
        },
        {
          "name": "9. Suspend transfer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\"\n  },\n  \"reason\": \"manual suspend\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/transferprocesses/{{transferProcessId}}/suspend"
            },
            "description": "Records stop arriving. Nothing is lost — offsets are only committed after delivery."
          },
          "response": []
        },
        {
          "name": "10. Resume transfer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/transferprocesses/{{transferProcessId}}/resume"
            },
            "description": "Consumption continues from the last committed offset, with no gap."
          },
          "response": []
        },
        {
          "name": "11. Terminate transfer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\"\n  },\n  \"reason\": \"manual terminate\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/transferprocesses/{{transferProcessId}}/terminate"
            }
          },
          "response": []
        },
        {
          "name": "12. Data plane: flows and pump stats",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{streamDataplaneBaseUrl}}/dataflows"
            }
          },
          "response": []
        },
        {
          "name": "13. GOVERNANCE PROOF: transfer with no agreement",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"@context\": {\n    \"@vocab\": \"https://w3id.org/edc/v0.0.1/ns/\"\n  },\n  \"@type\": \"TransferRequest\",\n  \"counterPartyAddress\": \"{{providerDspUrl}}\",\n  \"contractId\": \"fabricated-agreement\",\n  \"assetId\": \"{{assetId}}\",\n  \"protocol\": \"dataspace-protocol-http:2025-1\",\n  \"transferType\": \"Kafka-PUSH\",\n  \"dataDestination\": {\n    \"@type\": \"DataAddress\",\n    \"type\": \"Kafka\",\n    \"topic\": \"{{sinkTopic}}\",\n    \"sourceTopic\": \"{{sourceTopic}}\",\n    \"bootstrapServers\": \"{{kafkaBrokers}}\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{consumerManagementUrl}}/v3/transferprocesses"
            },
            "description": "MUST be refused. This is FR-8 — the test that separates a governed flow from a simulation."
          },
          "response": []
        },
        {
          "name": "14. Data plane: audit trail",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{streamDataplaneBaseUrl}}/audit"
            }
          },
          "response": []
        },
        {
          "name": "15. Data plane: readiness",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{streamDataplaneBaseUrl}}/ready"
            }
          },
          "response": []
        },
        {
          "name": "16. Source app stats",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{streamProviderBaseUrl}}/stats"
            }
          },
          "response": []
        },
        {
          "name": "17. Source app: change rate",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"intervalMs\": 1000\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{streamProviderBaseUrl}}/rate"
            },
            "description": "Slow the stream down to make suspend visible."
          },
          "response": []
        },
        {
          "name": "18. Sink app stats",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{streamConsumerBaseUrl}}/stats"
            }
          },
          "response": []
        },
        {
          "name": "19. Sink app: recent events",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{streamConsumerBaseUrl}}/events"
            }
          },
          "response": []
        },
        {
          "name": "20. Sink app: gap analysis (no-loss evidence)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{streamConsumerBaseUrl}}/coverage"
            },
            "description": "missingCount should stay 0 across a suspend/resume cycle."
          },
          "response": []
        }
      ]
    }
  ]
}
