diff --git a/Changelog.md b/Changelog.md
index 8b2cbccbab074be1a7047c2eebf63a554b04d895..a43c10d48d18746ec55c814297e676a300e9e7cf 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,6 +1,10 @@
 # Changelog
 
 All notable changes to this project will be documented in this file.
+
+## [1.0] - 2024-03-28
+- PHASE 1 initial release
+
 ## [0.9] - 2024-03-20
 - `migrate_iptrunk` workflow includes Ansible trunk checks.
 - `create_iptrunk` and `migrate_iptrunk` now update IPAM / DNS.
diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst
index 432f73de9e8cdd7f7fd38c5e99f9b434fb1fd82c..4844b8049b2fff9c98561ac6e9f56b92a63ef4ae 100644
--- a/docs/source/glossary.rst
+++ b/docs/source/glossary.rst
@@ -3,6 +3,9 @@ Glossary of terms
 
 .. glossary::
 
+  AAI
+    Authentication and Authorisation Infrastructure
+
   API
     Application Programming Interface
 
@@ -23,9 +26,15 @@ Glossary of terms
   CRUD
     Create, Read, Update, Delete
 
+  DNS
+    Domain Name System
+
   FQDN
     Fully Quantified Domain Name
 
+  GAP
+    The GÉANT Automation Platform
+
   GSO
     GÉANT Service Orchestrator
 
@@ -42,6 +51,9 @@ Glossary of terms
   ISO
     International Organisation for Standardisation
 
+  JSON
+    JavaScript Object Notation
+
   LAG
     Link Aggregation: a bundle of multiple network connections.
 
@@ -69,6 +81,3 @@ Glossary of terms
 
   WFO
     `Workflow Orchestrator <https://workfloworchestrator.org/>`_
-
-  AAI
-    Authentication and Authorisation Infrastructure
diff --git a/docs/source/module/workflows/router/modify_connection_strategy.rst b/docs/source/module/workflows/router/modify_connection_strategy.rst
new file mode 100644
index 0000000000000000000000000000000000000000..b60db9b2fcee862a351408d2d66a693c3fc61024
--- /dev/null
+++ b/docs/source/module/workflows/router/modify_connection_strategy.rst
@@ -0,0 +1,6 @@
+``gso.workflows.router.modify_connection_strategy``
+=========================================
+
+.. automodule:: gso.workflows.router.modify_connection_strategy
+   :members:
+   :show-inheritance:
diff --git a/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt b/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt
index 2b2d2a4f8b71a93a3dbce06687222161e1d1db01..af07d476da370b4fda5d5f2902f784731ae6d5d4 100644
--- a/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt
+++ b/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt
@@ -1,7 +1,12 @@
 GÉANT Automation Platform
+G[ÉE]ANT
 [GSO|gso]
+(GSO|gso)
+N(okia|OKIA)
+IMS
 Vereniging
-[[T|t]erminate|TERMINATE]
+[T|t]erminate
+TERMINATED?
 WFO
 Ansible
 [Dd]eprovision
@@ -9,12 +14,15 @@ API
 DNS
 dry_run
 Dark_fiber
-[A|a]ddress
-[I|i]ptrunk
-[A|a]llocate
+[Aa]ddress
+[Ii]ptrunk
+[Aa]llocate
 PHASE 1
 [Mm]odify
+FQDN
 AAI
-[M|m]iddleware
+[Mm]iddleware
 TWAMP
 Pydantic
+UUID
+SNMP
diff --git a/gso/api/v1/subscriptions.py b/gso/api/v1/subscriptions.py
index 9cc6075ac8bb4858e0354afb691b645a0bdcc1dd..bf4e96bfb6c0b2932d183d1c2707f2a18bf5a608 100644
--- a/gso/api/v1/subscriptions.py
+++ b/gso/api/v1/subscriptions.py
@@ -2,14 +2,16 @@
 
 from typing import Any
 
-from fastapi import Depends, status
+from fastapi import Depends, Response, status
 from fastapi.routing import APIRouter
 from orchestrator.domain import SubscriptionModel
 from orchestrator.schemas import SubscriptionDomainModelSchema
 from orchestrator.services.subscriptions import build_extended_domain_model
+from orchestrator.types import SubscriptionLifecycle
 
 from gso.auth.api_key_auth import get_api_key
-from gso.services.subscriptions import get_active_router_subscriptions
+from gso.products import ProductType
+from gso.services.subscriptions import get_router_subscriptions, get_subscriptions
 
 router = APIRouter(
     prefix="/subscriptions",
@@ -24,11 +26,44 @@ router = APIRouter(
     response_model=list[SubscriptionDomainModelSchema],
 )
 def subscription_routers() -> list[dict[str, Any]]:
-    """Retrieve all active router subscriptions."""
+    """Retrieve all active or provisioning router subscriptions."""
     subscriptions = []
-    for r in get_active_router_subscriptions():
+    routers = get_router_subscriptions(lifecycles=[SubscriptionLifecycle.ACTIVE, SubscriptionLifecycle.PROVISIONING])
+    for r in routers:
         subscription = SubscriptionModel.from_subscription(r["subscription_id"])
         extended_model = build_extended_domain_model(subscription)
         subscriptions.append(extended_model)
 
     return subscriptions
+
+
+@router.get(
+    "/dashboard_devices",
+    status_code=status.HTTP_200_OK,
+    response_class=Response,
+    responses={
+        200: {
+            "content": {"text/plain": {}},
+            "description": "Return a flat file of FQDNs.",
+        }
+    },
+)
+def subscription_dashboard_devices() -> Response:
+    """Retrieve FQDN for all dashboard devices that are monitored."""
+    fqdns = []
+    dashboard_devices = get_subscriptions(
+        product_types=[ProductType.ROUTER, ProductType.SUPER_POP_SWITCH, ProductType.OFFICE_ROUTER],
+        lifecycles=[SubscriptionLifecycle.ACTIVE, SubscriptionLifecycle.PROVISIONING],
+    )
+    for device in dashboard_devices:
+        subscription = SubscriptionModel.from_subscription(device["subscription_id"])
+        extended_model = build_extended_domain_model(subscription)
+        if extended_model["product"]["product_type"] == ProductType.ROUTER:
+            fqdns.append(extended_model["router"]["router_fqdn"])
+        elif extended_model["product"]["product_type"] == ProductType.SUPER_POP_SWITCH:
+            fqdns.append(extended_model["super_pop_switch"]["super_pop_switch_fqdn"])
+        elif extended_model["product"]["product_type"] == ProductType.OFFICE_ROUTER:
+            fqdns.append(extended_model["office_router"]["office_router_fqdn"])
+
+    fqdn_flat_file = "\n".join(fqdns)
+    return Response(content=fqdn_flat_file, media_type="text/plain")
diff --git a/gso/db/models.py b/gso/db/models.py
index 350aa9072198c5e4adfb903f213aa681f19aca81..02d8c59ce72e6e1ecb47bab5c513853a9f59551b 100644
--- a/gso/db/models.py
+++ b/gso/db/models.py
@@ -33,18 +33,19 @@ class PartnerTable(BaseModel):
     __tablename__ = "partners"
 
     partner_id = mapped_column(String, server_default=text("uuid_generate_v4"), primary_key=True)
-    name = mapped_column(String, unique=True)
-    email = mapped_column(String, unique=True, nullable=True)
+    name = mapped_column(String, unique=True, nullable=True)
+    email = mapped_column(String, unique=True, nullable=False)
+    partner_type = mapped_column(Enum(PartnerType), nullable=False)
+
     as_number = mapped_column(
-        String, unique=True
+        String, unique=True, nullable=True
     )  # the as_number and as_set are mutually exclusive. if you give me one I don't need the other
-    as_set = mapped_column(String)
+    as_set = mapped_column(String, nullable=True)
     route_set = mapped_column(String, nullable=True)
     black_listed_as_sets = mapped_column(ARRAY(String), nullable=True)
     additional_routers = mapped_column(ARRAY(String), nullable=True)
     additional_bgp_speakers = mapped_column(ARRAY(String), nullable=True)
 
-    partner_type = mapped_column(Enum(PartnerType), nullable=False)
     created_at = mapped_column(UtcTimestamp, server_default=text("current_timestamp"), nullable=False)
     updated_at = mapped_column(
         UtcTimestamp, server_default=text("current_timestamp"), nullable=False, onupdate=text("current_timestamp")
diff --git a/gso/migrations/versions/2023-11-21_e8378fbcfbf3_add_initial_products.py b/gso/migrations/versions/2023-11-21_e8378fbcfbf3_add_initial_products.py
index 12c51d34d2aba2e4eb5d569f361e9e1c25409c43..d0c1a38893f1355a597a121cd39d553b6a847325 100644
--- a/gso/migrations/versions/2023-11-21_e8378fbcfbf3_add_initial_products.py
+++ b/gso/migrations/versions/2023-11-21_e8378fbcfbf3_add_initial_products.py
@@ -11,7 +11,7 @@ from alembic import op
 # revision identifiers, used by Alembic.
 revision = 'e8378fbcfbf3'
 down_revision = 'da5c9f4cce1c'
-branch_labels = ('data',)
+branch_labels = None
 depends_on = None
 
 
diff --git a/gso/migrations/versions/2024-03-20_d61c0f92da1e_edit_partner_table_making_some_fields_.py b/gso/migrations/versions/2024-03-20_d61c0f92da1e_edit_partner_table_making_some_fields_.py
new file mode 100644
index 0000000000000000000000000000000000000000..8449597fb5eec689e1a840a1ab700f22bf121640
--- /dev/null
+++ b/gso/migrations/versions/2024-03-20_d61c0f92da1e_edit_partner_table_making_some_fields_.py
@@ -0,0 +1,30 @@
+"""Edit Partner table, Making some fields required.
+
+Revision ID: d61c0f92da1e
+Revises: eaed66b04913
+Create Date: 2024-03-20 12:29:24.145489
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'd61c0f92da1e'
+down_revision = 'eaed66b04913'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(
+        sa.text(
+            """UPDATE partners SET email = 'goat@geant.org' WHERE name='GEANT'"""))
+
+    op.alter_column('partners', 'email', existing_type=sa.String(), nullable=False)
+
+
+def downgrade() -> None:
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.alter_column('partners', 'email', existing_type=sa.String(), nullable=True)
+    # ### end Alembic commands ###
diff --git a/gso/migrations/versions/2024-03-21_734e36a3e70b_add_subscription_cancellation_workflow.py b/gso/migrations/versions/2024-03-21_734e36a3e70b_add_subscription_cancellation_workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..195995bac5923f770effa966475a7159b0707998
--- /dev/null
+++ b/gso/migrations/versions/2024-03-21_734e36a3e70b_add_subscription_cancellation_workflow.py
@@ -0,0 +1,45 @@
+"""Add subscription cancellation workflow.
+
+Revision ID: 734e36a3e70b
+Revises: d61c0f92da1e
+Create Date: 2024-03-21 13:03:08.981028
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '734e36a3e70b'
+down_revision = 'a2cd3f2e6d7a'
+branch_labels = None
+depends_on = None
+
+
+from orchestrator.migrations.helpers import add_products_to_workflow_by_product_tag, create_workflow, delete_workflow, remove_products_from_workflow_by_product_tag
+
+
+products = ["RTR", "IPTRUNK"]
+new_workflows = [
+    {
+        "name": "cancel_subscription",
+        "target": "TERMINATE",
+        "description": "Cancel a subscription",
+        "product_type": "Site"
+    }
+]
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        create_workflow(conn, workflow)
+    for product_tag in products:
+        add_products_to_workflow_by_product_tag(conn, "cancel_subscription", product_tag)
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    for product_tag in products:
+        remove_products_from_workflow_by_product_tag(conn, "cancel_subscription", product_tag)
+    for workflow in new_workflows:
+        delete_workflow(conn, workflow["name"])
diff --git a/gso/migrations/versions/2024-03-21_a2cd3f2e6d7a_modify_connection_streategy_workflow.py b/gso/migrations/versions/2024-03-21_a2cd3f2e6d7a_modify_connection_streategy_workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..d15621b846374f2f5a2846f7ccb48e36a49723a0
--- /dev/null
+++ b/gso/migrations/versions/2024-03-21_a2cd3f2e6d7a_modify_connection_streategy_workflow.py
@@ -0,0 +1,39 @@
+"""Modify connection strategy workflow.
+
+Revision ID: a2cd3f2e6d7a
+Revises:
+Create Date: 2024-03-21 16:05:59.043106
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'a2cd3f2e6d7a'
+down_revision = 'd61c0f92da1e'
+branch_labels = None
+depends_on = None
+
+
+from orchestrator.migrations.helpers import create_workflow, delete_workflow
+
+new_workflows = [
+    {
+        "name": "modify_connection_strategy",
+        "target": "MODIFY",
+        "description": "Modify connection strategy",
+        "product_type": "Router"
+    }
+]
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        create_workflow(conn, workflow)
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        delete_workflow(conn, workflow["name"])
diff --git a/gso/migrations/versions/2024-03-27_4ec89ab289c0_remove_subscription_cancellation_.py b/gso/migrations/versions/2024-03-27_4ec89ab289c0_remove_subscription_cancellation_.py
new file mode 100644
index 0000000000000000000000000000000000000000..d9ee38563c2eed43f0012b504af4d81af8d4d07c
--- /dev/null
+++ b/gso/migrations/versions/2024-03-27_4ec89ab289c0_remove_subscription_cancellation_.py
@@ -0,0 +1,51 @@
+"""remove subscription cancellation workflow.
+
+Revision ID: 4ec89ab289c0
+Revises:
+Create Date: 2024-03-27 10:21:08.539591
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '4ec89ab289c0'
+down_revision = '734e36a3e70b'
+branch_labels = None
+depends_on = None
+
+
+from orchestrator.migrations.helpers import create_workflow, delete_workflow
+
+old_workflows = [
+    {
+        "name": "cancel_subscription",
+        "target": "TERMINATE",
+        "description": "Cancel a subscription",
+        "product_type": "Iptrunk"
+    },
+    {
+        "name": "cancel_subscription",
+        "target": "TERMINATE",
+        "description": "Cancel a subscription",
+        "product_type": "Router"
+    },
+    {
+        "name": "cancel_subscription",
+        "target": "TERMINATE",
+        "description": "Cancel a subscription",
+        "product_type": "Site"
+    }
+]
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    for workflow in old_workflows:
+        delete_workflow(conn, workflow["name"])
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    for workflow in old_workflows:
+        create_workflow(conn, workflow)
diff --git a/gso/products/__init__.py b/gso/products/__init__.py
index 2fd25d9fe556299706f618213d06a9ae9f04763e..32350d65cc3a9b0e92e4757ffc8a7faf632f7379 100644
--- a/gso/products/__init__.py
+++ b/gso/products/__init__.py
@@ -15,8 +15,8 @@ from gso.products.product_types.site import Site
 from gso.products.product_types.super_pop_switch import SuperPopSwitch
 
 
-class ProductType(strEnum):
-    """An enumerator of available products in :term:`GSO`."""
+class ProductName(strEnum):
+    """An enumerator of available product names in :term:`GSO`."""
 
     IP_TRUNK = "IP trunk"
     ROUTER = "Router"
@@ -25,12 +25,22 @@ class ProductType(strEnum):
     OFFICE_ROUTER = "Office router"
 
 
+class ProductType(strEnum):
+    """An enumerator of available product types in :term:`GSO`."""
+
+    IP_TRUNK = Iptrunk.__name__
+    ROUTER = Router.__name__
+    SITE = Site.__name__
+    SUPER_POP_SWITCH = SuperPopSwitch.__name__
+    OFFICE_ROUTER = OfficeRouter.__name__
+
+
 SUBSCRIPTION_MODEL_REGISTRY.update(
     {
-        "IP trunk": Iptrunk,
-        "Router": Router,
-        "Site": Site,
-        "Super PoP switch": SuperPopSwitch,
-        "Office router": OfficeRouter,
+        ProductName.IP_TRUNK.value: Iptrunk,
+        ProductName.ROUTER.value: Router,
+        ProductName.SITE.value: Site,
+        ProductName.SUPER_POP_SWITCH.value: SuperPopSwitch,
+        ProductName.OFFICE_ROUTER.value: OfficeRouter,
     },
 )
diff --git a/gso/services/subscriptions.py b/gso/services/subscriptions.py
index e89d9c25ad1cc52f68ae6ac3aea75a0542b92559..8d9900a39049275509b82e94043018a048d122ff 100644
--- a/gso/services/subscriptions.py
+++ b/gso/services/subscriptions.py
@@ -19,28 +19,31 @@ from orchestrator.services.subscriptions import query_in_use_by_subscriptions
 from orchestrator.types import SubscriptionLifecycle
 from pydantic_forms.types import UUIDstr
 
-from gso.products import ProductType
+from gso.products import ProductName, ProductType
 from gso.products.product_types.site import Site
 
 SubscriptionType = dict[str, Any]
 
 
 def get_subscriptions(
-    product_type: str,
-    lifecycle: SubscriptionLifecycle,
+    product_types: list[ProductType],
+    lifecycles: list[SubscriptionLifecycle] | None = None,
     includes: list[str] | None = None,
     excludes: list[str] | None = None,
 ) -> list[SubscriptionType]:
     """Retrieve active subscriptions for a specific product type.
 
-    :param str product_type: The type of the product for which to retrieve subscriptions.
-    :param SubscriptionLifecycle lifecycle: The lifecycle that the products must be in.
+    :param list[ProductName] product_types: The types of the product for which to retrieve subscriptions.
+    :param SubscriptionLifecycle lifecycles: The lifecycles that the products must be in.
     :param list[str] includes: List of fields to be included in the returned Subscription objects.
     :param list[str] excludes: List of fields to be excluded from the returned Subscription objects.
 
     :return: A list of Subscription objects that match the query.
     :rtype: list[Subscription]
     """
+    if not lifecycles:
+        lifecycles = list(SubscriptionLifecycle)
+
     if not includes:
         includes = [col.name for col in SubscriptionTable.__table__.columns]
 
@@ -50,8 +53,8 @@ def get_subscriptions(
     dynamic_fields = [getattr(SubscriptionTable, field) for field in includes]
 
     query = SubscriptionTable.query.join(ProductTable).filter(
-        ProductTable.product_type == product_type,
-        SubscriptionTable.status == lifecycle,
+        ProductTable.product_type.in_([str(product_type) for product_type in product_types]),
+        SubscriptionTable.status.in_([str(lifecycle) for lifecycle in lifecycles]),
     )
 
     results = query.with_entities(*dynamic_fields).all()
@@ -68,7 +71,23 @@ def get_active_site_subscriptions(includes: list[str] | None = None) -> list[Sub
     :return: A list of Subscription objects for sites.
     :rtype: list[Subscription]
     """
-    return get_subscriptions(product_type=ProductType.SITE, lifecycle=SubscriptionLifecycle.ACTIVE, includes=includes)
+    return get_subscriptions(
+        product_types=[ProductType.SITE], lifecycles=[SubscriptionLifecycle.ACTIVE], includes=includes
+    )
+
+
+def get_router_subscriptions(
+    includes: list[str] | None = None, lifecycles: list[SubscriptionLifecycle] | None = None
+) -> list[SubscriptionType]:
+    """Retrieve subscriptions specifically for routers.
+
+    :param includes: The fields to be included in the returned Subscription objects.
+    :type includes: list[str]
+
+    :return: A list of Subscription objects for routers.
+    :rtype: list[Subscription]
+    """
+    return get_subscriptions(product_types=[ProductType.ROUTER], lifecycles=lifecycles, includes=includes)
 
 
 def get_active_router_subscriptions(includes: list[str] | None = None) -> list[SubscriptionType]:
@@ -80,7 +99,9 @@ def get_active_router_subscriptions(includes: list[str] | None = None) -> list[S
     :return: A list of Subscription objects for routers.
     :rtype: list[Subscription]
     """
-    return get_subscriptions(product_type="Router", lifecycle=SubscriptionLifecycle.ACTIVE, includes=includes)
+    return get_subscriptions(
+        product_types=[ProductType.ROUTER], lifecycles=[SubscriptionLifecycle.ACTIVE], includes=includes
+    )
 
 
 def get_provisioning_router_subscriptions(includes: list[str] | None = None) -> list[SubscriptionType]:
@@ -89,7 +110,9 @@ def get_provisioning_router_subscriptions(includes: list[str] | None = None) ->
     :param list[str] includes: The fields to be included in the returned Subscription objects.
     :return list[Subscription]: A list of router Subscription objects.
     """
-    return get_subscriptions(product_type="Router", lifecycle=SubscriptionLifecycle.PROVISIONING, includes=includes)
+    return get_subscriptions(
+        product_types=[ProductType.ROUTER], lifecycles=[SubscriptionLifecycle.PROVISIONING], includes=includes
+    )
 
 
 def get_active_iptrunk_subscriptions(includes: list[str] | None = None) -> list[SubscriptionType]:
@@ -101,17 +124,22 @@ def get_active_iptrunk_subscriptions(includes: list[str] | None = None) -> list[
     :return: A list of Subscription objects for IP trunks.
     :rtype: list[Subscription]
     """
-    return get_subscriptions(product_type="Iptrunk", lifecycle=SubscriptionLifecycle.ACTIVE, includes=includes)
+    return get_subscriptions(
+        product_types=[ProductType.IP_TRUNK], lifecycles=[SubscriptionLifecycle.ACTIVE], includes=includes
+    )
 
 
-def get_active_trunks_that_terminate_on_router(subscription_id: UUIDstr) -> list[SubscriptionTable]:
-    """Get all IP trunk subscriptions that are active, and terminate on the given ``subscription_id`` of a Router.
+def get_trunks_that_terminate_on_router(
+    subscription_id: UUIDstr, lifecycle_state: SubscriptionLifecycle
+) -> list[SubscriptionTable]:
+    """Get all IP trunk subscriptions that terminate on the given ``subscription_id`` of a Router.
 
-    Given a ``subscription_id`` of a Router subscription, this method gives a list of all active IP trunk subscriptions
-    that terminate on this Router.
+    Given a ``subscription_id`` of a Router subscription, this method gives a list of all IP trunk subscriptions that
+    terminate on this Router. The given lifecycle state dictates the state of trunk subscriptions that are counted as
+    terminating on this router.
 
-    :param subscription_id: Subscription ID of a Router
-    :type subscription_id: UUIDstr
+    :param UUIDstr subscription_id: Subscription ID of a Router
+    :param SubscriptionLifecycle lifecycle_state: Required lifecycle state of the IP trunk
 
     :return: A list of IP trunk subscriptions
     :rtype: list[SubscriptionTable]
@@ -120,18 +148,18 @@ def get_active_trunks_that_terminate_on_router(subscription_id: UUIDstr) -> list
         query_in_use_by_subscriptions(UUID(subscription_id))
         .join(ProductTable)
         .filter(
-            ProductTable.product_type == "Iptrunk",
-            SubscriptionTable.status == "active",
+            ProductTable.product_type == ProductType.IP_TRUNK,
+            SubscriptionTable.status == lifecycle_state,
         )
         .all()
     )
 
 
-def get_product_id_by_name(product_name: ProductType) -> UUID:
+def get_product_id_by_name(product_name: ProductName) -> UUID:
     """Retrieve the :term:`UUID` of a product by its name.
 
     :param product_name: The name of the product.
-    :type product_name: ProductType
+    :type product_name: ProductName
 
     :return UUID: The :term:`UUID` of the product.
     :rtype: UUID
diff --git a/gso/translations/en-GB.json b/gso/translations/en-GB.json
index 46f0e96e201e09fd1e8b3e9973084289e108f075..fe687f342d00b95c0a44c1045a3e9718f8c1df47 100644
--- a/gso/translations/en-GB.json
+++ b/gso/translations/en-GB.json
@@ -38,10 +38,12 @@
     "workflow": {
         "activate_iptrunk": "Activate IP Trunk",
         "activate_router": "Activate router",
+        "cancel_subscription": "Cancel subscription",
         "confirm_info": "Please verify this form looks correct.",
         "deploy_twamp": "Deploy TWAMP",
         "migrate_iptrunk": "Migrate IP Trunk",
         "modify_isis_metric": "Modify the ISIS metric",
+        "modify_site": "Modify site",
         "modify_trunk_interface": "Modify IP Trunk interface",
         "redeploy_base_config": "Redeploy base config",
         "update_ibgp_mesh": "Update iBGP mesh"
diff --git a/gso/utils/shared_enums.py b/gso/utils/shared_enums.py
index 4861e9e134ecf113d74772a08282e7928829d19b..c0116e1690d6384cabd9ce16cf1ee79201a0d6b8 100644
--- a/gso/utils/shared_enums.py
+++ b/gso/utils/shared_enums.py
@@ -19,3 +19,10 @@ class PortNumber(ConstrainedInt):
 
     gt = 0
     le = 49151
+
+
+class ConnectionStrategy(strEnum):
+    """An enumerator for the connection Strategies."""
+
+    IN_BAND = "IN BAND"
+    OUT_OF_BAND = "OUT OF BAND"
diff --git a/gso/utils/workflow_steps.py b/gso/utils/workflow_steps.py
index 522ceaf58587fbfe7ae4555637431c5e90568ca7..7b3b5cc91fe5899b1c8580501d85da3a638459d9 100644
--- a/gso/utils/workflow_steps.py
+++ b/gso/utils/workflow_steps.py
@@ -63,7 +63,7 @@ def deploy_base_config_real(
     return {"subscription": subscription}
 
 
-@step("[COMMIT] Set ISIS metric to very high value")
+@step("[FOR REAL] Set ISIS metric to very high value")
 def set_isis_to_max(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State:
     """Workflow step for setting the :term:`ISIS` metric to an arbitrarily high value to drain a link."""
     old_isis_metric = subscription.iptrunk.iptrunk_isis_metric
@@ -92,7 +92,7 @@ def set_isis_to_max(subscription: Iptrunk, process_id: UUIDstr, callback_route:
     }
 
 
-@step("[CHECK] Run show commands after base config install")
+@step("Run show commands after base config install")
 def run_checks_after_base_config(subscription: dict[str, Any], callback_route: str) -> None:
     """Workflow step for running show commands after installing base config."""
     execute_playbook(
diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index d25088730e93388aadef15057e80d3eca8a93ce6..05848a322c6b893231a8c754954c43ecf38da943 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -5,6 +5,7 @@ from orchestrator.workflows import LazyWorkflowInstance
 
 WF_USABLE_MAP.update(
     {
+        "cancel_subscription": ["initial"],
         "redeploy_base_config": ["provisioning", "active"],
         "update_ibgp_mesh": ["provisioning", "active"],
         "activate_router": ["provisioning"],
@@ -26,6 +27,8 @@ LazyWorkflowInstance("gso.workflows.router.create_router", "create_router")
 LazyWorkflowInstance("gso.workflows.router.redeploy_base_config", "redeploy_base_config")
 LazyWorkflowInstance("gso.workflows.router.terminate_router", "terminate_router")
 LazyWorkflowInstance("gso.workflows.router.update_ibgp_mesh", "update_ibgp_mesh")
+LazyWorkflowInstance("gso.workflows.router.modify_connection_strategy", "modify_connection_strategy")
+LazyWorkflowInstance("gso.workflows.shared.cancel_subscription", "cancel_subscription")
 LazyWorkflowInstance("gso.workflows.site.create_site", "create_site")
 LazyWorkflowInstance("gso.workflows.site.modify_site", "modify_site")
 LazyWorkflowInstance("gso.workflows.site.terminate_site", "terminate_site")
diff --git a/gso/workflows/iptrunk/create_iptrunk.py b/gso/workflows/iptrunk/create_iptrunk.py
index 481ca07838d523e366e7c8387442c0a8dba94336..378f32b0beebfc9012bab8605f01d6f3f33aa891 100644
--- a/gso/workflows/iptrunk/create_iptrunk.py
+++ b/gso/workflows/iptrunk/create_iptrunk.py
@@ -253,7 +253,7 @@ def initialize_subscription(
     return {"subscription": subscription}
 
 
-@step("Provision IP trunk interface [DRY RUN]")
+@step("[DRY RUN] Provision IP trunk interface")
 def provision_ip_trunk_iface_dry(
     subscription: IptrunkInactive,
     callback_route: str,
@@ -281,7 +281,7 @@ def provision_ip_trunk_iface_dry(
     return {"subscription": subscription}
 
 
-@step("Provision IP trunk interface [FOR REAL]")
+@step("[FOR REAL] Provision IP trunk interface")
 def provision_ip_trunk_iface_real(
     subscription: IptrunkInactive,
     callback_route: str,
@@ -327,7 +327,7 @@ def check_ip_trunk_connectivity(
     return {"subscription": subscription}
 
 
-@step("Provision IP trunk ISIS interface [DRY RUN]")
+@step("[DRY RUN] Provision IP trunk ISIS interface")
 def provision_ip_trunk_isis_iface_dry(
     subscription: IptrunkInactive,
     callback_route: str,
@@ -355,7 +355,7 @@ def provision_ip_trunk_isis_iface_dry(
     return {"subscription": subscription}
 
 
-@step("Provision IP trunk ISIS interface [FOR REAL]")
+@step("[FOR REAL] Provision IP trunk ISIS interface")
 def provision_ip_trunk_isis_iface_real(
     subscription: IptrunkInactive,
     callback_route: str,
diff --git a/gso/workflows/iptrunk/migrate_iptrunk.py b/gso/workflows/iptrunk/migrate_iptrunk.py
index 3ee509cb23ea44bc236ba7f7158b65200c9741aa..add6d5f01d6c5d74f31dc426453856f4240bf963 100644
--- a/gso/workflows/iptrunk/migrate_iptrunk.py
+++ b/gso/workflows/iptrunk/migrate_iptrunk.py
@@ -276,7 +276,7 @@ def disable_old_config_dry(
     return {"subscription": subscription}
 
 
-@step("[REAL] Disable configuration on old router")
+@step("[FOR REAL] Disable configuration on old router")
 def disable_old_config_real(
     subscription: Iptrunk,
     callback_route: str,
@@ -352,7 +352,7 @@ def deploy_new_config_dry(
     return {"subscription": subscription}
 
 
-@step("Deploy configuration on new router")
+@step("[FOR REAL] Deploy configuration on new router")
 def deploy_new_config_real(
     subscription: Iptrunk,
     callback_route: str,
@@ -423,7 +423,7 @@ def check_ip_trunk_connectivity(
     return {"subscription": subscription}
 
 
-@step("Deploy ISIS configuration on new router")
+@step("[FOR REAL] Deploy ISIS configuration on new router")
 def deploy_new_isis(
     subscription: Iptrunk,
     callback_route: str,
@@ -494,7 +494,7 @@ def confirm_continue_restore_isis() -> FormGenerator:
     return {}
 
 
-@step("Restore ISIS metric to original value")
+@step("[FOR REAL] Restore ISIS metric to original value")
 def restore_isis_metric(
     subscription: Iptrunk,
     process_id: UUIDstr,
@@ -561,7 +561,7 @@ def delete_old_config_dry(
     return {"subscription": subscription}
 
 
-@step("Delete configuration on old router")
+@step("[FOR REAL] Delete configuration on old router")
 def delete_old_config_real(
     subscription: Iptrunk,
     callback_route: str,
diff --git a/gso/workflows/iptrunk/modify_isis_metric.py b/gso/workflows/iptrunk/modify_isis_metric.py
index 55fea705f8af6595933df3786a96de4bf962b953..b9e0a54934d7cff5f891bdce787f17724999b4f3 100644
--- a/gso/workflows/iptrunk/modify_isis_metric.py
+++ b/gso/workflows/iptrunk/modify_isis_metric.py
@@ -35,7 +35,7 @@ def modify_iptrunk_subscription(subscription: Iptrunk, isis_metric: int) -> Stat
     return {"subscription": subscription}
 
 
-@step("Provision IP trunk ISIS interface [DRY RUN]")
+@step("[DRY RUN] Provision IP trunk ISIS interface")
 def provision_ip_trunk_isis_iface_dry(
     subscription: Iptrunk,
     process_id: UUIDstr,
@@ -63,7 +63,7 @@ def provision_ip_trunk_isis_iface_dry(
     return {"subscription": subscription}
 
 
-@step("Provision IP trunk ISIS interface [FOR REAL]")
+@step("[FOR REAL] Provision IP trunk ISIS interface")
 def provision_ip_trunk_isis_iface_real(
     subscription: Iptrunk,
     process_id: UUIDstr,
diff --git a/gso/workflows/iptrunk/modify_trunk_interface.py b/gso/workflows/iptrunk/modify_trunk_interface.py
index 86b43eb55955857148c9184ae9963858bced337c..329d84aa6ef77002e1cc89bf3aa623672dd16167 100644
--- a/gso/workflows/iptrunk/modify_trunk_interface.py
+++ b/gso/workflows/iptrunk/modify_trunk_interface.py
@@ -214,7 +214,7 @@ def modify_iptrunk_subscription(
     }
 
 
-@step("Provision IP trunk interface [DRY RUN]")
+@step("[DRY RUN] Provision IP trunk interface")
 def provision_ip_trunk_iface_dry(
     subscription: Iptrunk,
     process_id: UUIDstr,
@@ -244,7 +244,7 @@ def provision_ip_trunk_iface_dry(
     return {"subscription": subscription}
 
 
-@step("Provision IP trunk interface [FOR REAL]")
+@step("[FOR REAL] Provision IP trunk interface")
 def provision_ip_trunk_iface_real(
     subscription: Iptrunk,
     process_id: UUIDstr,
diff --git a/gso/workflows/iptrunk/terminate_iptrunk.py b/gso/workflows/iptrunk/terminate_iptrunk.py
index 469491ebde407ddb5d92e9ffbb32e96fbb12d994..7a2afe6c22f0e2a1f381c92734f0efd5a8dbbb25 100644
--- a/gso/workflows/iptrunk/terminate_iptrunk.py
+++ b/gso/workflows/iptrunk/terminate_iptrunk.py
@@ -49,7 +49,7 @@ def initial_input_form_generator() -> FormGenerator:
     return user_input.dict()
 
 
-@step("Deprovision IP trunk [DRY RUN]")
+@step("[DRY RUN] Deprovision IP trunk")
 def deprovision_ip_trunk_dry(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State:
     """Perform a dry run of deleting configuration from the routers."""
     extra_vars = {
@@ -72,7 +72,7 @@ def deprovision_ip_trunk_dry(subscription: Iptrunk, process_id: UUIDstr, callbac
     return {"subscription": subscription}
 
 
-@step("Deprovision IP trunk [FOR REAL]")
+@step("[FOR REAL] Deprovision IP trunk")
 def deprovision_ip_trunk_real(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State:
     """Delete configuration from the routers."""
     extra_vars = {
diff --git a/gso/workflows/router/modify_connection_strategy.py b/gso/workflows/router/modify_connection_strategy.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3f5b5ae2f1f8cd0aa58d1d407d2daf28662c8a0
--- /dev/null
+++ b/gso/workflows/router/modify_connection_strategy.py
@@ -0,0 +1,52 @@
+"""Modify connection strategy workflow. Flipping the connection between in-band to out-of-band."""
+
+from orchestrator.forms import FormPage
+from orchestrator.targets import Target
+from orchestrator.types import FormGenerator, State, UUIDstr
+from orchestrator.workflow import StepList, done, init, step, workflow
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+
+from gso.products.product_types.router import Router
+from gso.utils.shared_enums import ConnectionStrategy
+
+
+def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
+    """Modify the connection strategy initial formruff format."""
+    subscription = Router.from_subscription(subscription_id)
+
+    current_connection_strategy = (
+        ConnectionStrategy.OUT_OF_BAND if subscription.router.router_access_via_ts else ConnectionStrategy.IN_BAND
+    )
+
+    class ModifyConnectionStrategyForm(FormPage):
+        class Config:
+            title = f"Modify the connection strategy of {subscription.router.router_fqdn}."
+
+        connection_strategy: ConnectionStrategy = current_connection_strategy
+
+    user_input = yield ModifyConnectionStrategyForm
+
+    return user_input.dict()
+
+
+@step("Update subscription model")
+def update_subscription_model(subscription: Router, connection_strategy: str) -> State:
+    """Update the database model to reflect the new connection strategy.
+
+    If the connection strategy is set to IN-BAND, then access_via_ts should be set to False.
+    Conversely, if the connection strategy is set to OUT-OF-BAND, access_via_ts should be set to True.
+    """
+    subscription.router.router_access_via_ts = connection_strategy == ConnectionStrategy.OUT_OF_BAND
+
+    return {"subscription": subscription}
+
+
+@workflow(
+    "Modify connection strategy",
+    initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
+    target=Target.MODIFY,
+)
+def modify_connection_strategy() -> StepList:
+    """Modify the connection strategy."""
+    return init >> store_process_subscription(Target.MODIFY) >> unsync >> update_subscription_model >> resync >> done
diff --git a/gso/workflows/router/update_ibgp_mesh.py b/gso/workflows/router/update_ibgp_mesh.py
index 80f63d21fbcf9c4a87545364504198f3fe67cde4..abef7da440b8024b578a7122be54f4440eb6764a 100644
--- a/gso/workflows/router/update_ibgp_mesh.py
+++ b/gso/workflows/router/update_ibgp_mesh.py
@@ -6,7 +6,7 @@ from orchestrator.config.assignee import Assignee
 from orchestrator.forms import FormPage
 from orchestrator.forms.validators import Label
 from orchestrator.targets import Target
-from orchestrator.types import FormGenerator, State, UUIDstr
+from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr
 from orchestrator.workflow import StepList, done, init, inputstep, step, workflow
 from orchestrator.workflows.steps import resync, store_process_subscription, unsync
 from orchestrator.workflows.utils import wrap_modify_initial_input_form
@@ -16,7 +16,7 @@ from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import Router
 from gso.services import librenms_client, lso_client, subscriptions
 from gso.services.lso_client import lso_interaction
-from gso.services.subscriptions import get_active_trunks_that_terminate_on_router
+from gso.services.subscriptions import get_trunks_that_terminate_on_router
 from gso.utils.helpers import SNMPVersion
 
 
@@ -36,8 +36,11 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
 
         @root_validator(allow_reuse=True)
         def router_has_a_trunk(cls, values: dict[str, Any]) -> dict[str, Any]:
-            if len(get_active_trunks_that_terminate_on_router(subscription_id)) == 0:
-                msg = "Selected router does not terminate any active IP trunks."
+            terminating_trunks = get_trunks_that_terminate_on_router(
+                subscription_id, SubscriptionLifecycle.PROVISIONING
+            ) + get_trunks_that_terminate_on_router(subscription_id, SubscriptionLifecycle.ACTIVE)
+            if len(terminating_trunks) == 0:
+                msg = "Selected router does not terminate any available IP trunks."
                 raise ValueError(msg)
 
             return values
diff --git a/gso/workflows/shared/__init__.py b/gso/workflows/shared/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..daa25805d2c02ae05d7292e4a74a649d93a07e6b
--- /dev/null
+++ b/gso/workflows/shared/__init__.py
@@ -0,0 +1 @@
+"""Workflows that are shared across multiple products."""
diff --git a/gso/workflows/shared/cancel_subscription.py b/gso/workflows/shared/cancel_subscription.py
new file mode 100644
index 0000000000000000000000000000000000000000..79bb05905f6940ce3eda6543d960acf41818fb4f
--- /dev/null
+++ b/gso/workflows/shared/cancel_subscription.py
@@ -0,0 +1,47 @@
+"""Cancel a subscription that is in the initial lifecycle state."""
+
+from orchestrator.forms import FormPage
+from orchestrator.forms.validators import Label
+from orchestrator.targets import Target
+from orchestrator.types import FormGenerator, SubscriptionLifecycle, UUIDstr
+from orchestrator.workflow import StepList, done, init, workflow
+from orchestrator.workflows.steps import resync, set_status, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+
+
+def _initial_input_form(subscription_id: UUIDstr) -> FormGenerator:
+    class CancelSubscriptionForm(FormPage):
+        info_label: Label = f"Canceling subscription with ID {subscription_id}"  # type:ignore[assignment]
+        info_label_2: Label = (
+            "This will immediately mark the subscription as terminated, preventing any other workflows from interacting"  # type:ignore[assignment]
+            " with this product subscription."
+        )
+        info_label_3: Label = "ONLY EXECUTE THIS WORKFLOW WHEN YOU ARE ABSOLUTELY SURE WHAT YOU ARE DOING."  # type:ignore[assignment]
+        info_label_4: Label = "THIS WORKFLOW IS IRREVERSIBLE AND MAY HAVE UNFORESEEN CONSEQUENCES."  # type:ignore[assignment]
+
+    yield CancelSubscriptionForm
+
+    return {"subscription_id": subscription_id}
+
+
+@workflow(
+    "Cancel an initial subscription",
+    initial_input_form=wrap_modify_initial_input_form(_initial_input_form),
+    target=Target.TERMINATE,
+)
+def cancel_subscription() -> StepList:
+    """Cancel an initial subscription, taking it from the ``INITIAL`` state straight to ``TERMINATED``.
+
+    This workflow can be used when a creation workflow has failed, and the process needs to be restarted. This workflow
+    will prevent a stray subscription, forever stuck in the initial state, to stick around.
+
+    * Update the subscription lifecycle state to ``TERMINATED``.
+    """
+    return (
+        init
+        >> store_process_subscription(Target.TERMINATE)
+        >> unsync
+        >> set_status(SubscriptionLifecycle.TERMINATED)
+        >> resync
+        >> done
+    )
diff --git a/gso/workflows/tasks/import_iptrunk.py b/gso/workflows/tasks/import_iptrunk.py
index 877c375e3b88b858c8e3484b8943ef80deed8250..c34be8ed4e8e9564b19eca4f9f56ef4c27b2f7bd 100644
--- a/gso/workflows/tasks/import_iptrunk.py
+++ b/gso/workflows/tasks/import_iptrunk.py
@@ -11,7 +11,7 @@ from orchestrator.types import FormGenerator, State, SubscriptionLifecycle
 from orchestrator.workflow import StepList, done, init, step
 from orchestrator.workflows.steps import resync, set_status, store_process_subscription
 
-from gso.products import ProductType
+from gso.products import ProductName
 from gso.products.product_blocks.iptrunk import IptrunkInterfaceBlockInactive, IptrunkType, PhyPortCapacity
 from gso.products.product_types.iptrunk import IptrunkInactive, IptrunkProvisioning
 from gso.products.product_types.router import Router
@@ -68,7 +68,7 @@ def initial_input_form_generator() -> FormGenerator:
 def create_subscription(partner: str) -> State:
     """Create a new subscription in the service database."""
     partner_id = get_partner_by_name(partner)["partner_id"]
-    product_id = subscriptions.get_product_id_by_name(ProductType.IP_TRUNK)
+    product_id = subscriptions.get_product_id_by_name(ProductName.IP_TRUNK)
     subscription = IptrunkInactive.from_product_id(product_id, partner_id)
 
     return {
diff --git a/gso/workflows/tasks/import_office_router.py b/gso/workflows/tasks/import_office_router.py
index 255c7f3133763609edc443f80d8199cef838882f..9168cdae0150a82a1893b6b3ceae450b5df542b5 100644
--- a/gso/workflows/tasks/import_office_router.py
+++ b/gso/workflows/tasks/import_office_router.py
@@ -9,7 +9,7 @@ from orchestrator.types import FormGenerator, State, SubscriptionLifecycle
 from orchestrator.workflow import StepList, done, init, step
 from orchestrator.workflows.steps import resync, set_status, store_process_subscription
 
-from gso.products import ProductType
+from gso.products import ProductName
 from gso.products.product_types import office_router
 from gso.products.product_types.office_router import OfficeRouterInactive
 from gso.services import subscriptions
@@ -22,7 +22,7 @@ from gso.utils.shared_enums import PortNumber, Vendor
 def create_subscription(partner: str) -> State:
     """Create a new subscription object."""
     partner_id = get_partner_by_name(partner)["partner_id"]
-    product_id = subscriptions.get_product_id_by_name(ProductType.OFFICE_ROUTER)
+    product_id = subscriptions.get_product_id_by_name(ProductName.OFFICE_ROUTER)
     subscription = OfficeRouterInactive.from_product_id(product_id, partner_id)
 
     return {
diff --git a/gso/workflows/tasks/import_router.py b/gso/workflows/tasks/import_router.py
index d284dbc56c9dc628739d918b47b5c7d93f2f1d28..c71ce26ee47a0e0929842d7261d7c0fd195d2e55 100644
--- a/gso/workflows/tasks/import_router.py
+++ b/gso/workflows/tasks/import_router.py
@@ -9,7 +9,7 @@ from orchestrator.types import FormGenerator, State, SubscriptionLifecycle
 from orchestrator.workflow import StepList, done, init, step
 from orchestrator.workflows.steps import resync, set_status, store_process_subscription
 
-from gso.products import ProductType
+from gso.products import ProductName
 from gso.products.product_blocks import router as router_pb
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types import router
@@ -25,7 +25,7 @@ from gso.utils.shared_enums import PortNumber, Vendor
 def create_subscription(partner: str) -> State:
     """Create a new subscription object."""
     partner_id = get_partner_by_name(partner)["partner_id"]
-    product_id = subscriptions.get_product_id_by_name(ProductType.ROUTER)
+    product_id = subscriptions.get_product_id_by_name(ProductName.ROUTER)
     subscription = RouterInactive.from_product_id(product_id, partner_id)
 
     return {
diff --git a/gso/workflows/tasks/import_site.py b/gso/workflows/tasks/import_site.py
index 026ffb1be99abab4f34d04f5cc64d72eba66f1c6..ff49808a5a86d1e73c6a741d74a75c4c7c233471 100644
--- a/gso/workflows/tasks/import_site.py
+++ b/gso/workflows/tasks/import_site.py
@@ -8,7 +8,7 @@ from orchestrator.types import FormGenerator, State, SubscriptionLifecycle
 from orchestrator.workflow import StepList, done, init, step, workflow
 from orchestrator.workflows.steps import resync, set_status, store_process_subscription
 
-from gso.products import ProductType
+from gso.products import ProductName
 from gso.products.product_blocks.site import SiteTier
 from gso.products.product_types.site import SiteInactive
 from gso.services import subscriptions
@@ -23,7 +23,7 @@ def create_subscription(partner: str) -> State:
     FIXME: all attributes passed by the input form appear to be unused
     """
     partner_id = get_partner_by_name(partner)["partner_id"]
-    product_id: UUID = subscriptions.get_product_id_by_name(ProductType.SITE)
+    product_id: UUID = subscriptions.get_product_id_by_name(ProductName.SITE)
     subscription = SiteInactive.from_product_id(product_id, partner_id)
 
     return {
diff --git a/gso/workflows/tasks/import_super_pop_switch.py b/gso/workflows/tasks/import_super_pop_switch.py
index aa6c832fde157ce6bcb1e0d6bd73a5e62f4e35c5..5f2796c2c2325ad439a0570db5154f57a0b435f1 100644
--- a/gso/workflows/tasks/import_super_pop_switch.py
+++ b/gso/workflows/tasks/import_super_pop_switch.py
@@ -9,7 +9,7 @@ from orchestrator.types import FormGenerator, State, SubscriptionLifecycle
 from orchestrator.workflow import StepList, done, init, step
 from orchestrator.workflows.steps import resync, set_status, store_process_subscription
 
-from gso.products import ProductType
+from gso.products import ProductName
 from gso.products.product_types import super_pop_switch
 from gso.products.product_types.super_pop_switch import SuperPopSwitchInactive
 from gso.services import subscriptions
@@ -23,7 +23,7 @@ from gso.utils.shared_enums import PortNumber, Vendor
 def create_subscription(partner: str) -> State:
     """Create a new subscription object."""
     partner_id = get_partner_by_name(partner)["partner_id"]
-    product_id = subscriptions.get_product_id_by_name(ProductType.SUPER_POP_SWITCH)
+    product_id = subscriptions.get_product_id_by_name(ProductName.SUPER_POP_SWITCH)
     subscription = SuperPopSwitchInactive.from_product_id(product_id, partner_id)
 
     return {
diff --git a/setup.py b/setup.py
index 3f5d75dc656e6416e47693fdc10235a230469d77..be3688d8df2ad8dda08a2f1ef611b261c1ce34ac 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import find_packages, setup
 
 setup(
     name="geant-service-orchestrator",
-    version="0.9",
+    version="1.0",
     author="GÉANT Orchestration and Automation Team",
     author_email="goat@geant.org",
     description="GÉANT Service Orchestrator",
diff --git a/test/api/conftest.py b/test/api/conftest.py
index e015f147d2b95bb8140ddabbf42ecadb68bb0302..e002fa13c19973dfbe733aa47fba34981558f116 100644
--- a/test/api/conftest.py
+++ b/test/api/conftest.py
@@ -2,5 +2,7 @@ from test.fixtures import (  # noqa: F401
     iptrunk_side_subscription_factory,
     iptrunk_subscription_factory,
     nokia_router_subscription_factory,
+    office_router_subscription_factory,
     site_subscription_factory,
+    super_pop_switch_subscription_factory,
 )
diff --git a/test/api/test_subscriptions.py b/test/api/test_subscriptions.py
index 37c74cd83bb24dfa32f338235983fb77ee7feee7..3e457a5879323360465a7b9a1ee23eb98627bfe3 100644
--- a/test/api/test_subscriptions.py
+++ b/test/api/test_subscriptions.py
@@ -1,6 +1,7 @@
 from orchestrator.types import SubscriptionLifecycle
 
 ROUTER_SUBSCRIPTION_ENDPOINT = "/api/v1/subscriptions/routers"
+DASHBOARD_DEVICES_ENDPOINT = "/api/v1/subscriptions/dashboard_devices"
 
 
 def test_router_subscriptions_endpoint_with_valid_api_key(test_client, nokia_router_subscription_factory):
@@ -30,3 +31,59 @@ def test_router_subscriptions_endpoint_without_api_key(test_client, nokia_router
 
     assert response.status_code == 403
     assert response.json() == {"detail": "Not authenticated"}
+
+
+def test_dashboard_devices_endpoint_with_valid_api_key(
+    test_client,
+    nokia_router_subscription_factory,
+    office_router_subscription_factory,
+    super_pop_switch_subscription_factory,
+):
+    nokia_router_subscription_factory(router_fqdn="mx1.ams.nl.geant.net")
+    nokia_router_subscription_factory(router_fqdn="mx2.ams.nl.geant.net")
+    nokia_router_subscription_factory(status=SubscriptionLifecycle.PROVISIONING, router_fqdn="mx3.ams.nl.geant.net")
+    nokia_router_subscription_factory(status=SubscriptionLifecycle.INITIAL, router_fqdn="mx4.ams.nl.geant.net")
+    office_router_subscription_factory(office_router_fqdn="office1.ams.nl.geant.net")
+    office_router_subscription_factory(
+        office_router_fqdn="office2.ams.nl.geant.net", status=SubscriptionLifecycle.TERMINATED
+    )
+    office_router_subscription_factory(
+        office_router_fqdn="office3.ams.nl.geant.net", status=SubscriptionLifecycle.PROVISIONING
+    )
+    super_pop_switch_subscription_factory(super_pop_switch_fqdn="superpop1.ams.nl.geant.net")
+    super_pop_switch_subscription_factory(
+        super_pop_switch_fqdn="superpop2.ams.nl.geant.net", status=SubscriptionLifecycle.TERMINATED
+    )
+    super_pop_switch_subscription_factory(
+        super_pop_switch_fqdn="superpop3.ams.nl.geant.net", status=SubscriptionLifecycle.PROVISIONING
+    )
+
+    response = test_client.get(
+        DASHBOARD_DEVICES_ENDPOINT, headers={"Authorization": "Bearer another_REALY_random_AND_3cure_T0keN"}
+    )
+
+    assert response.status_code == 200
+    fqdns = response.text.strip().split("\n")
+    assert sorted(fqdns) == [
+        "mx1.ams.nl.geant.net",
+        "mx2.ams.nl.geant.net",
+        "mx3.ams.nl.geant.net",
+        "office1.ams.nl.geant.net",
+        "office3.ams.nl.geant.net",
+        "superpop1.ams.nl.geant.net",
+        "superpop3.ams.nl.geant.net",
+    ]
+
+
+def test_dashboard_devices_endpoint_with_invalid_api_key(test_client, nokia_router_subscription_factory):
+    response = test_client.get(DASHBOARD_DEVICES_ENDPOINT, headers={"Authorization": "Bearer fake_invalid_api_key"})
+
+    assert response.status_code == 403
+    assert response.json() == {"detail": "Invalid API Key"}
+
+
+def test_dashboard_devices_endpoint_without_api_key(test_client, nokia_router_subscription_factory):
+    response = test_client.get(DASHBOARD_DEVICES_ENDPOINT)
+
+    assert response.status_code == 403
+    assert response.json() == {"detail": "Not authenticated"}
diff --git a/test/conftest.py b/test/conftest.py
index 96b4a64056c621b43e3dd724829bcda4835b3fe4..d0bfebfed7b8bf9e04e2d086fc5bd568550dc321 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -246,4 +246,4 @@ def test_client(fastapi_app):
 
 @pytest.fixture(scope="session")
 def geant_partner():
-    return create_partner(PartnerCreate(name="GEANT-TEST", partner_type=PartnerType.GEANT))
+    return create_partner(PartnerCreate(name="GEANT-TEST", partner_type=PartnerType.GEANT, email="goat-test@geant.org"))
diff --git a/test/fixtures.py b/test/fixtures.py
index 0800edec6a175435a7af60afa51cb3b99f1301b7..f0c55c2190041a837594b7907063abcdce69e04a 100644
--- a/test/fixtures.py
+++ b/test/fixtures.py
@@ -5,7 +5,7 @@ from orchestrator.db import db
 from orchestrator.domain import SubscriptionModel
 from orchestrator.types import SubscriptionLifecycle, UUIDstr
 
-from gso.products import ProductType
+from gso.products import ProductName
 from gso.products.product_blocks.iptrunk import (
     IptrunkInterfaceBlock,
     IptrunkSideBlock,
@@ -15,8 +15,10 @@ from gso.products.product_blocks.iptrunk import (
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_blocks.site import SiteTier
 from gso.products.product_types.iptrunk import IptrunkInactive
+from gso.products.product_types.office_router import OfficeRouterInactive
 from gso.products.product_types.router import Router, RouterInactive
 from gso.products.product_types.site import Site, SiteInactive
+from gso.products.product_types.super_pop_switch import SuperPopSwitchInactive
 from gso.services import subscriptions
 from gso.utils.shared_enums import Vendor
 
@@ -36,6 +38,7 @@ def site_subscription_factory(faker, geant_partner):
         site_internal_id=None,
         site_tier=SiteTier.TIER1,
         site_ts_address=None,
+        status: SubscriptionLifecycle | None = None,
         partner: dict | None = None,
     ) -> UUIDstr:
         if partner is None:
@@ -52,7 +55,7 @@ def site_subscription_factory(faker, geant_partner):
         site_internal_id = site_internal_id or faker.pyint()
         site_ts_address = site_ts_address or faker.ipv4()
 
-        product_id = subscriptions.get_product_id_by_name(ProductType.SITE)
+        product_id = subscriptions.get_product_id_by_name(ProductName.SITE)
         site_subscription = SiteInactive.from_product_id(product_id, customer_id=partner["partner_id"], insync=True)
         site_subscription.site.site_city = site_city
         site_subscription.site.site_name = site_name
@@ -68,6 +71,9 @@ def site_subscription_factory(faker, geant_partner):
         site_subscription = SubscriptionModel.from_other_lifecycle(site_subscription, SubscriptionLifecycle.ACTIVE)
         site_subscription.description = description
         site_subscription.start_date = start_date
+        if status:
+            site_subscription.status = status
+
         site_subscription.save()
         db.session.commit()
 
@@ -104,7 +110,7 @@ def nokia_router_subscription_factory(site_subscription_factory, faker, geant_pa
         router_lo_iso_address = router_lo_iso_address or faker.word()
         router_site = router_site or site_subscription_factory()
 
-        product_id = subscriptions.get_product_id_by_name(ProductType.ROUTER)
+        product_id = subscriptions.get_product_id_by_name(ProductName.ROUTER)
         router_subscription = RouterInactive.from_product_id(product_id, customer_id=partner["partner_id"], insync=True)
         router_subscription.router.router_fqdn = router_fqdn
         router_subscription.router.router_ts_port = router_ts_port
@@ -159,7 +165,7 @@ def juniper_router_subscription_factory(site_subscription_factory, faker, geant_
         router_lo_iso_address = router_lo_iso_address or faker.word()
         router_site = router_site or site_subscription_factory()
 
-        product_id = subscriptions.get_product_id_by_name(ProductType.ROUTER)
+        product_id = subscriptions.get_product_id_by_name(ProductName.ROUTER)
 
         router_subscription = RouterInactive.from_product_id(product_id, customer_id=partner["partner_id"], insync=True)
         router_subscription.router.router_fqdn = router_fqdn
@@ -244,7 +250,7 @@ def iptrunk_subscription_factory(iptrunk_side_subscription_factory, faker, geant
         if partner is None:
             partner = geant_partner
 
-        product_id = subscriptions.get_product_id_by_name(ProductType.IP_TRUNK)
+        product_id = subscriptions.get_product_id_by_name(ProductName.IP_TRUNK)
         description = description or faker.sentence()
 
         geant_s_sid = geant_s_sid or faker.geant_sid()
@@ -286,3 +292,106 @@ def iptrunk_subscription_factory(iptrunk_side_subscription_factory, faker, geant
         return str(iptrunk_subscription.subscription_id)
 
     return subscription_create
+
+
+@pytest.fixture()
+def office_router_subscription_factory(site_subscription_factory, faker, geant_partner):
+    def subscription_create(
+        description=None,
+        start_date="2023-05-24T00:00:00+00:00",
+        office_router_fqdn=None,
+        office_router_ts_port=None,
+        office_router_lo_ipv4_address=None,
+        office_router_lo_ipv6_address=None,
+        office_router_site=None,
+        status: SubscriptionLifecycle | None = None,
+        partner: dict | None = None,
+    ) -> UUIDstr:
+        if partner is None:
+            partner = geant_partner
+
+        description = description or faker.text(max_nb_chars=30)
+        office_router_fqdn = office_router_fqdn or faker.domain_name(levels=4)
+        office_router_ts_port = office_router_ts_port or faker.random_int(min=1, max=49151)
+        office_router_lo_ipv4_address = office_router_lo_ipv4_address or ipaddress.IPv4Address(faker.ipv4())
+        office_router_lo_ipv6_address = office_router_lo_ipv6_address or ipaddress.IPv6Address(faker.ipv6())
+        office_router_site = office_router_site or site_subscription_factory()
+
+        product_id = subscriptions.get_product_id_by_name(ProductName.OFFICE_ROUTER)
+        office_router_subscription = OfficeRouterInactive.from_product_id(
+            product_id, customer_id=partner["partner_id"], insync=True
+        )
+        office_router_subscription.office_router.office_router_fqdn = office_router_fqdn
+        office_router_subscription.office_router.office_router_ts_port = office_router_ts_port
+        office_router_subscription.office_router.office_router_lo_ipv4_address = office_router_lo_ipv4_address
+        office_router_subscription.office_router.office_router_lo_ipv6_address = office_router_lo_ipv6_address
+        office_router_subscription.office_router.office_router_site = Site.from_subscription(office_router_site).site
+        office_router_subscription.office_router.vendor = Vendor.NOKIA
+
+        office_router_subscription = SubscriptionModel.from_other_lifecycle(
+            office_router_subscription, SubscriptionLifecycle.ACTIVE
+        )
+        office_router_subscription.description = description
+        office_router_subscription.start_date = start_date
+
+        if status:
+            office_router_subscription.status = status
+
+        office_router_subscription.save()
+        db.session.commit()
+
+        return str(office_router_subscription.subscription_id)
+
+    return subscription_create
+
+
+@pytest.fixture()
+def super_pop_switch_subscription_factory(site_subscription_factory, faker, geant_partner):
+    def subscription_create(
+        description=None,
+        start_date="2023-05-24T00:00:00+00:00",
+        super_pop_switch_fqdn=None,
+        super_pop_switch_ts_port=None,
+        super_pop_switch_mgmt_ipv4_address=None,
+        super_pop_switch_site=None,
+        status: SubscriptionLifecycle | None = None,
+        partner: dict | None = None,
+    ) -> UUIDstr:
+        if partner is None:
+            partner = geant_partner
+
+        description = description or faker.text(max_nb_chars=30)
+        super_pop_switch_fqdn = super_pop_switch_fqdn or faker.domain_name(levels=4)
+        super_pop_switch_ts_port = super_pop_switch_ts_port or faker.random_int(min=1, max=49151)
+        super_pop_switch_mgmt_ipv4_address = super_pop_switch_mgmt_ipv4_address or ipaddress.IPv4Address(faker.ipv4())
+        super_pop_switch_site = super_pop_switch_site or site_subscription_factory()
+
+        product_id = subscriptions.get_product_id_by_name(ProductName.SUPER_POP_SWITCH)
+        super_pop_switch_subscription = SuperPopSwitchInactive.from_product_id(
+            product_id, customer_id=partner["partner_id"], insync=True
+        )
+        super_pop_switch_subscription.super_pop_switch.super_pop_switch_fqdn = super_pop_switch_fqdn
+        super_pop_switch_subscription.super_pop_switch.super_pop_switch_ts_port = super_pop_switch_ts_port
+        super_pop_switch_subscription.super_pop_switch.super_pop_switch_mgmt_ipv4_address = (
+            super_pop_switch_mgmt_ipv4_address
+        )
+        super_pop_switch_subscription.super_pop_switch.super_pop_switch_site = Site.from_subscription(
+            super_pop_switch_site
+        ).site
+        super_pop_switch_subscription.super_pop_switch.vendor = Vendor.NOKIA
+
+        super_pop_switch_subscription = SubscriptionModel.from_other_lifecycle(
+            super_pop_switch_subscription, SubscriptionLifecycle.ACTIVE
+        )
+        super_pop_switch_subscription.description = description
+        super_pop_switch_subscription.start_date = start_date
+
+        if status:
+            super_pop_switch_subscription.status = status
+
+        super_pop_switch_subscription.save()
+        db.session.commit()
+
+        return str(super_pop_switch_subscription.subscription_id)
+
+    return subscription_create
diff --git a/test/workflows/iptrunk/test_create_iptrunk.py b/test/workflows/iptrunk/test_create_iptrunk.py
index 38611a75e4f2ebf513fcd9875e8dac619dc1ff99..589fbabb9e9e918a6861af93a81827301a5627bb 100644
--- a/test/workflows/iptrunk/test_create_iptrunk.py
+++ b/test/workflows/iptrunk/test_create_iptrunk.py
@@ -3,7 +3,7 @@ from unittest.mock import patch
 
 import pytest
 
-from gso.products import Iptrunk, ProductType
+from gso.products import Iptrunk, ProductName
 from gso.products.product_blocks.iptrunk import IptrunkType, PhyPortCapacity
 from gso.services.subscriptions import get_product_id_by_name
 from gso.utils.helpers import LAGMember
@@ -116,7 +116,7 @@ def test_successful_iptrunk_creation_with_standard_lso_result(
     mock_create_host.return_value = None
     mock_allocate_v4_network.return_value = faker.ipv4_network(max_subnet=31)
     mock_allocate_v6_network.return_value = faker.ipv6_network(max_subnet=126)
-    product_id = get_product_id_by_name(ProductType.IP_TRUNK)
+    product_id = get_product_id_by_name(ProductName.IP_TRUNK)
     initial_site_data = [{"product": product_id}, *input_form_wizard_data]
     result, process_stat, step_log = run_workflow("create_iptrunk", initial_site_data)
 
@@ -162,7 +162,7 @@ def test_iptrunk_creation_fails_when_lso_return_code_is_one(
 ):
     mock_allocate_v4_network.return_value = faker.ipv4_network(max_subnet=31)
     mock_allocate_v6_network.return_value = faker.ipv6_network(max_subnet=126)
-    product_id = get_product_id_by_name(ProductType.IP_TRUNK)
+    product_id = get_product_id_by_name(ProductName.IP_TRUNK)
 
     initial_site_data = [{"product": product_id}, *input_form_wizard_data]
     result, process_stat, step_log = run_workflow("create_iptrunk", initial_site_data)
@@ -195,7 +195,7 @@ def test_successful_iptrunk_creation_with_juniper_interface_names(
     mock_create_host.return_value = None
     mock_allocate_v4_network.return_value = faker.ipv4_network(max_subnet=31)
     mock_allocate_v6_network.return_value = faker.ipv6_network(max_subnet=126)
-    product_id = get_product_id_by_name(ProductType.IP_TRUNK)
+    product_id = get_product_id_by_name(ProductName.IP_TRUNK)
     initial_site_data = [{"product": product_id}, *input_form_wizard_data]
     result, process_stat, step_log = run_workflow("create_iptrunk", initial_site_data)
 
diff --git a/test/workflows/router/test_create_router.py b/test/workflows/router/test_create_router.py
index a6d61729946e11249b8773599edfd3d023499dbb..33244ae5bbc17feef0dbb7c4abbc72823b058526 100644
--- a/test/workflows/router/test_create_router.py
+++ b/test/workflows/router/test_create_router.py
@@ -3,7 +3,7 @@ from unittest.mock import patch
 import pytest
 from infoblox_client import objects
 
-from gso.products import ProductType, Site
+from gso.products import ProductName, Site
 from gso.products.product_blocks.router import RouterRole
 from gso.products.product_types.router import Router
 from gso.services.subscriptions import get_product_id_by_name
@@ -51,7 +51,7 @@ def test_create_nokia_router_success(
     data_config_filename,
 ):
     #  Set up mock return values
-    product_id = get_product_id_by_name(ProductType.ROUTER)
+    product_id = get_product_id_by_name(ProductName.ROUTER)
     mock_site = Site.from_subscription(router_creation_input_form_data["router_site"]).site
     mock_v4 = faker.ipv4()
     mock_v6 = faker.ipv6()
@@ -169,7 +169,7 @@ def test_create_nokia_router_lso_failure(
     )
 
     #  Run workflow
-    product_id = get_product_id_by_name(ProductType.ROUTER)
+    product_id = get_product_id_by_name(ProductName.ROUTER)
     initial_router_data = [{"product": product_id}, router_creation_input_form_data]
     result, process_stat, step_log = run_workflow("create_router", initial_router_data)
 
diff --git a/test/workflows/router/test_modify_connection_stratey.py b/test/workflows/router/test_modify_connection_stratey.py
new file mode 100644
index 0000000000000000000000000000000000000000..669ad10fe6bfcecfa464b5115f4ec9adb180a8b2
--- /dev/null
+++ b/test/workflows/router/test_modify_connection_stratey.py
@@ -0,0 +1,23 @@
+import pytest
+
+from gso.products import Router
+from gso.utils.shared_enums import ConnectionStrategy
+from test.workflows import assert_complete, run_workflow
+
+
+@pytest.mark.workflow()
+def test_modify_connection_strategy(responses, nokia_router_subscription_factory):
+    subscription_id = nokia_router_subscription_factory(router_access_via_ts=True)
+    subscription = Router.from_subscription(subscription_id)
+    assert subscription.router.router_access_via_ts is True
+    form_data = [
+        {"subscription_id": subscription_id},
+        {"connection_strategy": ConnectionStrategy.IN_BAND},
+    ]
+    result, _, _ = run_workflow("modify_connection_strategy", form_data)
+    assert_complete(result)
+
+    # Fetch the updated subscription after running the workflow
+    updated_subscription = Router.from_subscription(subscription_id)
+    assert updated_subscription.status == "active"
+    assert updated_subscription.router.router_access_via_ts is False
diff --git a/test/workflows/router/test_update_ibgp_mesh.py b/test/workflows/router/test_update_ibgp_mesh.py
index b2f6756b8d7820e8e9dedd9f0f23b644eb86bb67..136a4a6196a5cf3cf124f79c481287138e22a50c 100644
--- a/test/workflows/router/test_update_ibgp_mesh.py
+++ b/test/workflows/router/test_update_ibgp_mesh.py
@@ -1,6 +1,7 @@
 from unittest.mock import patch
 
 import pytest
+from orchestrator.types import SubscriptionLifecycle
 from orchestrator.workflow import StepStatus
 from pydantic_forms.exceptions import FormValidationError
 
@@ -16,23 +17,22 @@ from test.workflows import (
 )
 
 
-@pytest.fixture()
-def ibgp_mesh_input_form_data(iptrunk_subscription_factory, faker):
-    ip_trunk = Iptrunk.from_subscription(iptrunk_subscription_factory())
-
-    return {"subscription_id": ip_trunk.iptrunk.iptrunk_sides[0].iptrunk_side_node.owner_subscription_id}
-
-
+@pytest.mark.parametrize("trunk_status", [SubscriptionLifecycle.PROVISIONING, SubscriptionLifecycle.ACTIVE])
 @pytest.mark.workflow()
 @patch("gso.workflows.router.update_ibgp_mesh.lso_client.execute_playbook")
 @patch("gso.workflows.router.update_ibgp_mesh.librenms_client.LibreNMSClient.add_device")
 def test_update_ibgp_mesh_success(
     mock_librenms_add_device,
     mock_execute_playbook,
-    ibgp_mesh_input_form_data,
+    trunk_status,
+    iptrunk_subscription_factory,
     data_config_filename,
     faker,
 ):
+    ip_trunk = Iptrunk.from_subscription(iptrunk_subscription_factory(status=trunk_status))
+    ibgp_mesh_input_form_data = {
+        "subscription_id": ip_trunk.iptrunk.iptrunk_sides[0].iptrunk_side_node.owner_subscription_id
+    }
     result, process_stat, step_log = run_workflow(
         "update_ibgp_mesh", [ibgp_mesh_input_form_data, {"tt_number": faker.tt_number()}]
     )
@@ -53,10 +53,23 @@ def test_update_ibgp_mesh_success(
     assert state["subscription"]["router"]["router_access_via_ts"] is False
 
 
+@pytest.mark.parametrize("trunk_status", [SubscriptionLifecycle.INITIAL, SubscriptionLifecycle.TERMINATED])
+@pytest.mark.workflow()
+def test_update_ibgp_mesh_failure(iptrunk_subscription_factory, data_config_filename, trunk_status):
+    ip_trunk = Iptrunk.from_subscription(iptrunk_subscription_factory(status=trunk_status))
+    ibgp_mesh_input_form_data = {
+        "subscription_id": ip_trunk.iptrunk.iptrunk_sides[0].iptrunk_side_node.owner_subscription_id
+    }
+
+    exception_message = "Selected router does not terminate any available IP trunks."
+    with pytest.raises(FormValidationError, match=exception_message):
+        run_workflow("update_ibgp_mesh", [ibgp_mesh_input_form_data, {}])
+
+
 @pytest.mark.workflow()
 def test_update_ibgp_mesh_isolated_router(nokia_router_subscription_factory, data_config_filename):
     router_id = nokia_router_subscription_factory(router_role=RouterRole.P)
 
-    exception_message = "Selected router does not terminate any active IP trunks."
+    exception_message = "Selected router does not terminate any available IP trunks."
     with pytest.raises(FormValidationError, match=exception_message):
         run_workflow("update_ibgp_mesh", [{"subscription_id": router_id}, {}])
diff --git a/test/workflows/shared/__init__.py b/test/workflows/shared/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/workflows/shared/cancel_subscription.py b/test/workflows/shared/cancel_subscription.py
new file mode 100644
index 0000000000000000000000000000000000000000..9963b8f21066cfd02408a819b8ce2ae5facf8cba
--- /dev/null
+++ b/test/workflows/shared/cancel_subscription.py
@@ -0,0 +1,30 @@
+import pytest
+from orchestrator.domain.base import SubscriptionModel
+from orchestrator.types import SubscriptionLifecycle
+
+from test.workflows import assert_complete, extract_state, run_workflow
+
+
+@pytest.mark.parametrize(
+    "subscription_factory",
+    [
+        "site_subscription_factory",
+        "juniper_router_subscription_factory",
+        "nokia_router_subscription_factory",
+        "iptrunk_subscription_factory",
+    ],
+)
+@pytest.mark.workflow()
+def test_cancel_workflow_success(subscription_factory, geant_partner, request):
+    subscription_id = request.getfixturevalue(subscription_factory)(
+        status=SubscriptionLifecycle.INITIAL,
+        partner=geant_partner,
+    )
+    initial_site_data = [{"subscription_id": subscription_id}, {}]
+    result, _, _ = run_workflow("cancel_subscription", initial_site_data)
+    assert_complete(result)
+
+    state = extract_state(result)
+    subscription_id = state["subscription_id"]
+    subscription = SubscriptionModel.from_subscription(subscription_id)
+    assert subscription.status == "terminated"
diff --git a/test/workflows/site/test_create_site.py b/test/workflows/site/test_create_site.py
index 5909b5db9d19fca60d7815befd1a60d7d3676086..e31576152634045e9efe57b864a51785495a41d1 100644
--- a/test/workflows/site/test_create_site.py
+++ b/test/workflows/site/test_create_site.py
@@ -1,7 +1,7 @@
 import pytest
 from pydantic_forms.exceptions import FormValidationError
 
-from gso.products import ProductType
+from gso.products import ProductName
 from gso.products.product_blocks.site import SiteTier
 from gso.products.product_types.site import Site
 from gso.services.partners import get_partner_by_name
@@ -11,7 +11,7 @@ from test.workflows import assert_complete, extract_state, run_workflow
 
 @pytest.mark.workflow()
 def test_create_site(responses, faker):
-    product_id = get_product_id_by_name(ProductType.SITE)
+    product_id = get_product_id_by_name(ProductName.SITE)
     initial_site_data = [
         {"product": product_id},
         {
@@ -51,7 +51,7 @@ def test_site_name_is_incorrect(responses, faker):
     """
     invalid_site_name = "AMST10"
     expected_exception_msg = rf".*Enter a valid site name.+Received: {invalid_site_name}.*"
-    product_id = get_product_id_by_name(ProductType.SITE)
+    product_id = get_product_id_by_name(ProductName.SITE)
     initial_site_data = [
         {"product": product_id},
         {