Commit 14c4b6b18a8 for woocommerce

commit 14c4b6b18a83ff687b413f15579fdc5250fbcd1d
Author: daledupreez <dale@automattic.com>
Date:   Thu Jun 18 16:24:00 2026 +0200

    Add woocommerce-subscriptions-engine package (#65767)

    * Initial shell for subscriptions-engine package
    * Implement core entities and basic scripts
    * Remove older repo reference
    * Remove DROP TABLE commands from uninstall
    * Update integration tests to reference renamed function
    * Ensure unit tests and integration tests work; add wp-env as a dev dependency
    * Remove ON_HOLD => EXPIRED transition
    * Update PHP namespace and package to Automattic/WooCommerce/SubscriptionsEngine
    * Rename package to woocommerce-subscriptions-engine
    * Fix directory for test:integration script
    * Add entry to CODEOWNERS
    * Validate incoming status and schedule_source values
    * Rename owner to extension_slug
    * Add validation for starting_cycle
    * Add validation for min_cycles and max_cycles
    * Add validation/normalization for trial_duration in billing policy
    * Add checks for delivery policy anchors
    * Require positive order ID
    * Add defensive code to Contract::create()
    * Defensive code against invalid data in Pricing_Policy::from_array()
    * Make bootstrap more robust against the 'init' action having run already
    * Add guard against previously defined constant
    * Add transition tests for EXPIRED status
    * Remove lingering software reference in license.txt
    * Remove lingering PHPCS exclusion that is not needed
    * Update PHPUnit configuration to be more precise
    * Add ABSPATH guard in subscriptions-engine.php
    * Rename top-level file to match package name
    * Linting fixes
    * Fix package name in package.json

diff --git a/CODEOWNERS b/CODEOWNERS
index 5d194a8f09a..53a0087b18a 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -47,3 +47,6 @@
 # Email Editor integrations
 /plugins/woocommerce/src/Internal/EmailEditor/ @woocommerce/ballade
 /plugins/woocommerce/client/admin/client/wp-admin-scripts/email-editor-integration/ @woocommerce/ballade
+
+# Subscriptions Engine packages
+/packages/php/woocommerce-subscriptions-engine/ @woocommerce/chronos
\ No newline at end of file
diff --git a/packages/php/woocommerce-subscriptions-engine/.wp-env.json b/packages/php/woocommerce-subscriptions-engine/.wp-env.json
new file mode 100644
index 00000000000..1ac6588b11f
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/.wp-env.json
@@ -0,0 +1,28 @@
+{
+	"phpVersion": "8.3",
+	"plugins": [
+		"."
+	],
+	"config": {
+		"JETPACK_AUTOLOAD_DEV": true,
+		"WP_DEBUG_LOG": true,
+		"WP_DEBUG_DISPLAY": true,
+		"ALTERNATE_WP_CRON": true
+	},
+	"mappings": {
+	},
+	"env": {
+		"development": {
+			"port": 8883
+		},
+		"tests": {
+			"port": 8083,
+			"plugins": [
+				"."
+			],
+			"themes": [],
+			"config": {
+			}
+		}
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/README.md b/packages/php/woocommerce-subscriptions-engine/README.md
new file mode 100644
index 00000000000..b968921ef3a
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/README.md
@@ -0,0 +1,3 @@
+# WooCommerce Subscriptions Engine
+
+This package implements a general-purpose subscriptions engine for WooCommerce.
diff --git a/packages/php/woocommerce-subscriptions-engine/changelog/.gitkeep b/packages/php/woocommerce-subscriptions-engine/changelog/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/packages/php/woocommerce-subscriptions-engine/composer.json b/packages/php/woocommerce-subscriptions-engine/composer.json
new file mode 100644
index 00000000000..9ab2e1f46a8
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/composer.json
@@ -0,0 +1,69 @@
+{
+	"name": "automattic/woocommerce-subscriptions-engine",
+	"description": "Subscriptions engine for WooCommerce.",
+	"type": "wordpress-plugin",
+	"license": "GPL-2.0-or-later",
+	"prefer-stable": true,
+	"minimum-stability": "dev",
+	"version": "0.0.1",
+	"autoload": {
+		"classmap": [
+			"src/"
+		]
+	},
+	"autoload-dev": {
+		"classmap": [
+			"tests/unit/",
+			"tests/integration/"
+		]
+	},
+	"require": {
+		"php": ">=7.4"
+	},
+	"require-dev": {
+		"automattic/jetpack-changelogger": "3.3.0",
+		"phpunit/phpunit": "^9.6",
+		"woocommerce/woocommerce-sniffs": "1.0.0",
+		"yoast/phpunit-polyfills": "^4.0"
+	},
+	"config": {
+		"platform": {
+			"php": "7.4"
+		},
+		"allow-plugins": {
+			"dealerdirect/phpcodesniffer-composer-installer": true
+		}
+	},
+	"scripts": {
+		"post-install-cmd": [
+			"composer dump-autoload -o"
+		],
+		"post-update-cmd": [
+			"composer dump-autoload -o"
+		],
+		"env:start": "npx wp-env start",
+		"env:stop": "npx wp-env stop",
+		"env:destroy": "npx wp-env destroy",
+		"test:unit": "phpunit",
+		"test:integration": "npx wp-env run tests-cli --env-cwd=wp-content/plugins/woocommerce-subscriptions-engine ./vendor/bin/phpunit --configuration phpunit-integration.xml.dist",
+		"phpcs": "phpcs -s -p",
+		"phpcbf": "phpcbf -p"
+	},
+	"extra": {
+		"changelogger": {
+			"formatter": {
+				"filename": "../../../tools/changelogger/class-php-package-formatter.php"
+			},
+			"types": {
+				"fix": "Fixes an existing bug",
+				"add": "Adds functionality",
+				"update": "Update existing functionality",
+				"dev": "Development related task",
+				"tweak": "A minor adjustment to the codebase",
+				"performance": "Address performance issues",
+				"enhancement": "Improve existing functionality"
+			},
+			"changelog": "changelog.md"
+		}
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/composer.lock b/packages/php/woocommerce-subscriptions-engine/composer.lock
new file mode 100644
index 00000000000..be315b9902f
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/composer.lock
@@ -0,0 +1,3612 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "c99e9fbb764865f370ade0b553c9729f",
+    "packages": [],
+    "packages-dev": [
+        {
+            "name": "automattic/jetpack-changelogger",
+            "version": "v3.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Automattic/jetpack-changelogger.git",
+                "reference": "8f63c829b8d1b0d7b1d5de93510d78523ed18959"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Automattic/jetpack-changelogger/zipball/8f63c829b8d1b0d7b1d5de93510d78523ed18959",
+                "reference": "8f63c829b8d1b0d7b1d5de93510d78523ed18959",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6",
+                "symfony/console": "^3.4 || ^5.2 || ^6.0",
+                "symfony/process": "^3.4 || ^5.2 || ^6.0",
+                "wikimedia/at-ease": "^1.2 || ^2.0"
+            },
+            "require-dev": {
+                "wikimedia/testing-access-wrapper": "^1.0 || ^2.0",
+                "yoast/phpunit-polyfills": "1.0.4"
+            },
+            "bin": [
+                "bin/changelogger"
+            ],
+            "type": "project",
+            "extra": {
+                "autotagger": true,
+                "mirror-repo": "Automattic/jetpack-changelogger",
+                "branch-alias": {
+                    "dev-trunk": "3.3.x-dev"
+                },
+                "changelogger": {
+                    "link-template": "https://github.com/Automattic/jetpack-changelogger/compare/${old}...${new}"
+                },
+                "version-constants": {
+                    "::VERSION": "src/Application.php"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Automattic\\Jetpack\\Changelog\\": "lib",
+                    "Automattic\\Jetpack\\Changelogger\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "GPL-2.0-or-later"
+            ],
+            "description": "Jetpack Changelogger tool. Allows for managing changelogs by dropping change files into a changelog directory with each PR.",
+            "support": {
+                "source": "https://github.com/Automattic/jetpack-changelogger/tree/v3.3.0"
+            },
+            "time": "2022-12-26T13:49:01+00:00"
+        },
+        {
+            "name": "dealerdirect/phpcodesniffer-composer-installer",
+            "version": "v1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPCSStandards/composer-installer.git",
+                "reference": "963f0c67bffde0eac41b56be71ac0e8ba132f0bd"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/963f0c67bffde0eac41b56be71ac0e8ba132f0bd",
+                "reference": "963f0c67bffde0eac41b56be71ac0e8ba132f0bd",
+                "shasum": ""
+            },
+            "require": {
+                "composer-plugin-api": "^2.2",
+                "php": ">=5.4",
+                "squizlabs/php_codesniffer": "^3.1.0 || ^4.0"
+            },
+            "require-dev": {
+                "composer/composer": "^2.2",
+                "ext-json": "*",
+                "ext-zip": "*",
+                "php-parallel-lint/php-parallel-lint": "^1.4.0",
+                "phpcompatibility/php-compatibility": "^9.0 || ^10.0.0@dev",
+                "yoast/phpunit-polyfills": "^1.0"
+            },
+            "type": "composer-plugin",
+            "extra": {
+                "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
+            },
+            "autoload": {
+                "psr-4": {
+                    "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Franck Nijhof",
+                    "email": "opensource@frenck.dev",
+                    "homepage": "https://frenck.dev",
+                    "role": "Open source developer"
+                },
+                {
+                    "name": "Contributors",
+                    "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors"
+                }
+            ],
+            "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
+            "keywords": [
+                "PHPCodeSniffer",
+                "PHP_CodeSniffer",
+                "code quality",
+                "codesniffer",
+                "composer",
+                "installer",
+                "phpcbf",
+                "phpcs",
+                "plugin",
+                "qa",
+                "quality",
+                "standard",
+                "standards",
+                "style guide",
+                "stylecheck",
+                "tests"
+            ],
+            "support": {
+                "issues": "https://github.com/PHPCSStandards/composer-installer/issues",
+                "security": "https://github.com/PHPCSStandards/composer-installer/security/policy",
+                "source": "https://github.com/PHPCSStandards/composer-installer"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/PHPCSStandards",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/jrfnl",
+                    "type": "github"
+                },
+                {
+                    "url": "https://opencollective.com/php_codesniffer",
+                    "type": "open_collective"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/phpcsstandards",
+                    "type": "thanks_dev"
+                }
+            ],
+            "time": "2026-05-06T08:26:05+00:00"
+        },
+        {
+            "name": "doctrine/instantiator",
+            "version": "1.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/instantiator.git",
+                "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b",
+                "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1 || ^8.0"
+            },
+            "require-dev": {
+                "doctrine/coding-standard": "^9 || ^11",
+                "ext-pdo": "*",
+                "ext-phar": "*",
+                "phpbench/phpbench": "^0.16 || ^1",
+                "phpstan/phpstan": "^1.4",
+                "phpstan/phpstan-phpunit": "^1",
+                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+                "vimeo/psalm": "^4.30 || ^5.4"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com",
+                    "homepage": "https://ocramius.github.io/"
+                }
+            ],
+            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+            "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
+            "keywords": [
+                "constructor",
+                "instantiate"
+            ],
+            "support": {
+                "issues": "https://github.com/doctrine/instantiator/issues",
+                "source": "https://github.com/doctrine/instantiator/tree/1.5.0"
+            },
+            "funding": [
+                {
+                    "url": "https://www.doctrine-project.org/sponsorship.html",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://www.patreon.com/phpdoctrine",
+                    "type": "patreon"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-12-30T00:15:36+00:00"
+        },
+        {
+            "name": "myclabs/deep-copy",
+            "version": "1.13.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/myclabs/DeepCopy.git",
+                "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+                "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1 || ^8.0"
+            },
+            "conflict": {
+                "doctrine/collections": "<1.6.8",
+                "doctrine/common": "<2.13.3 || >=3 <3.2.2"
+            },
+            "require-dev": {
+                "doctrine/collections": "^1.6.8",
+                "doctrine/common": "^2.13.3 || ^3.2.2",
+                "phpspec/prophecy": "^1.10",
+                "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/DeepCopy/deep_copy.php"
+                ],
+                "psr-4": {
+                    "DeepCopy\\": "src/DeepCopy/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Create deep copies (clones) of your objects",
+            "keywords": [
+                "clone",
+                "copy",
+                "duplicate",
+                "object",
+                "object graph"
+            ],
+            "support": {
+                "issues": "https://github.com/myclabs/DeepCopy/issues",
+                "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
+            },
+            "funding": [
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-08-01T08:46:24+00:00"
+        },
+        {
+            "name": "nikic/php-parser",
+            "version": "v5.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nikic/PHP-Parser.git",
+                "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82",
+                "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82",
+                "shasum": ""
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-json": "*",
+                "ext-tokenizer": "*",
+                "php": ">=7.4"
+            },
+            "require-dev": {
+                "ircmaxell/php-yacc": "^0.0.7",
+                "phpunit/phpunit": "^9.0"
+            },
+            "bin": [
+                "bin/php-parse"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "PhpParser\\": "lib/PhpParser"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Nikita Popov"
+                }
+            ],
+            "description": "A PHP parser written in PHP",
+            "keywords": [
+                "parser",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/nikic/PHP-Parser/issues",
+                "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0"
+            },
+            "time": "2025-12-06T11:56:16+00:00"
+        },
+        {
+            "name": "phar-io/manifest",
+            "version": "2.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phar-io/manifest.git",
+                "reference": "54750ef60c58e43759730615a392c31c80e23176"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+                "reference": "54750ef60c58e43759730615a392c31c80e23176",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-libxml": "*",
+                "ext-phar": "*",
+                "ext-xmlwriter": "*",
+                "phar-io/version": "^3.0.1",
+                "php": "^7.2 || ^8.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Heuer",
+                    "email": "sebastian@phpeople.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+            "support": {
+                "issues": "https://github.com/phar-io/manifest/issues",
+                "source": "https://github.com/phar-io/manifest/tree/2.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/theseer",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-03-03T12:33:53+00:00"
+        },
+        {
+            "name": "phar-io/version",
+            "version": "3.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phar-io/version.git",
+                "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+                "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2 || ^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Heuer",
+                    "email": "sebastian@phpeople.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Library for handling version information and constraints",
+            "support": {
+                "issues": "https://github.com/phar-io/version/issues",
+                "source": "https://github.com/phar-io/version/tree/3.2.1"
+            },
+            "time": "2022-02-21T01:04:05+00:00"
+        },
+        {
+            "name": "phpcompatibility/php-compatibility",
+            "version": "9.3.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
+                "reference": "9fb324479acf6f39452e0655d2429cc0d3914243"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243",
+                "reference": "9fb324479acf6f39452e0655d2429cc0d3914243",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3",
+                "squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
+            },
+            "conflict": {
+                "squizlabs/php_codesniffer": "2.6.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
+            },
+            "suggest": {
+                "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
+                "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+            },
+            "type": "phpcodesniffer-standard",
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-3.0-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "Wim Godden",
+                    "homepage": "https://github.com/wimg",
+                    "role": "lead"
+                },
+                {
+                    "name": "Juliette Reinders Folmer",
+                    "homepage": "https://github.com/jrfnl",
+                    "role": "lead"
+                },
+                {
+                    "name": "Contributors",
+                    "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors"
+                }
+            ],
+            "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.",
+            "homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
+            "keywords": [
+                "compatibility",
+                "phpcs",
+                "standards"
+            ],
+            "support": {
+                "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues",
+                "source": "https://github.com/PHPCompatibility/PHPCompatibility"
+            },
+            "time": "2019-12-27T09:44:58+00:00"
+        },
+        {
+            "name": "phpcompatibility/phpcompatibility-paragonie",
+            "version": "1.3.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git",
+                "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/244d7b04fc4bc2117c15f5abe23eb933b5f02bbf",
+                "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf",
+                "shasum": ""
+            },
+            "require": {
+                "phpcompatibility/php-compatibility": "^9.0"
+            },
+            "require-dev": {
+                "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+                "paragonie/random_compat": "dev-master",
+                "paragonie/sodium_compat": "dev-master"
+            },
+            "suggest": {
+                "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
+                "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+            },
+            "type": "phpcodesniffer-standard",
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-3.0-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "Wim Godden",
+                    "role": "lead"
+                },
+                {
+                    "name": "Juliette Reinders Folmer",
+                    "role": "lead"
+                }
+            ],
+            "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.",
+            "homepage": "http://phpcompatibility.com/",
+            "keywords": [
+                "compatibility",
+                "paragonie",
+                "phpcs",
+                "polyfill",
+                "standards",
+                "static analysis"
+            ],
+            "support": {
+                "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues",
+                "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy",
+                "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/PHPCompatibility",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/jrfnl",
+                    "type": "github"
+                },
+                {
+                    "url": "https://opencollective.com/php_codesniffer",
+                    "type": "open_collective"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/phpcompatibility",
+                    "type": "thanks_dev"
+                }
+            ],
+            "time": "2025-09-19T17:43:28+00:00"
+        },
+        {
+            "name": "phpcompatibility/phpcompatibility-wp",
+            "version": "2.1.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git",
+                "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/7c8d18b4d90dac9e86b0869a608fa09158e168fa",
+                "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa",
+                "shasum": ""
+            },
+            "require": {
+                "phpcompatibility/php-compatibility": "^9.0",
+                "phpcompatibility/phpcompatibility-paragonie": "^1.0",
+                "squizlabs/php_codesniffer": "^3.3"
+            },
+            "require-dev": {
+                "dealerdirect/phpcodesniffer-composer-installer": "^1.0"
+            },
+            "suggest": {
+                "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
+                "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+            },
+            "type": "phpcodesniffer-standard",
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-3.0-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "Wim Godden",
+                    "role": "lead"
+                },
+                {
+                    "name": "Juliette Reinders Folmer",
+                    "role": "lead"
+                }
+            ],
+            "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.",
+            "homepage": "http://phpcompatibility.com/",
+            "keywords": [
+                "compatibility",
+                "phpcs",
+                "standards",
+                "static analysis",
+                "wordpress"
+            ],
+            "support": {
+                "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues",
+                "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy",
+                "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/PHPCompatibility",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/jrfnl",
+                    "type": "github"
+                },
+                {
+                    "url": "https://opencollective.com/php_codesniffer",
+                    "type": "open_collective"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/phpcompatibility",
+                    "type": "thanks_dev"
+                }
+            ],
+            "time": "2025-10-18T00:05:59+00:00"
+        },
+        {
+            "name": "phpcsstandards/phpcsextra",
+            "version": "1.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPCSStandards/PHPCSExtra.git",
+                "reference": "b598aa890815b8df16363271b659d73280129101"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/b598aa890815b8df16363271b659d73280129101",
+                "reference": "b598aa890815b8df16363271b659d73280129101",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4",
+                "phpcsstandards/phpcsutils": "^1.2.0",
+                "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1"
+            },
+            "require-dev": {
+                "php-parallel-lint/php-console-highlighter": "^1.0",
+                "php-parallel-lint/php-parallel-lint": "^1.4.0",
+                "phpcsstandards/phpcsdevcs": "^1.2.0",
+                "phpcsstandards/phpcsdevtools": "^1.2.1",
+                "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
+            },
+            "type": "phpcodesniffer-standard",
+            "extra": {
+                "branch-alias": {
+                    "dev-stable": "1.x-dev",
+                    "dev-develop": "1.x-dev"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-3.0-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "Juliette Reinders Folmer",
+                    "homepage": "https://github.com/jrfnl",
+                    "role": "lead"
+                },
+                {
+                    "name": "Contributors",
+                    "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors"
+                }
+            ],
+            "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.",
+            "keywords": [
+                "PHP_CodeSniffer",
+                "phpcbf",
+                "phpcodesniffer-standard",
+                "phpcs",
+                "standards",
+                "static analysis"
+            ],
+            "support": {
+                "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues",
+                "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy",
+                "source": "https://github.com/PHPCSStandards/PHPCSExtra"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/PHPCSStandards",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/jrfnl",
+                    "type": "github"
+                },
+                {
+                    "url": "https://opencollective.com/php_codesniffer",
+                    "type": "open_collective"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/phpcsstandards",
+                    "type": "thanks_dev"
+                }
+            ],
+            "time": "2025-11-12T23:06:57+00:00"
+        },
+        {
+            "name": "phpcsstandards/phpcsutils",
+            "version": "1.2.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPCSStandards/PHPCSUtils.git",
+                "reference": "c216317e96c8b3f5932808f9b0f1f7a14e3bbf55"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/c216317e96c8b3f5932808f9b0f1f7a14e3bbf55",
+                "reference": "c216317e96c8b3f5932808f9b0f1f7a14e3bbf55",
+                "shasum": ""
+            },
+            "require": {
+                "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0",
+                "php": ">=5.4",
+                "squizlabs/php_codesniffer": "^3.13.5 || ^4.0.1"
+            },
+            "require-dev": {
+                "ext-filter": "*",
+                "php-parallel-lint/php-console-highlighter": "^1.0",
+                "php-parallel-lint/php-parallel-lint": "^1.4.0",
+                "phpcsstandards/phpcsdevcs": "^1.2.0",
+                "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0 || ^3.0.0"
+            },
+            "type": "phpcodesniffer-standard",
+            "extra": {
+                "branch-alias": {
+                    "dev-stable": "1.x-dev",
+                    "dev-develop": "1.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "PHPCSUtils/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-3.0-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "Juliette Reinders Folmer",
+                    "homepage": "https://github.com/jrfnl",
+                    "role": "lead"
+                },
+                {
+                    "name": "Contributors",
+                    "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors"
+                }
+            ],
+            "description": "A suite of utility functions for use with PHP_CodeSniffer",
+            "homepage": "https://phpcsutils.com/",
+            "keywords": [
+                "PHP_CodeSniffer",
+                "phpcbf",
+                "phpcodesniffer-standard",
+                "phpcs",
+                "phpcs3",
+                "phpcs4",
+                "standards",
+                "static analysis",
+                "tokens",
+                "utility"
+            ],
+            "support": {
+                "docs": "https://phpcsutils.com/",
+                "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues",
+                "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy",
+                "source": "https://github.com/PHPCSStandards/PHPCSUtils"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/PHPCSStandards",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/jrfnl",
+                    "type": "github"
+                },
+                {
+                    "url": "https://opencollective.com/php_codesniffer",
+                    "type": "open_collective"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/phpcsstandards",
+                    "type": "thanks_dev"
+                }
+            ],
+            "time": "2025-12-08T14:27:58+00:00"
+        },
+        {
+            "name": "phpunit/php-code-coverage",
+            "version": "9.2.32",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+                "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5",
+                "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-libxml": "*",
+                "ext-xmlwriter": "*",
+                "nikic/php-parser": "^4.19.1 || ^5.1.0",
+                "php": ">=7.3",
+                "phpunit/php-file-iterator": "^3.0.6",
+                "phpunit/php-text-template": "^2.0.4",
+                "sebastian/code-unit-reverse-lookup": "^2.0.3",
+                "sebastian/complexity": "^2.0.3",
+                "sebastian/environment": "^5.1.5",
+                "sebastian/lines-of-code": "^1.0.4",
+                "sebastian/version": "^3.0.2",
+                "theseer/tokenizer": "^1.2.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.6"
+            },
+            "suggest": {
+                "ext-pcov": "PHP extension that provides line coverage",
+                "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "9.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+            "keywords": [
+                "coverage",
+                "testing",
+                "xunit"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+                "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
+                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-08-22T04:23:01+00:00"
+        },
+        {
+            "name": "phpunit/php-file-iterator",
+            "version": "3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+                "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
+                "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+            "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+            "keywords": [
+                "filesystem",
+                "iterator"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+                "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2021-12-02T12:48:52+00:00"
+        },
+        {
+            "name": "phpunit/php-invoker",
+            "version": "3.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-invoker.git",
+                "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+                "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "ext-pcntl": "*",
+                "phpunit/phpunit": "^9.3"
+            },
+            "suggest": {
+                "ext-pcntl": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.1-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Invoke callables with a timeout",
+            "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+            "keywords": [
+                "process"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+                "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-09-28T05:58:55+00:00"
+        },
+        {
+            "name": "phpunit/php-text-template",
+            "version": "2.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-text-template.git",
+                "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+                "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Simple template engine.",
+            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+            "keywords": [
+                "template"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+                "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T05:33:50+00:00"
+        },
+        {
+            "name": "phpunit/php-timer",
+            "version": "5.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-timer.git",
+                "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+                "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Utility class for timing",
+            "homepage": "https://github.com/sebastianbergmann/php-timer/",
+            "keywords": [
+                "timer"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+                "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T13:16:10+00:00"
+        },
+        {
+            "name": "phpunit/phpunit",
+            "version": "9.6.34",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit.git",
+                "reference": "b36f02317466907a230d3aa1d34467041271ef4a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b36f02317466907a230d3aa1d34467041271ef4a",
+                "reference": "b36f02317466907a230d3aa1d34467041271ef4a",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.5.0 || ^2",
+                "ext-dom": "*",
+                "ext-json": "*",
+                "ext-libxml": "*",
+                "ext-mbstring": "*",
+                "ext-xml": "*",
+                "ext-xmlwriter": "*",
+                "myclabs/deep-copy": "^1.13.4",
+                "phar-io/manifest": "^2.0.4",
+                "phar-io/version": "^3.2.1",
+                "php": ">=7.3",
+                "phpunit/php-code-coverage": "^9.2.32",
+                "phpunit/php-file-iterator": "^3.0.6",
+                "phpunit/php-invoker": "^3.1.1",
+                "phpunit/php-text-template": "^2.0.4",
+                "phpunit/php-timer": "^5.0.3",
+                "sebastian/cli-parser": "^1.0.2",
+                "sebastian/code-unit": "^1.0.8",
+                "sebastian/comparator": "^4.0.10",
+                "sebastian/diff": "^4.0.6",
+                "sebastian/environment": "^5.1.5",
+                "sebastian/exporter": "^4.0.8",
+                "sebastian/global-state": "^5.0.8",
+                "sebastian/object-enumerator": "^4.0.4",
+                "sebastian/resource-operations": "^3.0.4",
+                "sebastian/type": "^3.2.1",
+                "sebastian/version": "^3.0.2"
+            },
+            "suggest": {
+                "ext-soap": "To be able to generate mocks based on WSDL files",
+                "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+            },
+            "bin": [
+                "phpunit"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "9.6-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/Framework/Assert/Functions.php"
+                ],
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "The PHP Unit Testing framework.",
+            "homepage": "https://phpunit.de/",
+            "keywords": [
+                "phpunit",
+                "testing",
+                "xunit"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+                "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.34"
+            },
+            "funding": [
+                {
+                    "url": "https://phpunit.de/sponsors.html",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-01-27T05:45:00+00:00"
+        },
+        {
+            "name": "psr/container",
+            "version": "1.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/container.git",
+                "reference": "513e0666f7216c7459170d56df27dfcefe1689ea"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea",
+                "reference": "513e0666f7216c7459170d56df27dfcefe1689ea",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.4.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Container\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common Container Interface (PHP FIG PSR-11)",
+            "homepage": "https://github.com/php-fig/container",
+            "keywords": [
+                "PSR-11",
+                "container",
+                "container-interface",
+                "container-interop",
+                "psr"
+            ],
+            "support": {
+                "issues": "https://github.com/php-fig/container/issues",
+                "source": "https://github.com/php-fig/container/tree/1.1.2"
+            },
+            "time": "2021-11-05T16:50:12+00:00"
+        },
+        {
+            "name": "sebastian/cli-parser",
+            "version": "1.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/cli-parser.git",
+                "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
+                "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library for parsing CLI options",
+            "homepage": "https://github.com/sebastianbergmann/cli-parser",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+                "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-03-02T06:27:43+00:00"
+        },
+        {
+            "name": "sebastian/code-unit",
+            "version": "1.0.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/code-unit.git",
+                "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120",
+                "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Collection of value objects that represent the PHP code units",
+            "homepage": "https://github.com/sebastianbergmann/code-unit",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+                "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T13:08:54+00:00"
+        },
+        {
+            "name": "sebastian/code-unit-reverse-lookup",
+            "version": "2.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+                "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+                "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Looks up which function or method a line of code belongs to",
+            "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+                "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-09-28T05:30:19+00:00"
+        },
+        {
+            "name": "sebastian/comparator",
+            "version": "4.0.10",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/comparator.git",
+                "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d",
+                "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3",
+                "sebastian/diff": "^4.0",
+                "sebastian/exporter": "^4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                }
+            ],
+            "description": "Provides the functionality to compare PHP values for equality",
+            "homepage": "https://github.com/sebastianbergmann/comparator",
+            "keywords": [
+                "comparator",
+                "compare",
+                "equality"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/comparator/issues",
+                "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-01-24T09:22:56+00:00"
+        },
+        {
+            "name": "sebastian/complexity",
+            "version": "2.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/complexity.git",
+                "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a",
+                "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a",
+                "shasum": ""
+            },
+            "require": {
+                "nikic/php-parser": "^4.18 || ^5.0",
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library for calculating the complexity of PHP code units",
+            "homepage": "https://github.com/sebastianbergmann/complexity",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/complexity/issues",
+                "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2023-12-22T06:19:30+00:00"
+        },
+        {
+            "name": "sebastian/diff",
+            "version": "4.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/diff.git",
+                "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc",
+                "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3",
+                "symfony/process": "^4.2 || ^5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Kore Nordmann",
+                    "email": "mail@kore-nordmann.de"
+                }
+            ],
+            "description": "Diff implementation",
+            "homepage": "https://github.com/sebastianbergmann/diff",
+            "keywords": [
+                "diff",
+                "udiff",
+                "unidiff",
+                "unified diff"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/diff/issues",
+                "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-03-02T06:30:58+00:00"
+        },
+        {
+            "name": "sebastian/environment",
+            "version": "5.1.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/environment.git",
+                "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
+                "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "suggest": {
+                "ext-posix": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.1-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides functionality to handle HHVM/PHP environments",
+            "homepage": "http://www.github.com/sebastianbergmann/environment",
+            "keywords": [
+                "Xdebug",
+                "environment",
+                "hhvm"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/environment/issues",
+                "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2023-02-03T06:03:51+00:00"
+        },
+        {
+            "name": "sebastian/exporter",
+            "version": "4.0.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/exporter.git",
+                "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c",
+                "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3",
+                "sebastian/recursion-context": "^4.0"
+            },
+            "require-dev": {
+                "ext-mbstring": "*",
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@gmail.com"
+                }
+            ],
+            "description": "Provides the functionality to export PHP variables for visualization",
+            "homepage": "https://www.github.com/sebastianbergmann/exporter",
+            "keywords": [
+                "export",
+                "exporter"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/exporter/issues",
+                "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-09-24T06:03:27+00:00"
+        },
+        {
+            "name": "sebastian/global-state",
+            "version": "5.0.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/global-state.git",
+                "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6",
+                "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3",
+                "sebastian/object-reflector": "^2.0",
+                "sebastian/recursion-context": "^4.0"
+            },
+            "require-dev": {
+                "ext-dom": "*",
+                "phpunit/phpunit": "^9.3"
+            },
+            "suggest": {
+                "ext-uopz": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Snapshotting of global state",
+            "homepage": "http://www.github.com/sebastianbergmann/global-state",
+            "keywords": [
+                "global state"
+            ],
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/global-state/issues",
+                "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-08-10T07:10:35+00:00"
+        },
+        {
+            "name": "sebastian/lines-of-code",
+            "version": "1.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+                "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5",
+                "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5",
+                "shasum": ""
+            },
+            "require": {
+                "nikic/php-parser": "^4.18 || ^5.0",
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library for counting the lines of code in PHP source code",
+            "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+                "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2023-12-22T06:20:34+00:00"
+        },
+        {
+            "name": "sebastian/object-enumerator",
+            "version": "4.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+                "reference": "5c9eeac41b290a3712d88851518825ad78f45c71"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71",
+                "reference": "5c9eeac41b290a3712d88851518825ad78f45c71",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3",
+                "sebastian/object-reflector": "^2.0",
+                "sebastian/recursion-context": "^4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+            "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+                "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T13:12:34+00:00"
+        },
+        {
+            "name": "sebastian/object-reflector",
+            "version": "2.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/object-reflector.git",
+                "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+                "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Allows reflection of object attributes, including inherited and non-public ones",
+            "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+                "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T13:14:26+00:00"
+        },
+        {
+            "name": "sebastian/recursion-context",
+            "version": "4.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/recursion-context.git",
+                "reference": "539c6691e0623af6dc6f9c20384c120f963465a0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0",
+                "reference": "539c6691e0623af6dc6f9c20384c120f963465a0",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides functionality to recursively process PHP variables",
+            "homepage": "https://github.com/sebastianbergmann/recursion-context",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+                "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                },
+                {
+                    "url": "https://liberapay.com/sebastianbergmann",
+                    "type": "liberapay"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/sebastianbergmann",
+                    "type": "thanks_dev"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-08-10T06:57:39+00:00"
+        },
+        {
+            "name": "sebastian/resource-operations",
+            "version": "3.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/resource-operations.git",
+                "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e",
+                "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides a list of PHP built-in functions that operate on resources",
+            "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+            "support": {
+                "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2024-03-14T16:00:52+00:00"
+        },
+        {
+            "name": "sebastian/type",
+            "version": "3.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/type.git",
+                "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
+                "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.2-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Collection of value objects that represent the types of the PHP type system",
+            "homepage": "https://github.com/sebastianbergmann/type",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/type/issues",
+                "source": "https://github.com/sebastianbergmann/type/tree/3.2.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2023-02-03T06:13:03+00:00"
+        },
+        {
+            "name": "sebastian/version",
+            "version": "3.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/version.git",
+                "reference": "c6c1022351a901512170118436c764e473f6de8c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c",
+                "reference": "c6c1022351a901512170118436c764e473f6de8c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+            "homepage": "https://github.com/sebastianbergmann/version",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/version/issues",
+                "source": "https://github.com/sebastianbergmann/version/tree/3.0.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-09-28T06:39:44+00:00"
+        },
+        {
+            "name": "squizlabs/php_codesniffer",
+            "version": "3.13.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
+                "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4",
+                "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4",
+                "shasum": ""
+            },
+            "require": {
+                "ext-simplexml": "*",
+                "ext-tokenizer": "*",
+                "ext-xmlwriter": "*",
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4"
+            },
+            "bin": [
+                "bin/phpcbf",
+                "bin/phpcs"
+            ],
+            "type": "library",
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Greg Sherwood",
+                    "role": "Former lead"
+                },
+                {
+                    "name": "Juliette Reinders Folmer",
+                    "role": "Current lead"
+                },
+                {
+                    "name": "Contributors",
+                    "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors"
+                }
+            ],
+            "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+            "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
+            "keywords": [
+                "phpcs",
+                "standards",
+                "static analysis"
+            ],
+            "support": {
+                "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues",
+                "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy",
+                "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
+                "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/PHPCSStandards",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/jrfnl",
+                    "type": "github"
+                },
+                {
+                    "url": "https://opencollective.com/php_codesniffer",
+                    "type": "open_collective"
+                },
+                {
+                    "url": "https://thanks.dev/u/gh/phpcsstandards",
+                    "type": "thanks_dev"
+                }
+            ],
+            "time": "2025-11-04T16:30:35+00:00"
+        },
+        {
+            "name": "symfony/console",
+            "version": "v5.4.47",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/console.git",
+                "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed",
+                "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/deprecation-contracts": "^2.1|^3",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/polyfill-php73": "^1.9",
+                "symfony/polyfill-php80": "^1.16",
+                "symfony/service-contracts": "^1.1|^2|^3",
+                "symfony/string": "^5.1|^6.0"
+            },
+            "conflict": {
+                "psr/log": ">=3",
+                "symfony/dependency-injection": "<4.4",
+                "symfony/dotenv": "<5.1",
+                "symfony/event-dispatcher": "<4.4",
+                "symfony/lock": "<4.4",
+                "symfony/process": "<4.4"
+            },
+            "provide": {
+                "psr/log-implementation": "1.0|2.0"
+            },
+            "require-dev": {
+                "psr/log": "^1|^2",
+                "symfony/config": "^4.4|^5.0|^6.0",
+                "symfony/dependency-injection": "^4.4|^5.0|^6.0",
+                "symfony/event-dispatcher": "^4.4|^5.0|^6.0",
+                "symfony/lock": "^4.4|^5.0|^6.0",
+                "symfony/process": "^4.4|^5.0|^6.0",
+                "symfony/var-dumper": "^4.4|^5.0|^6.0"
+            },
+            "suggest": {
+                "psr/log": "For using the console logger",
+                "symfony/event-dispatcher": "",
+                "symfony/lock": "",
+                "symfony/process": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Console\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Eases the creation of beautiful and testable command line interfaces",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "cli",
+                "command-line",
+                "console",
+                "terminal"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/console/tree/v5.4.47"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-11-06T11:30:55+00:00"
+        },
+        {
+            "name": "symfony/deprecation-contracts",
+            "version": "v2.5.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/deprecation-contracts.git",
+                "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918",
+                "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/contracts",
+                    "name": "symfony/contracts"
+                },
+                "branch-alias": {
+                    "dev-main": "2.5-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "function.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "A generic function and convention to trigger deprecation notices",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-09-25T14:11:13+00:00"
+        },
+        {
+            "name": "symfony/polyfill-ctype",
+            "version": "v1.37.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "141046a8f9477948ff284fa65be2095baafb94f2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2",
+                "reference": "141046a8f9477948ff284fa65be2095baafb94f2",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "provide": {
+                "ext-ctype": "*"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Gert de Pagter",
+                    "email": "BackEndTea@gmail.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-04-10T16:19:22+00:00"
+        },
+        {
+            "name": "symfony/polyfill-intl-grapheme",
+            "version": "v1.38.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
+                "reference": "e9247d281d694a5120554d9afaf54e070e88a603"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603",
+                "reference": "e9247d281d694a5120554d9afaf54e070e88a603",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for intl's grapheme_* functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "grapheme",
+                "intl",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-05-26T05:58:03+00:00"
+        },
+        {
+            "name": "symfony/polyfill-intl-normalizer",
+            "version": "v1.38.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+                "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b",
+                "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for intl's Normalizer class and related functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "intl",
+                "normalizer",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-05-25T13:48:31+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.38.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
+                "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
+                "shasum": ""
+            },
+            "require": {
+                "ext-iconv": "*",
+                "php": ">=7.2"
+            },
+            "provide": {
+                "ext-mbstring": "*"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-05-27T06:59:30+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php73",
+            "version": "v1.37.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php73.git",
+                "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb",
+                "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php73\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php73/tree/v1.37.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-09-09T11:45:10+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php80",
+            "version": "v1.37.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
+                "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php80\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-04-10T16:19:22+00:00"
+        },
+        {
+            "name": "symfony/process",
+            "version": "v5.4.51",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/process.git",
+                "reference": "467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/process/zipball/467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f",
+                "reference": "467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/polyfill-php80": "^1.16"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Process\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Executes commands in sub-processes",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/process/tree/v5.4.51"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/nicolas-grekas",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2026-01-26T15:53:37+00:00"
+        },
+        {
+            "name": "symfony/service-contracts",
+            "version": "v2.5.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/service-contracts.git",
+                "reference": "f37b419f7aea2e9abf10abd261832cace12e3300"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300",
+                "reference": "f37b419f7aea2e9abf10abd261832cace12e3300",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "psr/container": "^1.1",
+                "symfony/deprecation-contracts": "^2.1|^3"
+            },
+            "conflict": {
+                "ext-psr": "<1.1|>=2"
+            },
+            "suggest": {
+                "symfony/service-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/contracts",
+                    "name": "symfony/contracts"
+                },
+                "branch-alias": {
+                    "dev-main": "2.5-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Service\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to writing services",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/service-contracts/tree/v2.5.4"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-09-25T14:11:13+00:00"
+        },
+        {
+            "name": "symfony/string",
+            "version": "v5.4.47",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/string.git",
+                "reference": "136ca7d72f72b599f2631aca474a4f8e26719799"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799",
+                "reference": "136ca7d72f72b599f2631aca474a4f8e26719799",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/polyfill-ctype": "~1.8",
+                "symfony/polyfill-intl-grapheme": "~1.0",
+                "symfony/polyfill-intl-normalizer": "~1.0",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/polyfill-php80": "~1.15"
+            },
+            "conflict": {
+                "symfony/translation-contracts": ">=3.0"
+            },
+            "require-dev": {
+                "symfony/error-handler": "^4.4|^5.0|^6.0",
+                "symfony/http-client": "^4.4|^5.0|^6.0",
+                "symfony/translation-contracts": "^1.1|^2",
+                "symfony/var-exporter": "^4.4|^5.0|^6.0"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "Resources/functions.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Component\\String\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "grapheme",
+                "i18n",
+                "string",
+                "unicode",
+                "utf-8",
+                "utf8"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/string/tree/v5.4.47"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-11-10T20:33:58+00:00"
+        },
+        {
+            "name": "theseer/tokenizer",
+            "version": "1.3.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/theseer/tokenizer.git",
+                "reference": "b7489ce515e168639d17feec34b8847c326b0b3c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c",
+                "reference": "b7489ce515e168639d17feec34b8847c326b0b3c",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-tokenizer": "*",
+                "ext-xmlwriter": "*",
+                "php": "^7.2 || ^8.0"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+            "support": {
+                "issues": "https://github.com/theseer/tokenizer/issues",
+                "source": "https://github.com/theseer/tokenizer/tree/1.3.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/theseer",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-11-17T20:03:58+00:00"
+        },
+        {
+            "name": "wikimedia/at-ease",
+            "version": "v2.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/wikimedia/at-ease.git",
+                "reference": "e8ebaa7bb7c8a8395481a05f6dc4deaceab11c33"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/wikimedia/at-ease/zipball/e8ebaa7bb7c8a8395481a05f6dc4deaceab11c33",
+                "reference": "e8ebaa7bb7c8a8395481a05f6dc4deaceab11c33",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.9"
+            },
+            "require-dev": {
+                "mediawiki/mediawiki-codesniffer": "35.0.0",
+                "mediawiki/minus-x": "1.1.1",
+                "ockcyp/covers-validator": "1.3.3",
+                "php-parallel-lint/php-console-highlighter": "0.5.0",
+                "php-parallel-lint/php-parallel-lint": "1.2.0",
+                "phpunit/phpunit": "^8.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/Wikimedia/Functions.php"
+                ],
+                "psr-4": {
+                    "Wikimedia\\AtEase\\": "src/Wikimedia/AtEase/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "GPL-2.0-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "Tim Starling",
+                    "email": "tstarling@wikimedia.org"
+                },
+                {
+                    "name": "MediaWiki developers",
+                    "email": "wikitech-l@lists.wikimedia.org"
+                }
+            ],
+            "description": "Safe replacement to @ for suppressing warnings.",
+            "homepage": "https://www.mediawiki.org/wiki/at-ease",
+            "support": {
+                "source": "https://github.com/wikimedia/at-ease/tree/v2.1.0"
+            },
+            "time": "2021-02-27T15:53:37+00:00"
+        },
+        {
+            "name": "woocommerce/woocommerce-sniffs",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/woocommerce/woocommerce-sniffs.git",
+                "reference": "3a65b917ff5ab5e65609e5dcb7bc62f9455bbef8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/woocommerce/woocommerce-sniffs/zipball/3a65b917ff5ab5e65609e5dcb7bc62f9455bbef8",
+                "reference": "3a65b917ff5ab5e65609e5dcb7bc62f9455bbef8",
+                "shasum": ""
+            },
+            "require": {
+                "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0",
+                "php": ">=7.0",
+                "phpcompatibility/phpcompatibility-wp": "^2.1.0",
+                "wp-coding-standards/wpcs": "^3.0.0"
+            },
+            "type": "phpcodesniffer-standard",
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "WooCommerce sniffs",
+            "keywords": [
+                "phpcs",
+                "standards",
+                "static analysis",
+                "woocommerce",
+                "wordpress"
+            ],
+            "support": {
+                "issues": "https://github.com/woocommerce/woocommerce-sniffs/issues",
+                "source": "https://github.com/woocommerce/woocommerce-sniffs/tree/1.0.0"
+            },
+            "time": "2023-09-29T13:52:33+00:00"
+        },
+        {
+            "name": "wp-coding-standards/wpcs",
+            "version": "3.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
+                "reference": "7795ec6fa05663d716a549d0b44e47ffc8b0d4a6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7795ec6fa05663d716a549d0b44e47ffc8b0d4a6",
+                "reference": "7795ec6fa05663d716a549d0b44e47ffc8b0d4a6",
+                "shasum": ""
+            },
+            "require": {
+                "ext-filter": "*",
+                "ext-libxml": "*",
+                "ext-tokenizer": "*",
+                "ext-xmlreader": "*",
+                "php": ">=7.2",
+                "phpcsstandards/phpcsextra": "^1.5.0",
+                "phpcsstandards/phpcsutils": "^1.1.0",
+                "squizlabs/php_codesniffer": "^3.13.4"
+            },
+            "require-dev": {
+                "php-parallel-lint/php-console-highlighter": "^1.0.0",
+                "php-parallel-lint/php-parallel-lint": "^1.4.0",
+                "phpcompatibility/php-compatibility": "^10.0.0@dev",
+                "phpcsstandards/phpcsdevtools": "^1.2.0",
+                "phpunit/phpunit": "^8.0 || ^9.0"
+            },
+            "suggest": {
+                "ext-iconv": "For improved results",
+                "ext-mbstring": "For improved results"
+            },
+            "type": "phpcodesniffer-standard",
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Contributors",
+                    "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors"
+                }
+            ],
+            "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions",
+            "keywords": [
+                "phpcs",
+                "standards",
+                "static analysis",
+                "wordpress"
+            ],
+            "support": {
+                "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues",
+                "source": "https://github.com/WordPress/WordPress-Coding-Standards",
+                "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki"
+            },
+            "funding": [
+                {
+                    "url": "https://opencollective.com/php_codesniffer",
+                    "type": "custom"
+                }
+            ],
+            "time": "2025-11-25T12:08:04+00:00"
+        },
+        {
+            "name": "yoast/phpunit-polyfills",
+            "version": "4.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Yoast/PHPUnit-Polyfills.git",
+                "reference": "134921bfca9b02d8f374c48381451da1d98402f9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/134921bfca9b02d8f374c48381451da1d98402f9",
+                "reference": "134921bfca9b02d8f374c48381451da1d98402f9",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1",
+                "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0 || ^11.0 || ^12.0"
+            },
+            "require-dev": {
+                "php-parallel-lint/php-console-highlighter": "^1.0.0",
+                "php-parallel-lint/php-parallel-lint": "^1.4.0",
+                "yoast/yoastcs": "^3.1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "4.x-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "phpunitpolyfills-autoload.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Team Yoast",
+                    "email": "support@yoast.com",
+                    "homepage": "https://yoast.com"
+                },
+                {
+                    "name": "Contributors",
+                    "homepage": "https://github.com/Yoast/PHPUnit-Polyfills/graphs/contributors"
+                }
+            ],
+            "description": "Set of polyfills for changed PHPUnit functionality to allow for creating PHPUnit cross-version compatible tests",
+            "homepage": "https://github.com/Yoast/PHPUnit-Polyfills",
+            "keywords": [
+                "phpunit",
+                "polyfill",
+                "testing"
+            ],
+            "support": {
+                "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues",
+                "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy",
+                "source": "https://github.com/Yoast/PHPUnit-Polyfills"
+            },
+            "time": "2025-02-09T18:58:54+00:00"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "dev",
+    "stability-flags": {},
+    "prefer-stable": true,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=7.4"
+    },
+    "platform-dev": {},
+    "platform-overrides": {
+        "php": "7.4"
+    },
+    "plugin-api-version": "2.9.0"
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/license.txt b/packages/php/woocommerce-subscriptions-engine/license.txt
new file mode 100644
index 00000000000..ffe165d04ec
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/license.txt
@@ -0,0 +1,356 @@
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+===================================
+
+
+GNU GENERAL PUBLIC LICENSE
+   Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+		Preamble
+
+The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+GNU GENERAL PUBLIC LICENSE
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+a) You must cause the modified files to carry prominent notices
+stating that you changed the files and the date of any change.
+
+b) You must cause any work that you distribute or publish, that in
+whole or in part contains or is derived from the Program or any
+part thereof, to be licensed as a whole at no charge to all third
+parties under the terms of this License.
+
+c) If the modified program normally reads commands interactively
+when run, you must cause it, when started running for such
+interactive use in the most ordinary way, to print or display an
+announcement including an appropriate copyright notice and a
+notice that there is no warranty (or else, saying that you provide
+a warranty) and that users may redistribute the program under
+these conditions, and telling the user how to view a copy of this
+License.  (Exception: if the Program itself is interactive but
+does not normally print such an announcement, your work based on
+the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+a) Accompany it with the complete corresponding machine-readable
+source code, which must be distributed under the terms of Sections
+1 and 2 above on a medium customarily used for software interchange; or,
+
+b) Accompany it with a written offer, valid for at least three
+years, to give any third party, for a charge no more than your
+cost of physically performing source distribution, a complete
+machine-readable copy of the corresponding source code, to be
+distributed under the terms of Sections 1 and 2 above on a medium
+customarily used for software interchange; or,
+
+c) Accompany it with the information you received as to the offer
+to distribute corresponding source code.  (This alternative is
+allowed only for noncommercial distribution and only if you
+received the program in object code or executable form with such
+an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+		NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+<one line to give the program's name and a brief idea of what it does.>
+Copyright (C) <year>  <name of author>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+Gnomovision version 69, Copyright (C) year name of author
+Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+This is free software, and you are welcome to redistribute it
+under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+`Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+<signature of Ty Coon>, 1 April 1989
+Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/packages/php/woocommerce-subscriptions-engine/package.json b/packages/php/woocommerce-subscriptions-engine/package.json
new file mode 100644
index 00000000000..3b9fb08cfd4
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/package.json
@@ -0,0 +1,15 @@
+{
+	"name": "@automattic/woocommerce-subscriptions-engine",
+	"description": "Subscriptions engine for WooCommerce.",
+	"scripts": {
+		"update:php": "XDEBUG_MODE=off composer update --quiet",
+		"lint": "pnpm --if-present '/^lint:lang:.*$/'",
+		"lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'",
+		"lint:fix:lang:php": "composer run-script phpcbf",
+		"lint:lang:php": "composer run-script phpcs"
+	},
+	"license": "GPL-2.0-or-later",
+	"devDependencies": {
+		"@wordpress/env": "11.0.1-next.v.20260206T143.0"
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/phpcs.xml b/packages/php/woocommerce-subscriptions-engine/phpcs.xml
new file mode 100644
index 00000000000..7ff32de0ecc
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/phpcs.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0"?>
+<ruleset name="Custom WordPress Standards">
+	<!-- Set the base standard to WordPress -->
+	<rule ref="WordPress"/>
+
+	<!-- Define files and folders to scan -->
+	<file>.</file>
+
+	<!-- Exclude test files from FileName rules -->
+	<rule ref="WordPress.Files.FileName">
+		<exclude-pattern>tests/*</exclude-pattern>
+	</rule>
+
+	<!-- Test method names are self-documenting; do not require per-method docblocks. -->
+	<rule ref="Squiz.Commenting.FunctionComment.Missing">
+		<exclude-pattern>tests/*</exclude-pattern>
+	</rule>
+	<rule ref="Generic.Commenting.DocComment.MissingShort">
+		<exclude-pattern>tests/*</exclude-pattern>
+	</rule>
+
+	<!--
+		The Core zone is WordPress-free by design (entities, value objects, the
+		state machine). WordPress-specific output-escaping rules do not apply
+		there: Core throws plain exceptions and never echoes, and pulling
+		esc_*() into Core would violate the zoning rule it exists to enforce.
+		Escaping is the integration layer's responsibility at the boundary.
+	-->
+	<rule ref="WordPress.Security.EscapeOutput">
+		<exclude-pattern>src/Core/*</exclude-pattern>
+	</rule>
+
+	<!--
+		The slow-meta-query heuristic targets wp_postmeta-style tables. The
+		engine's own contract-meta table merely shares the meta_key/meta_value
+		column names; it is always queried by indexed contract_id, so the
+		heuristic does not apply to the storage layer.
+	-->
+	<rule ref="WordPress.DB.SlowDBQuery">
+		<exclude-pattern>src/Integration/Storage/*</exclude-pattern>
+	</rule>
+
+	<!-- Exclude test files from the PSR2.Methods.MethodDeclaration.Underscore rule due to methods _after() and _before() -->
+	<rule ref="PSR2.Methods.MethodDeclaration.Underscore">
+		<exclude-pattern>tests/unit</exclude-pattern>
+	</rule>
+
+	<!-- Skip the vendor directory -->
+	<exclude-pattern>vendor/*</exclude-pattern>
+
+	<!-- Skip the node_modules directory -->
+	<exclude-pattern>node_modules/*</exclude-pattern>
+
+	<!-- Skip the PHPStan temp directory -->
+	<exclude-pattern>tasks/phpstan/temp/*</exclude-pattern>
+
+	<!-- Skip the build directory -->
+	<exclude-pattern>build/*</exclude-pattern>
+</ruleset>
diff --git a/packages/php/woocommerce-subscriptions-engine/phpunit-integration.xml.dist b/packages/php/woocommerce-subscriptions-engine/phpunit-integration.xml.dist
new file mode 100644
index 00000000000..8250f22b1b9
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/phpunit-integration.xml.dist
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		 bootstrap="tests/integration/bootstrap.php"
+		 backupGlobals="false"
+		 colors="true"
+		 convertErrorsToExceptions="true"
+		 convertNoticesToExceptions="true"
+		 convertWarningsToExceptions="true"
+		 verbose="true"
+		 xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
+	<testsuites>
+		<testsuite name="Integration">
+			<directory suffix="_Test.php">./tests/integration</directory>
+		</testsuite>
+	</testsuites>
+</phpunit>
diff --git a/packages/php/woocommerce-subscriptions-engine/phpunit.xml.dist b/packages/php/woocommerce-subscriptions-engine/phpunit.xml.dist
new file mode 100644
index 00000000000..2af6d1e1451
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/phpunit.xml.dist
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+		 backupStaticAttributes="false"
+		 colors="true"
+		 bootstrap="tests/unit/bootstrap.php"
+		 convertErrorsToExceptions="true"
+		 convertNoticesToExceptions="true"
+		 convertWarningsToExceptions="true"
+		 processIsolation="false"
+		 stopOnFailure="false"
+		 verbose="true">
+	<testsuites>
+		<testsuite name="WooCommerceSubscriptionsEngineUnitTestSuite">
+			<directory suffix="_Test.php">./tests/unit</directory>
+		</testsuite>
+	</testsuites>
+
+	<php>
+		<ini name="display_errors" value="true"/>
+	</php>
+</phpunit>
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/class-contract-status.php b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/class-contract-status.php
new file mode 100644
index 00000000000..02971bc6c5d
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/class-contract-status.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Contract_Status - the contract lifecycle state machine.
+ *
+ * Owns the set of valid statuses and the allowed transitions between them.
+ * Status transitions are validated here and applied by the {@see Contract} entity.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine\Core\Entity
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Core\Entity;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Contract_Status value/helper class.
+ */
+final class Contract_Status {
+
+	const ACTIVE               = 'active';
+	const ON_HOLD              = 'on-hold';
+	const PENDING_CANCELLATION = 'pending-cancellation';
+	const CANCELLED            = 'cancelled';
+	const EXPIRED              = 'expired';
+
+	/**
+	 * All known statuses.
+	 *
+	 * @return array<int, string>
+	 */
+	public static function all(): array {
+		return array(
+			self::ACTIVE,
+			self::ON_HOLD,
+			self::PENDING_CANCELLATION,
+			self::CANCELLED,
+			self::EXPIRED,
+		);
+	}
+
+	/**
+	 * Whether `$status` is a known status.
+	 *
+	 * @param string $status Status to check.
+	 */
+	public static function is_valid( string $status ): bool {
+		return in_array( $status, self::all(), true );
+	}
+
+	/**
+	 * Whether `$status` is terminal (no transitions out).
+	 *
+	 * @param string $status Status to check.
+	 */
+	public static function is_terminal( string $status ): bool {
+		return self::is_valid( $status ) && array() === self::transitions()[ $status ];
+	}
+
+	/**
+	 * Whether a contract may move from `$from` to `$to`.
+	 *
+	 * @param string $from Current status.
+	 * @param string $to   Target status.
+	 */
+	public static function can_transition( string $from, string $to ): bool {
+		if ( ! self::is_valid( $from ) || ! self::is_valid( $to ) ) {
+			return false;
+		}
+
+		return in_array( $to, self::transitions()[ $from ], true );
+	}
+
+	/**
+	 * Allowed transitions: current status => list of reachable statuses.
+	 *
+	 * @return array<string, array<int, string>>
+	 */
+	private static function transitions(): array {
+		return array(
+			self::ACTIVE               => array( self::ON_HOLD, self::PENDING_CANCELLATION, self::CANCELLED, self::EXPIRED ),
+			self::ON_HOLD              => array( self::ACTIVE, self::PENDING_CANCELLATION, self::CANCELLED ),
+			self::PENDING_CANCELLATION => array( self::ACTIVE, self::CANCELLED ),
+			self::CANCELLED            => array(),
+			self::EXPIRED              => array(),
+		);
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/class-contract.php b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/class-contract.php
new file mode 100644
index 00000000000..2218ed277b0
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/class-contract.php
@@ -0,0 +1,555 @@
+<?php
+/**
+ * Contract - the stable, customer-facing identity of a subscription. Manages
+ * core data for the subscription and enforces lifecycle transitions through
+ * {@see Contract_Status}.
+ *
+ * Money totals are kept as decimal-safe strings; timestamps are GMT strings
+ * (`Y-m-d H:i:s`). The payment instrument is exposed as an {@see Instrument_Ref}
+ * rather than a live payment token.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine\Core\Entity
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Core\Entity;
+
+use DomainException;
+use Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject\Instrument_Ref;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Contract entity.
+ *
+ * Construct via {@see self::create()} for a new (unsaved) contract or
+ * {@see self::from_storage()} when hydrating a stored row.
+ */
+final class Contract {
+
+	const SCHEDULE_SOURCE_PRIMITIVE = 'primitive';
+	const SCHEDULE_SOURCE_GATEWAY   = 'gateway';
+
+	const ADDRESS_BILLING  = 'billing';
+	const ADDRESS_SHIPPING = 'shipping';
+
+	/**
+	 * Contract id, or null before it is persisted.
+	 *
+	 * @var int|null
+	 */
+	private $id;
+
+	/**
+	 * Lifecycle status. See {@see Contract_Status}.
+	 *
+	 * @var string
+	 */
+	private $status;
+
+	/**
+	 * Owning customer id.
+	 *
+	 * @var int
+	 */
+	private $customer_id;
+
+	/**
+	 * ISO-4217 currency code, locked at creation.
+	 *
+	 * @var string
+	 */
+	private $currency;
+
+	/**
+	 * Foreign key to the selling plan.
+	 *
+	 * @var int
+	 */
+	private $selling_plan_id;
+
+	/**
+	 * Foreign key to the order that triggered this contract.
+	 *
+	 * @var int
+	 */
+	private $origin_order_id;
+
+	/**
+	 * Owning extension slug, or null until owner semantics are assigned.
+	 *
+	 * @var string|null
+	 */
+	private $extension_slug;
+
+	/**
+	 * Gateway code, or null.
+	 *
+	 * @var string|null
+	 */
+	private $payment_method;
+
+	/**
+	 * Human-readable gateway title, or null.
+	 *
+	 * @var string|null
+	 */
+	private $payment_method_title;
+
+	/**
+	 * Payment token id, or null.
+	 *
+	 * @var int|null
+	 */
+	private $payment_token_id;
+
+	/**
+	 * Recurring total per cycle (decimal-safe string).
+	 *
+	 * @var string
+	 */
+	private $billing_total;
+
+	/**
+	 * Recurring discount per cycle (decimal-safe string).
+	 *
+	 * @var string
+	 */
+	private $discount_total;
+
+	/**
+	 * Recurring shipping per cycle (decimal-safe string).
+	 *
+	 * @var string
+	 */
+	private $shipping_total;
+
+	/**
+	 * Recurring tax per cycle (decimal-safe string).
+	 *
+	 * @var string
+	 */
+	private $tax_total;
+
+	/**
+	 * When the contract goes (or went) active. GMT string.
+	 *
+	 * @var string
+	 */
+	private $start_gmt;
+
+	/**
+	 * Next renewal attempt, or null. GMT string.
+	 *
+	 * @var string|null
+	 */
+	private $next_payment_gmt;
+
+	/**
+	 * Last successful renewal payment, or null. GMT string.
+	 *
+	 * @var string|null
+	 */
+	private $last_payment_gmt;
+
+	/**
+	 * Last attempted renewal cycle regardless of outcome, or null. GMT string.
+	 *
+	 * @var string|null
+	 */
+	private $last_attempt_gmt;
+
+	/**
+	 * End of trial window, or null. GMT string.
+	 *
+	 * @var string|null
+	 */
+	private $trial_end_gmt;
+
+	/**
+	 * Hard end (cancelled / expired / max_cycles reached), or null. GMT string.
+	 *
+	 * @var string|null
+	 */
+	private $end_gmt;
+
+	/**
+	 * Count of successfully-paid renewal cycles.
+	 *
+	 * @var int
+	 */
+	private $cycle_count;
+
+	/**
+	 * Who runs renewals: 'primitive' (this engine) or 'gateway'.
+	 *
+	 * @var string
+	 */
+	private $schedule_source;
+
+	/**
+	 * Line items, each a plain associative array matching the items table shape.
+	 *
+	 * @var array<int, array<string, mixed>>
+	 */
+	private $items;
+
+	/**
+	 * Addresses keyed by type ('billing' | 'shipping').
+	 *
+	 * @var array{ billing: array<string, mixed>, shipping: array<string, mixed> }
+	 */
+	private $addresses;
+
+	/**
+	 * Contract meta as key => value.
+	 *
+	 * @var array<string, string>
+	 */
+	private $meta;
+
+	/**
+	 * Use {@see self::create()} or {@see self::from_storage()}.
+	 *
+	 * @param array<string, mixed> $fields Internal field map.
+	 */
+	private function __construct( array $fields ) {
+		$this->id                   = $fields['id'];
+		$this->status               = $fields['status'];
+		$this->customer_id          = $fields['customer_id'];
+		$this->currency             = $fields['currency'];
+		$this->selling_plan_id      = $fields['selling_plan_id'];
+		$this->origin_order_id      = $fields['origin_order_id'];
+		$this->extension_slug       = $fields['extension_slug'];
+		$this->payment_method       = $fields['payment_method'];
+		$this->payment_method_title = $fields['payment_method_title'];
+		$this->payment_token_id     = $fields['payment_token_id'];
+		$this->billing_total        = $fields['billing_total'];
+		$this->discount_total       = $fields['discount_total'];
+		$this->shipping_total       = $fields['shipping_total'];
+		$this->tax_total            = $fields['tax_total'];
+		$this->start_gmt            = $fields['start_gmt'];
+		$this->next_payment_gmt     = $fields['next_payment_gmt'];
+		$this->last_payment_gmt     = $fields['last_payment_gmt'];
+		$this->last_attempt_gmt     = $fields['last_attempt_gmt'];
+		$this->trial_end_gmt        = $fields['trial_end_gmt'];
+		$this->end_gmt              = $fields['end_gmt'];
+		$this->cycle_count          = $fields['cycle_count'];
+		$this->schedule_source      = $fields['schedule_source'];
+		$this->items                = $fields['items'];
+		$this->addresses            = $fields['addresses'];
+		$this->meta                 = $fields['meta'];
+	}
+
+	/**
+	 * Build a new, unsaved contract.
+	 *
+	 * @param array{
+	 *     customer_id: int,
+	 *     currency: string,
+	 *     selling_plan_id: int,
+	 *     origin_order_id: int,
+	 *     start_gmt: string,
+	 *     status?: string,
+	 *     extension_slug?: string,
+	 *     payment_method?: string,
+	 *     payment_method_title?: string,
+	 *     payment_token_id?: int,
+	 *     billing_total: string,
+	 *     discount_total?: string,
+	 *     shipping_total?: string,
+	 *     tax_total?: string,
+	 *     next_payment_gmt?: string,
+	 *     trial_end_gmt?: string,
+	 *     schedule_source: string,
+	 *     items: array<int, array<string, mixed>>,
+	 *     addresses: array{ billing: array<string, mixed>, shipping: array<string, mixed> },
+	 *     meta: array<string, string>,
+	 * } $args Contract attributes.
+	 * @throws DomainException If the contract attributes are not valid.
+	 */
+	public static function create( array $args ): self {
+		$status = (string) ( $args['status'] ?? Contract_Status::ACTIVE );
+		if ( ! Contract_Status::is_valid( $status ) ) {
+			throw new DomainException(
+				sprintf( 'Contract: invalid status "%s".', $status )
+			);
+		}
+
+		$schedule_source = (string) ( $args['schedule_source'] ?? self::SCHEDULE_SOURCE_PRIMITIVE );
+		if ( ! in_array( $schedule_source, array( self::SCHEDULE_SOURCE_PRIMITIVE, self::SCHEDULE_SOURCE_GATEWAY ), true ) ) {
+			throw new DomainException(
+				sprintf( 'Contract: invalid schedule source "%s".', $schedule_source )
+			);
+		}
+
+		return new self(
+			array(
+				'id'                   => null,
+				'status'               => $status,
+				'customer_id'          => (int) $args['customer_id'],
+				'currency'             => (string) $args['currency'],
+				'selling_plan_id'      => (int) $args['selling_plan_id'],
+				'origin_order_id'      => (int) $args['origin_order_id'],
+				'extension_slug'       => is_string( $args['extension_slug'] ?? null ) ? $args['extension_slug'] : null,
+				'payment_method'       => is_string( $args['payment_method'] ?? null ) ? $args['payment_method'] : null,
+				'payment_method_title' => is_string( $args['payment_method_title'] ?? null ) ? $args['payment_method_title'] : null,
+				'payment_token_id'     => isset( $args['payment_token_id'] ) ? (int) $args['payment_token_id'] : null,
+				'billing_total'        => (string) ( $args['billing_total'] ?? '0' ),
+				'discount_total'       => (string) ( $args['discount_total'] ?? '0' ),
+				'shipping_total'       => (string) ( $args['shipping_total'] ?? '0' ),
+				'tax_total'            => (string) ( $args['tax_total'] ?? '0' ),
+				'start_gmt'            => (string) $args['start_gmt'],
+				'next_payment_gmt'     => $args['next_payment_gmt'] ?? null,
+				'last_payment_gmt'     => null,
+				'last_attempt_gmt'     => null,
+				'trial_end_gmt'        => is_string( $args['trial_end_gmt'] ?? null ) ? $args['trial_end_gmt'] : null,
+				'end_gmt'              => null,
+				'cycle_count'          => 0,
+				'schedule_source'      => $schedule_source,
+				'items'                => is_array( $args['items'] ?? null ) ? $args['items'] : array(),
+				'addresses'            => is_array( $args['addresses'] ?? null ) ? $args['addresses'] : array(),
+				'meta'                 => is_array( $args['meta'] ?? null ) ? $args['meta'] : array(),
+			)
+		);
+	}
+
+	/**
+	 * Hydrate from stored rows.
+	 *
+	 * @param array<string, mixed>                $row       Contract row.
+	 * @param array<int, array<string, mixed>>    $items     Item rows.
+	 * @param array<string, array<string, mixed>> $addresses Address rows keyed by type.
+	 * @param array<string, string>               $meta      Meta as key => value.
+	 */
+	public static function from_storage( array $row, array $items = array(), array $addresses = array(), array $meta = array() ): self {
+		return new self(
+			array(
+				'id'                   => isset( $row['id'] ) ? (int) $row['id'] : null,
+				'status'               => (string) $row['status'],
+				'customer_id'          => (int) $row['customer_id'],
+				'currency'             => (string) $row['currency'],
+				'selling_plan_id'      => (int) $row['selling_plan_id'],
+				'origin_order_id'      => (int) $row['origin_order_id'],
+				'extension_slug'       => isset( $row['extension_slug'] ) ? (string) $row['extension_slug'] : null,
+				'payment_method'       => isset( $row['payment_method'] ) ? (string) $row['payment_method'] : null,
+				'payment_method_title' => isset( $row['payment_method_title'] ) ? (string) $row['payment_method_title'] : null,
+				'payment_token_id'     => isset( $row['payment_token_id'] ) ? (int) $row['payment_token_id'] : null,
+				'billing_total'        => (string) ( $row['billing_total'] ?? '0' ),
+				'discount_total'       => (string) ( $row['discount_total'] ?? '0' ),
+				'shipping_total'       => (string) ( $row['shipping_total'] ?? '0' ),
+				'tax_total'            => (string) ( $row['tax_total'] ?? '0' ),
+				'start_gmt'            => (string) $row['start_gmt'],
+				'next_payment_gmt'     => $row['next_payment_gmt'] ?? null,
+				'last_payment_gmt'     => $row['last_payment_gmt'] ?? null,
+				'last_attempt_gmt'     => $row['last_attempt_gmt'] ?? null,
+				'trial_end_gmt'        => $row['trial_end_gmt'] ?? null,
+				'end_gmt'              => $row['end_gmt'] ?? null,
+				'cycle_count'          => (int) ( $row['cycle_count'] ?? 0 ),
+				'schedule_source'      => (string) ( $row['schedule_source'] ?? self::SCHEDULE_SOURCE_PRIMITIVE ),
+				'items'                => $items,
+				'addresses'            => $addresses,
+				'meta'                 => $meta,
+			)
+		);
+	}
+
+	/**
+	 * Contract id, or null before save.
+	 */
+	public function get_id(): ?int {
+		return $this->id;
+	}
+
+	/**
+	 * Assign the id after a successful insert.
+	 *
+	 * @param int $id Contract id.
+	 */
+	public function set_id( int $id ): void {
+		$this->id = $id;
+	}
+
+	/**
+	 * Lifecycle status.
+	 */
+	public function get_status(): string {
+		return $this->status;
+	}
+
+	/**
+	 * Transition the contract to a new status.
+	 *
+	 * @param string $status Target status.
+	 * @throws DomainException If the transition is not allowed by Contract_Status.
+	 */
+	public function set_status( string $status ): void {
+		if ( $status === $this->status ) {
+			return;
+		}
+
+		if ( ! Contract_Status::can_transition( $this->status, $status ) ) {
+			throw new DomainException(
+				sprintf( 'Contract: illegal status transition from "%s" to "%s".', $this->status, $status )
+			);
+		}
+
+		$this->status = $status;
+	}
+
+	/**
+	 * Owning customer id.
+	 */
+	public function get_customer_id(): int {
+		return $this->customer_id;
+	}
+
+	/**
+	 * ISO-4217 currency code.
+	 */
+	public function get_currency(): string {
+		return $this->currency;
+	}
+
+	/**
+	 * Foreign key to the selling plan.
+	 */
+	public function get_selling_plan_id(): int {
+		return $this->selling_plan_id;
+	}
+
+	/**
+	 * Foreign key to the origin order.
+	 */
+	public function get_origin_order_id(): int {
+		return $this->origin_order_id;
+	}
+
+	/**
+	 * Owning extension slug, or null.
+	 */
+	public function get_extension_slug(): ?string {
+		return $this->extension_slug;
+	}
+
+	/**
+	 * The payment instrument as an immutable reference.
+	 */
+	public function get_payment_instrument(): Instrument_Ref {
+		return new Instrument_Ref( $this->payment_token_id, $this->payment_method, $this->payment_method_title );
+	}
+
+	/**
+	 * Set the payment instrument from an immutable reference.
+	 *
+	 * @param Instrument_Ref $instrument Payment instrument reference.
+	 */
+	public function set_payment_instrument( Instrument_Ref $instrument ): void {
+		$this->payment_token_id     = $instrument->get_token_id();
+		$this->payment_method       = $instrument->get_gateway();
+		$this->payment_method_title = $instrument->get_title();
+	}
+
+	/**
+	 * Recurring total per cycle (decimal-safe string).
+	 */
+	public function get_billing_total(): string {
+		return $this->billing_total;
+	}
+
+	/**
+	 * Next renewal attempt, or null.
+	 */
+	public function get_next_payment_gmt(): ?string {
+		return $this->next_payment_gmt;
+	}
+
+	/**
+	 * Set the next renewal attempt timestamp.
+	 *
+	 * @param string|null $next_payment_gmt GMT string or null.
+	 */
+	public function set_next_payment_gmt( ?string $next_payment_gmt ): void {
+		$this->next_payment_gmt = $next_payment_gmt;
+	}
+
+	/**
+	 * Start timestamp (GMT string).
+	 */
+	public function get_start_gmt(): string {
+		return $this->start_gmt;
+	}
+
+	/**
+	 * Count of successfully-paid renewal cycles.
+	 */
+	public function get_cycle_count(): int {
+		return $this->cycle_count;
+	}
+
+	/**
+	 * Who runs renewals: 'primitive' or 'gateway'.
+	 */
+	public function get_schedule_source(): string {
+		return $this->schedule_source;
+	}
+
+	/**
+	 * Line items.
+	 *
+	 * @return array<int, array<string, mixed>>
+	 */
+	public function get_items(): array {
+		return $this->items;
+	}
+
+	/**
+	 * Addresses keyed by type.
+	 *
+	 * @return array<string, array<string, mixed>>
+	 */
+	public function get_addresses(): array {
+		return $this->addresses;
+	}
+
+	/**
+	 * Contract meta as key => value.
+	 *
+	 * @return array<string, string>
+	 */
+	public function get_meta(): array {
+		return $this->meta;
+	}
+
+	/**
+	 * Serialize the contract row (excluding generated id/timestamps).
+	 *
+	 * @return array<string, mixed>
+	 */
+	public function to_storage(): array {
+		return array(
+			'status'               => $this->status,
+			'customer_id'          => $this->customer_id,
+			'currency'             => $this->currency,
+			'selling_plan_id'      => $this->selling_plan_id,
+			'origin_order_id'      => $this->origin_order_id,
+			'extension_slug'       => $this->extension_slug,
+			'payment_method'       => $this->payment_method,
+			'payment_method_title' => $this->payment_method_title,
+			'payment_token_id'     => $this->payment_token_id,
+			'billing_total'        => $this->billing_total,
+			'discount_total'       => $this->discount_total,
+			'shipping_total'       => $this->shipping_total,
+			'tax_total'            => $this->tax_total,
+			'start_gmt'            => $this->start_gmt,
+			'next_payment_gmt'     => $this->next_payment_gmt,
+			'last_payment_gmt'     => $this->last_payment_gmt,
+			'last_attempt_gmt'     => $this->last_attempt_gmt,
+			'trial_end_gmt'        => $this->trial_end_gmt,
+			'end_gmt'              => $this->end_gmt,
+			'cycle_count'          => $this->cycle_count,
+			'schedule_source'      => $this->schedule_source,
+		);
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/class-plan-group.php b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/class-plan-group.php
new file mode 100644
index 00000000000..5ce6490daf5
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/class-plan-group.php
@@ -0,0 +1,190 @@
+<?php
+/**
+ * Plan_Group - a merchandising container for selling plans.
+ *
+ * `merchant_code` is an optional stable external identifier; when present it is
+ * unique at the storage layer and is the deduplication key consumers use to
+ * make group creation idempotent. `app_id` scopes a group to a solution family.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine\Core\Entity
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Core\Entity;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Plan_Group entity.
+ *
+ * Construct via {@see self::create()} for a new (unsaved) group or
+ * {@see self::from_storage()} when hydrating a stored row.
+ */
+final class Plan_Group {
+
+	/**
+	 * Group id, or null before it is persisted.
+	 *
+	 * @var int|null
+	 */
+	private $id;
+
+	/**
+	 * Display name.
+	 *
+	 * @var string
+	 */
+	private $name;
+
+	/**
+	 * Optional stable external identifier; unique at the storage layer.
+	 *
+	 * @var string|null
+	 */
+	private $merchant_code;
+
+	/**
+	 * Display ordering metadata, e.g. [{ name, position }].
+	 *
+	 * @var array<int, mixed>
+	 */
+	private $options_display;
+
+	/**
+	 * Solution-family scope, e.g. a third-party app slug.
+	 *
+	 * @var string|null
+	 */
+	private $app_id;
+
+	/**
+	 * Use {@see self::create()} or {@see self::from_storage()}.
+	 *
+	 * @param int|null          $id              Group id, or null before save.
+	 * @param string            $name            Display name.
+	 * @param string|null       $merchant_code   Optional stable external identifier.
+	 * @param array<int, mixed> $options_display Display ordering metadata.
+	 * @param string|null       $app_id          Solution-family scope.
+	 */
+	private function __construct( ?int $id, string $name, ?string $merchant_code, array $options_display, ?string $app_id ) {
+		$this->id              = $id;
+		$this->name            = $name;
+		$this->merchant_code   = $merchant_code;
+		$this->options_display = $options_display;
+		$this->app_id          = $app_id;
+	}
+
+	/**
+	 * Build a new, unsaved group.
+	 *
+	 * @param array{
+	 *     name: string,
+	 *     merchant_code?: string|null,
+	 *     options_display?: array<int, mixed>,
+	 *     app_id?: string|null,
+	 * } $args Group attributes.
+	 */
+	public static function create( array $args ): self {
+		return new self(
+			null,
+			(string) $args['name'],
+			$args['merchant_code'] ?? null,
+			$args['options_display'] ?? array(),
+			$args['app_id'] ?? null
+		);
+	}
+
+	/**
+	 * Hydrate from a stored row.
+	 *
+	 * @param array<string, mixed> $row Decoded plan-group row.
+	 */
+	public static function from_storage( array $row ): self {
+		return new self(
+			isset( $row['id'] ) ? (int) $row['id'] : null,
+			(string) $row['name'],
+			isset( $row['merchant_code'] ) ? (string) $row['merchant_code'] : null,
+			is_array( $row['options_display'] ?? null ) ? $row['options_display'] : array(),
+			isset( $row['app_id'] ) ? (string) $row['app_id'] : null
+		);
+	}
+
+	/**
+	 * Group id, or null before save.
+	 */
+	public function get_id(): ?int {
+		return $this->id;
+	}
+
+	/**
+	 * Assign the id after a successful insert.
+	 *
+	 * @param int $id Group id.
+	 */
+	public function set_id( int $id ): void {
+		$this->id = $id;
+	}
+
+	/**
+	 * Display name.
+	 */
+	public function get_name(): string {
+		return $this->name;
+	}
+
+	/**
+	 * Set the display name.
+	 *
+	 * @param string $name Display name.
+	 */
+	public function set_name( string $name ): void {
+		$this->name = $name;
+	}
+
+	/**
+	 * Optional stable external identifier; unique at the storage layer.
+	 */
+	public function get_merchant_code(): ?string {
+		return $this->merchant_code;
+	}
+
+	/**
+	 * Display ordering metadata.
+	 *
+	 * @return array<int, mixed>
+	 */
+	public function get_options_display(): array {
+		return $this->options_display;
+	}
+
+	/**
+	 * Set the display ordering metadata.
+	 *
+	 * @param array<int, mixed> $options_display Display ordering metadata.
+	 */
+	public function set_options_display( array $options_display ): void {
+		$this->options_display = $options_display;
+	}
+
+	/**
+	 * Solution-family scope.
+	 */
+	public function get_app_id(): ?string {
+		return $this->app_id;
+	}
+
+	/**
+	 * Serialize to the storage column shape (excluding generated id/timestamps).
+	 *
+	 * @return array<string, mixed>
+	 */
+	public function to_storage(): array {
+		return array(
+			'name'            => $this->name,
+			'merchant_code'   => $this->merchant_code,
+			'options_display' => $this->options_display,
+			'app_id'          => $this->app_id,
+		);
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/class-plan.php b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/class-plan.php
new file mode 100644
index 00000000000..63060cadf33
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/class-plan.php
@@ -0,0 +1,480 @@
+<?php
+/**
+ * Plan - a subscription selling plan: cadence, pricing, and delivery policy for
+ * one or more products.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine\Core\Entity
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Core\Entity;
+
+use InvalidArgumentException;
+use Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject\Billing_Policy;
+use Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject\Delivery_Policy;
+use Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject\Pricing_Policy;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Plan entity.
+ *
+ * Construct via {@see self::create()} for a new (unsaved) plan or
+ * {@see self::from_storage()} when hydrating a stored row.
+ */
+final class Plan {
+
+	const DEFAULT_CATEGORY = 'SUBSCRIPTION';
+
+	const ALLOWED_POLICY_TYPES = array( 'percentage', 'fixed_amount', 'price' );
+
+	/**
+	 * Plan id, or null before it is persisted.
+	 *
+	 * @var int|null
+	 */
+	private $id;
+
+	/**
+	 * Parent plan group id.
+	 *
+	 * @var int
+	 */
+	private $group_id;
+
+	/**
+	 * Display name.
+	 *
+	 * @var string
+	 */
+	private $name;
+
+	/**
+	 * Optional description.
+	 *
+	 * @var string|null
+	 */
+	private $description;
+
+	/**
+	 * Picker options, e.g. [{ name, value }].
+	 *
+	 * @var array<int, mixed>
+	 */
+	private $options;
+
+	/**
+	 * Billing cadence. Required - every plan has one.
+	 *
+	 * @var Billing_Policy
+	 */
+	private $billing_policy;
+
+	/**
+	 * Optional delivery policy.
+	 *
+	 * @var Delivery_Policy|null
+	 */
+	private $delivery_policy;
+
+	/**
+	 * Optional pricing policy.
+	 *
+	 * @var Pricing_Policy|null
+	 */
+	private $pricing_policy;
+
+	/**
+	 * Plan category.
+	 *
+	 * @var string
+	 */
+	private $category;
+
+	/**
+	 * Owning extension slug, or null until owner semantics are assigned.
+	 *
+	 * @var string|null
+	 */
+	private $extension_slug;
+
+	/**
+	 * Use {@see self::create()} or {@see self::from_storage()}.
+	 *
+	 * @param int|null             $id              Plan id, or null before save.
+	 * @param int                  $group_id        Parent plan group id.
+	 * @param string               $name            Display name.
+	 * @param string|null          $description     Optional description.
+	 * @param array<int, mixed>    $options         Picker options.
+	 * @param Billing_Policy       $billing_policy  Billing cadence.
+	 * @param Delivery_Policy|null $delivery_policy Optional delivery policy.
+	 * @param Pricing_Policy|null  $pricing_policy  Optional pricing policy.
+	 * @param string               $category        Plan category.
+	 * @param string|null          $extension_slug  Owning extension slug.
+	 */
+	private function __construct(
+		?int $id,
+		int $group_id,
+		string $name,
+		?string $description,
+		array $options,
+		Billing_Policy $billing_policy,
+		?Delivery_Policy $delivery_policy,
+		?Pricing_Policy $pricing_policy,
+		string $category,
+		?string $extension_slug
+	) {
+		$this->id              = $id;
+		$this->group_id        = $group_id;
+		$this->name            = $name;
+		$this->description     = $description;
+		$this->options         = $options;
+		$this->billing_policy  = $billing_policy;
+		$this->delivery_policy = $delivery_policy;
+		$this->pricing_policy  = $pricing_policy;
+		$this->category        = $category;
+		$this->extension_slug  = $extension_slug;
+	}
+
+	/**
+	 * Build a new, unsaved plan.
+	 *
+	 * @param int    $group_id Parent plan group id.
+	 * @param array{
+	 *     name: string,
+	 *     billing_policy: Billing_Policy,
+	 *     description?: string,
+	 *     options?: array<int, mixed>,
+	 *     delivery_policy?: Delivery_Policy,
+	 *     pricing_policy?: Pricing_Policy,
+	 *     category: string,
+	 *     extension_slug?: string,
+	 * } $args     Plan attributes.
+	 * @throws InvalidArgumentException If pricing_policy entries fail validation.
+	 */
+	public static function create( int $group_id, array $args ): self {
+		$pricing_policy = $args['pricing_policy'] ?? null;
+		if ( null !== $pricing_policy ) {
+			self::validate_pricing_policy( $pricing_policy );
+		}
+
+		return new self(
+			null,
+			$group_id,
+			(string) $args['name'],
+			$args['description'] ?? null,
+			$args['options'] ?? array(),
+			$args['billing_policy'],
+			$args['delivery_policy'] ?? null,
+			$pricing_policy,
+			$args['category'] ?? self::DEFAULT_CATEGORY,
+			$args['extension_slug'] ?? null
+		);
+	}
+
+	/**
+	 * Hydrate from a stored row. Policy columns arrive JSON-decoded.
+	 *
+	 * @param array<string, mixed> $row Decoded plan row.
+	 */
+	public static function from_storage( array $row ): self {
+		return new self(
+			isset( $row['id'] ) ? (int) $row['id'] : null,
+			(int) $row['group_id'],
+			(string) $row['name'],
+			isset( $row['description'] ) ? (string) $row['description'] : null,
+			is_array( $row['options'] ?? null ) ? $row['options'] : array(),
+			Billing_Policy::from_array( $row['billing_policy'] ),
+			isset( $row['delivery_policy'] ) && is_array( $row['delivery_policy'] ) ? Delivery_Policy::from_array( $row['delivery_policy'] ) : null,
+			isset( $row['pricing_policy'] ) && is_array( $row['pricing_policy'] ) ? Pricing_Policy::from_array( $row['pricing_policy'] ) : null,
+			(string) ( $row['category'] ?? self::DEFAULT_CATEGORY ),
+			isset( $row['extension_slug'] ) ? (string) $row['extension_slug'] : null
+		);
+	}
+
+	/**
+	 * Plan id, or null before save.
+	 */
+	public function get_id(): ?int {
+		return $this->id;
+	}
+
+	/**
+	 * Assign the id after a successful insert.
+	 *
+	 * @param int $id Plan id.
+	 */
+	public function set_id( int $id ): void {
+		$this->id = $id;
+	}
+
+	/**
+	 * Parent plan group id.
+	 */
+	public function get_group_id(): int {
+		return $this->group_id;
+	}
+
+	/**
+	 * Display name.
+	 */
+	public function get_name(): string {
+		return $this->name;
+	}
+
+	/**
+	 * Set the display name.
+	 *
+	 * @param string $name Display name.
+	 */
+	public function set_name( string $name ): void {
+		$this->name = $name;
+	}
+
+	/**
+	 * Optional description.
+	 */
+	public function get_description(): ?string {
+		return $this->description;
+	}
+
+	/**
+	 * Set the description.
+	 *
+	 * @param string|null $description Description.
+	 */
+	public function set_description( ?string $description ): void {
+		$this->description = $description;
+	}
+
+	/**
+	 * Picker options.
+	 *
+	 * @return array<int, mixed>
+	 */
+	public function get_options(): array {
+		return $this->options;
+	}
+
+	/**
+	 * Set the picker options.
+	 *
+	 * @param array<int, mixed> $options Picker options.
+	 */
+	public function set_options( array $options ): void {
+		$this->options = $options;
+	}
+
+	/**
+	 * Billing cadence.
+	 */
+	public function get_billing_policy(): Billing_Policy {
+		return $this->billing_policy;
+	}
+
+	/**
+	 * Set the billing cadence.
+	 *
+	 * @param Billing_Policy $billing_policy Billing cadence.
+	 */
+	public function set_billing_policy( Billing_Policy $billing_policy ): void {
+		$this->billing_policy = $billing_policy;
+	}
+
+	/**
+	 * Optional delivery policy.
+	 */
+	public function get_delivery_policy(): ?Delivery_Policy {
+		return $this->delivery_policy;
+	}
+
+	/**
+	 * Set the delivery policy.
+	 *
+	 * @param Delivery_Policy|null $delivery_policy Delivery policy.
+	 */
+	public function set_delivery_policy( ?Delivery_Policy $delivery_policy ): void {
+		$this->delivery_policy = $delivery_policy;
+	}
+
+	/**
+	 * Optional pricing policy.
+	 */
+	public function get_pricing_policy(): ?Pricing_Policy {
+		return $this->pricing_policy;
+	}
+
+	/**
+	 * Set the pricing policy.
+	 *
+	 * @param Pricing_Policy|null $pricing_policy Pricing policy.
+	 * @throws InvalidArgumentException If pricing_policy entries fail validation.
+	 */
+	public function set_pricing_policy( ?Pricing_Policy $pricing_policy ): void {
+		if ( null !== $pricing_policy ) {
+			self::validate_pricing_policy( $pricing_policy );
+		}
+		$this->pricing_policy = $pricing_policy;
+	}
+
+	/**
+	 * Plan category.
+	 */
+	public function get_category(): string {
+		return $this->category;
+	}
+
+	/**
+	 * Set the plan category.
+	 *
+	 * @param string $category Plan category.
+	 */
+	public function set_category( string $category ): void {
+		$this->category = $category;
+	}
+
+	/**
+	 * Owning extension slug, or null.
+	 */
+	public function get_extension_slug(): ?string {
+		return $this->extension_slug;
+	}
+
+	/**
+	 * Apply this plan's pricing policy (if any) to a base price for the cycle.
+	 *
+	 * When no pricing policy is set, returns `$base_price` unchanged.
+	 *
+	 * @param float $base_price The product's base price for this cycle.
+	 * @param int   $cycle      1-indexed cycle number (1 = first billing cycle).
+	 */
+	public function calculate_price( float $base_price, int $cycle = 1 ): float {
+		if ( null === $this->pricing_policy ) {
+			return $base_price;
+		}
+
+		return $this->pricing_policy->calculate_price( $base_price, $cycle );
+	}
+
+	/**
+	 * Serialize to the storage column shape (excluding generated id/timestamps).
+	 *
+	 * Policy value objects are returned as arrays; the repository JSON-encodes them.
+	 *
+	 * @return array<string, mixed>
+	 */
+	public function to_storage(): array {
+		return array(
+			'group_id'        => $this->group_id,
+			'name'            => $this->name,
+			'description'     => $this->description,
+			'options'         => $this->options,
+			'billing_policy'  => $this->billing_policy->to_array(),
+			'delivery_policy' => null !== $this->delivery_policy ? $this->delivery_policy->to_array() : null,
+			'pricing_policy'  => null !== $this->pricing_policy ? $this->pricing_policy->to_array() : null,
+			'category'        => $this->category,
+			'extension_slug'  => $this->extension_slug,
+		);
+	}
+
+	/**
+	 * Validate every entry in a pricing policy's policies[] and one_time_fees[].
+	 *
+	 * Rules:
+	 *  - policies[].type is one of percentage, fixed_amount, price.
+	 *  - policies[].value is numeric and non-negative; percentage is capped at 100.
+	 *  - one_time_fees[].amount is numeric and non-negative.
+	 *  - one_time_fees[].taxable is a bool.
+	 *  - one_time_fees[].tax_class is string or null (preserves '' != null).
+	 *  - one_time_fees[].kind is intentionally not whitelisted - consumers extend
+	 *    with namespaced kinds.
+	 *
+	 * @param Pricing_Policy $pricing_policy Policy to validate.
+	 * @throws InvalidArgumentException With a message naming the offending entry index.
+	 */
+	private static function validate_pricing_policy( Pricing_Policy $pricing_policy ): void {
+		foreach ( $pricing_policy->get_policies() as $index => $entry ) {
+			if ( ! is_array( $entry ) ) {
+				throw new InvalidArgumentException(
+					sprintf( 'pricing_policy.policies[%d]: must be an array, got %s', (int) $index, gettype( $entry ) )
+				);
+			}
+
+			$type           = $entry['type'] ?? null;
+			$value          = $entry['value'] ?? null;
+			$starting_cycle = $entry['starting_cycle'] ?? null;
+
+			if ( ! is_string( $type ) || ! in_array( $type, self::ALLOWED_POLICY_TYPES, true ) ) {
+				$shown = is_scalar( $type ) ? (string) $type : gettype( $type );
+				throw new InvalidArgumentException(
+					sprintf( 'pricing_policy.policies[%d]: invalid type %s', (int) $index, $shown )
+				);
+			}
+
+			if ( ! is_numeric( $value ) ) {
+				throw new InvalidArgumentException(
+					sprintf( 'pricing_policy.policies[%d]: value must be numeric, got %s', (int) $index, gettype( $value ) )
+				);
+			}
+
+			$value = (float) $value;
+
+			if ( $value < 0 ) {
+				throw new InvalidArgumentException(
+					sprintf( 'pricing_policy.policies[%d]: %s value must be non-negative, got %s', (int) $index, $type, $value )
+				);
+			}
+
+			if ( 'percentage' === $type && $value > 100 ) {
+				throw new InvalidArgumentException(
+					sprintf( 'pricing_policy.policies[%d]: percentage must not exceed 100, got %s', (int) $index, $value )
+				);
+			}
+
+			if ( null !== $starting_cycle ) {
+				if ( ! is_int( $starting_cycle ) ) {
+					throw new InvalidArgumentException(
+						sprintf( 'pricing_policy.policies[%d]: starting_cycle must be an integer, got %s', (int) $index, gettype( $starting_cycle ) )
+					);
+				}
+
+				if ( $starting_cycle < 1 ) {
+					throw new InvalidArgumentException(
+						sprintf( 'pricing_policy.policies[%d]: starting_cycle must be at least 1, got %d', (int) $index, $starting_cycle )
+					);
+				}
+			}
+		}
+
+		foreach ( $pricing_policy->get_one_time_fees() as $index => $entry ) {
+			$amount    = $entry['amount'] ?? null;
+			$taxable   = $entry['taxable'] ?? null;
+			$tax_class = $entry['tax_class'] ?? null;
+
+			if ( ! is_numeric( $amount ) ) {
+				throw new InvalidArgumentException(
+					sprintf( 'pricing_policy.one_time_fees[%d]: amount must be numeric, got %s', (int) $index, gettype( $amount ) )
+				);
+			}
+
+			if ( (float) $amount < 0 ) {
+				throw new InvalidArgumentException(
+					sprintf( 'pricing_policy.one_time_fees[%d]: amount must be non-negative, got %s', (int) $index, $amount )
+				);
+			}
+
+			if ( ! is_bool( $taxable ) ) {
+				throw new InvalidArgumentException(
+					sprintf( 'pricing_policy.one_time_fees[%d]: taxable must be a bool, got %s', (int) $index, gettype( $taxable ) )
+				);
+			}
+
+			if ( null !== $tax_class && ! is_string( $tax_class ) ) {
+				throw new InvalidArgumentException(
+					sprintf( 'pricing_policy.one_time_fees[%d]: tax_class must be string or null, got %s', (int) $index, gettype( $tax_class ) )
+				);
+			}
+		}
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/class-billing-policy.php b/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/class-billing-policy.php
new file mode 100644
index 00000000000..56c13f9e75a
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/class-billing-policy.php
@@ -0,0 +1,318 @@
+<?php
+/**
+ * Billing_Policy - typed value object for a plan's billing cadence and trial.
+ *
+ * Mirrors the `billing_policy` JSON column shape. Shape:
+ *   {
+ *     period:         'day' | 'week' | 'month' | 'year',
+ *     interval:       int,
+ *     min_cycles:     ?int,
+ *     max_cycles:     ?int,
+ *     trial_duration: { length: int, unit: 'day'|'week'|'month'|'year' } | null
+ *   }
+ *
+ * Trial is a native field: the first cycle's billing date is delayed by the
+ * trial at contract creation rather than modelled as a discount.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject;
+
+use DateTimeImmutable;
+use DateTimeZone;
+use DomainException;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Billing_Policy value object.
+ *
+ * Immutable. Construct via {@see self::from_array()} when hydrating from a
+ * stored row, or via the constructor when building one in code.
+ */
+final class Billing_Policy {
+
+	/**
+	 * Period unit: 'day' | 'week' | 'month' | 'year'.
+	 *
+	 * @var string
+	 */
+	private $period;
+
+	/**
+	 * Period count (e.g. 2 with 'week' = every 2 weeks).
+	 *
+	 * @var int
+	 */
+	private $interval;
+
+	/**
+	 * Minimum cycles before cancellation is allowed; null if unbounded below.
+	 *
+	 * @var int|null
+	 */
+	private $min_cycles;
+
+	/**
+	 * Total cycles before the contract ends; null if open-ended.
+	 *
+	 * @var int|null
+	 */
+	private $max_cycles;
+
+	/**
+	 * Native trial; null if no trial.
+	 *
+	 * @var array{length: int, unit: string}|null
+	 */
+	private $trial_duration;
+
+	/**
+	 * Build a billing policy.
+	 *
+	 * @param string                                $period         One of 'day' | 'week' | 'month' | 'year'.
+	 * @param int                                   $interval       Period count.
+	 * @param int|null                              $min_cycles     Minimum cycles before cancellation is allowed.
+	 * @param int|null                              $max_cycles     Total cycles before the contract ends.
+	 * @param array{length: int, unit: string}|null $trial_duration Native trial; null if none.
+	 */
+	public function __construct( string $period, int $interval, ?int $min_cycles, ?int $max_cycles, ?array $trial_duration ) {
+		$this->validate_min_max_cycles( $min_cycles, $max_cycles );
+
+		$this->period         = $period;
+		$this->interval       = $interval;
+		$this->min_cycles     = $min_cycles;
+		$this->max_cycles     = $max_cycles;
+		$this->trial_duration = self::normalize_trial_duration( $trial_duration );
+	}
+
+	/**
+	 * Hydrate from the JSON-decoded `billing_policy` column shape.
+	 *
+	 * Missing nullable keys default to null. `period` and `interval` are required.
+	 *
+	 * @param array<string, mixed> $data Decoded billing_policy row.
+	 * @throws DomainException If the data is not valid.
+	 */
+	public static function from_array( array $data ): self {
+		if ( ! array_key_exists( 'period', $data ) ) {
+			throw new DomainException( 'Billing_Policy: period is required, but not supplied.' );
+		}
+		if ( ! array_key_exists( 'interval', $data ) ) {
+			throw new DomainException( 'Billing_Policy: interval is required, but not supplied.' );
+		}
+		if ( ! is_string( $data['period'] ) ) {
+			throw new DomainException( 'Billing_Policy: period must be a string, got ' . gettype( $data['period'] ) . '.' );
+		}
+		if ( ! is_int( $data['interval'] ) ) {
+			throw new DomainException( 'Billing_Policy: interval must be an integer, got ' . gettype( $data['interval'] ) . '.' );
+		}
+
+		$trial = $data['trial_duration'] ?? null;
+		if ( null !== $trial && ! is_array( $trial ) ) {
+			throw new DomainException(
+				sprintf( 'Billing_Policy: trial_duration must be null or an array, got %s.', wp_json_encode( $trial ) )
+			);
+		}
+
+		$trial = self::normalize_trial_duration( $trial );
+
+		return new self(
+			(string) $data['period'],
+			(int) $data['interval'],
+			isset( $data['min_cycles'] ) ? (int) $data['min_cycles'] : null,
+			isset( $data['max_cycles'] ) ? (int) $data['max_cycles'] : null,
+			$trial
+		);
+	}
+
+	/**
+	 * Period unit: 'day' | 'week' | 'month' | 'year'.
+	 */
+	public function get_period(): string {
+		return $this->period;
+	}
+
+	/**
+	 * Period count. Together with `period` defines cadence.
+	 */
+	public function get_interval(): int {
+		return $this->interval;
+	}
+
+	/**
+	 * Minimum cycles before cancellation is allowed; null if unbounded below.
+	 */
+	public function get_min_cycles(): ?int {
+		return $this->min_cycles;
+	}
+
+	/**
+	 * Total cycles before the contract ends; null if open-ended.
+	 */
+	public function get_max_cycles(): ?int {
+		return $this->max_cycles;
+	}
+
+	/**
+	 * Native trial duration, or null if no trial.
+	 *
+	 * @return array{length: int, unit: string}|null
+	 */
+	public function get_trial_duration(): ?array {
+		return $this->trial_duration;
+	}
+
+	/**
+	 * Compute the next renewal moment by adding the policy's cadence to `$anchor`.
+	 *
+	 * Trial duration is not applied here. The result is normalized to UTC and
+	 * period semantics are calendar-aware (matching DateTimeImmutable::modify()):
+	 * adding 1 month to 2026-01-31 yields 2026-03-03, not 2026-02-31.
+	 *
+	 * @param DateTimeImmutable $anchor The moment the next cycle is computed from.
+	 * @return DateTimeImmutable The next renewal moment in UTC.
+	 * @throws DomainException If `period` is unknown or `interval` is not positive.
+	 */
+	public function compute_next_renewal_from( DateTimeImmutable $anchor ): DateTimeImmutable {
+		if ( $this->interval <= 0 ) {
+			throw new DomainException(
+				sprintf( 'Billing_Policy::compute_next_renewal_from(): interval must be positive, got %d.', $this->interval )
+			);
+		}
+
+		$unit = $this->normalize_unit( $this->period, 'period' );
+		$utc  = $anchor->setTimezone( new DateTimeZone( 'UTC' ) );
+
+		return $utc->modify( sprintf( '+%d %s', $this->interval, $unit ) );
+	}
+
+	/**
+	 * Compute the first renewal moment for a freshly-created contract.
+	 *
+	 * Honours the policy's native trial: when set, the first cycle's billing
+	 * date is the end of the trial. With no trial this delegates to
+	 * {@see self::compute_next_renewal_from()} so there is one cadence-math path.
+	 *
+	 * @param DateTimeImmutable $contract_start Moment the contract was created.
+	 * @return DateTimeImmutable The first renewal moment in UTC.
+	 * @throws DomainException If trial length is not positive or trial unit is unknown.
+	 */
+	public function compute_first_renewal_from( DateTimeImmutable $contract_start ): DateTimeImmutable {
+		if ( null === $this->trial_duration ) {
+			return $this->compute_next_renewal_from( $contract_start );
+		}
+
+		$length = (int) $this->trial_duration['length'];
+		$unit   = (string) $this->trial_duration['unit'];
+
+		if ( $length <= 0 ) {
+			throw new DomainException(
+				sprintf( 'Billing_Policy::compute_first_renewal_from(): trial length must be positive, got %d.', $length )
+			);
+		}
+
+		$normalized_unit = $this->normalize_unit( $unit, 'trial unit' );
+
+		return $contract_start
+			->setTimezone( new DateTimeZone( 'UTC' ) )
+			->modify( sprintf( '+%d %s', $length, $normalized_unit ) );
+	}
+
+	/**
+	 * Serialize back to the JSON column shape. Lossless round-trip with from_array().
+	 *
+	 * @return array<string, mixed>
+	 */
+	public function to_array(): array {
+		return array(
+			'period'         => $this->period,
+			'interval'       => $this->interval,
+			'min_cycles'     => $this->min_cycles,
+			'max_cycles'     => $this->max_cycles,
+			'trial_duration' => $this->trial_duration,
+		);
+	}
+
+	/**
+	 * Validate and pass through a period/trial unit.
+	 *
+	 * @param string $unit  The raw unit.
+	 * @param string $label Where the unit came from, for the error message.
+	 * @throws DomainException If the unit is not one of day/week/month/year.
+	 */
+	private function normalize_unit( string $unit, string $label ): string {
+		if ( ! in_array( $unit, array( 'day', 'week', 'month', 'year' ), true ) ) {
+			throw new DomainException(
+				sprintf( 'Billing_Policy: invalid %s "%s".', $label, $unit )
+			);
+		}
+
+		return $unit;
+	}
+
+	/**
+	 * Validate the min_cycles and max_cycles fields.
+	 *
+	 * @param int|null $min_cycles Minimum cycles before cancellation is allowed.
+	 * @param int|null $max_cycles Total cycles before the contract ends.
+	 * @throws DomainException If min_cycles or max_cycles are not valid.
+	 */
+	private function validate_min_max_cycles( ?int $min_cycles, ?int $max_cycles ): void {
+		if ( null !== $min_cycles && $min_cycles < 0 ) {
+			throw new DomainException(
+				sprintf( 'Billing_Policy: min_cycles must be 0 or greater, got %d.', $min_cycles )
+			);
+		}
+
+		if ( null !== $max_cycles && $max_cycles < 0 ) {
+			throw new DomainException(
+				sprintf( 'Billing_Policy: max_cycles must be 0 or greater, got %d.', $max_cycles )
+			);
+		}
+
+		if ( null !== $min_cycles && null !== $max_cycles && $min_cycles > $max_cycles ) {
+			throw new DomainException(
+				sprintf( 'Billing_Policy: min_cycles cannot exceed max_cycles, got %d and %d.', $min_cycles, $max_cycles )
+			);
+		}
+	}
+
+	/**
+	 * Normalize the trial duration.
+	 *
+	 * @param array{length: int, unit: string}|null $trial_duration The trial duration.
+	 * @return array{length: int, unit: string}|null The normalized trial duration.
+	 * @throws DomainException If the trial duration is not valid.
+	 */
+	private static function normalize_trial_duration( ?array $trial_duration ): ?array {
+		if ( null === $trial_duration ) {
+			return null;
+		}
+
+		if ( ! array_key_exists( 'length', $trial_duration ) ) {
+			throw new DomainException( "Billing_Policy: trial_duration['length'] is required." );
+		}
+		if ( ! array_key_exists( 'unit', $trial_duration ) ) {
+			throw new DomainException( "Billing_Policy: trial_duration['unit'] is required." );
+		}
+		if ( ! is_int( $trial_duration['length'] ) ) {
+			throw new DomainException(
+				sprintf( "Billing_Policy: trial_duration['length'] must be an integer, got %s.", gettype( $trial_duration['length'] ) )
+			);
+		}
+		if ( ! is_string( $trial_duration['unit'] ) ) {
+			throw new DomainException(
+				sprintf( "Billing_Policy: trial_duration['unit'] must be a string, got %s.", gettype( $trial_duration['unit'] ) )
+			);
+		}
+
+		return array(
+			'length' => (int) $trial_duration['length'],
+			'unit'   => (string) $trial_duration['unit'],
+		);
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/class-delivery-policy.php b/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/class-delivery-policy.php
new file mode 100644
index 00000000000..8e431f83a21
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/class-delivery-policy.php
@@ -0,0 +1,127 @@
+<?php
+/**
+ * Delivery_Policy - typed value object for a plan's delivery anchors, cutoff,
+ * and intent.
+ *
+ * Mirrors the `delivery_policy` JSON column shape, deliberately thin for now.
+ * Shape:
+ *   {
+ *     anchors: [{ type: 'MONTHDAY', day: int }, { type: 'YEARDAY', day: int, month: int }, ...],
+ *     cutoff:  ?mixed,
+ *     intent:  ?mixed
+ *   }
+ *
+ * The shipping/delivery policy parameter set is a new concept still being
+ * designed, so `cutoff` and `intent` are passed through verbatim and anchor
+ * entries stay as plain associative arrays until a call site needs typed access.
+ *
+ * Lives in the WordPress-free Core zone.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Delivery_Policy value object.
+ *
+ * Immutable. Construct via {@see self::from_array()} when hydrating from a
+ * stored row, or via the constructor when building one in code.
+ */
+final class Delivery_Policy {
+
+	/**
+	 * Anchor entries. Each: `{type, day, month?}`.
+	 *
+	 * @var array<int, array<string, mixed>>
+	 */
+	private $anchors;
+
+	/**
+	 * Cutoff window - shape to be designed; passed through verbatim.
+	 *
+	 * @var mixed
+	 */
+	private $cutoff;
+
+	/**
+	 * Delivery intent - shape to be designed; passed through verbatim.
+	 *
+	 * @var mixed
+	 */
+	private $intent;
+
+	/**
+	 * Build a delivery policy.
+	 *
+	 * @param array<int, array<string, mixed>> $anchors Anchor entries.
+	 * @param mixed                            $cutoff  Cutoff window.
+	 * @param mixed                            $intent  Delivery intent.
+	 */
+	public function __construct( array $anchors, $cutoff, $intent ) {
+		$this->anchors = $anchors;
+		$this->cutoff  = $cutoff;
+		$this->intent  = $intent;
+	}
+
+	/**
+	 * Hydrate from the JSON-decoded `delivery_policy` column shape.
+	 *
+	 * Missing keys default to safe values - empty array for `anchors`, null for
+	 * `cutoff` and `intent`.
+	 *
+	 * @param array<string, mixed> $data Decoded delivery_policy row.
+	 */
+	public static function from_array( array $data ): self {
+		$anchors = is_array( $data['anchors'] ?? null ) ? $data['anchors'] : array();
+		return new self(
+			$anchors,
+			$data['cutoff'] ?? null,
+			$data['intent'] ?? null
+		);
+	}
+
+	/**
+	 * Anchor entries describing when in the cycle a charge fires.
+	 *
+	 * @return array<int, array<string, mixed>>
+	 */
+	public function get_anchors(): array {
+		return $this->anchors;
+	}
+
+	/**
+	 * Cutoff window. Shape to be designed; returned verbatim.
+	 *
+	 * @return mixed
+	 */
+	public function get_cutoff() {
+		return $this->cutoff;
+	}
+
+	/**
+	 * Delivery intent. Shape to be designed; returned verbatim.
+	 *
+	 * @return mixed
+	 */
+	public function get_intent() {
+		return $this->intent;
+	}
+
+	/**
+	 * Serialize back to the JSON column shape. Lossless round-trip with from_array().
+	 *
+	 * @return array<string, mixed>
+	 */
+	public function to_array(): array {
+		return array(
+			'anchors' => $this->anchors,
+			'cutoff'  => $this->cutoff,
+			'intent'  => $this->intent,
+		);
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/class-instrument-ref.php b/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/class-instrument-ref.php
new file mode 100644
index 00000000000..99f3cf6d963
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/class-instrument-ref.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Instrument_Ref - an immutable reference to a stored payment instrument.
+ *
+ * Carries the payment token id plus the gateway code and human-readable title
+ * frozen at the time the instrument was attached. The Core zone never loads a
+ * live payment token; the Payments host binding resolves the reference when a
+ * charge is attempted.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Instrument_Ref value object.
+ *
+ * Immutable. A null token id covers gateways that do not expose a stored token
+ * (for example some manual gateways).
+ */
+final class Instrument_Ref {
+
+	/**
+	 * Payment token id, or null when the gateway exposes no token.
+	 *
+	 * @var int|null
+	 */
+	private $token_id;
+
+	/**
+	 * Gateway code (for example 'woocommerce_payments').
+	 *
+	 * @var string|null
+	 */
+	private $gateway;
+
+	/**
+	 * Human-readable gateway title, frozen at checkout time.
+	 *
+	 * @var string|null
+	 */
+	private $title;
+
+	/**
+	 * Build an instrument reference.
+	 *
+	 * @param int|null    $token_id Payment token id, or null.
+	 * @param string|null $gateway  Gateway code.
+	 * @param string|null $title    Human-readable gateway title.
+	 */
+	public function __construct( ?int $token_id, ?string $gateway = null, ?string $title = null ) {
+		$this->token_id = $token_id;
+		$this->gateway  = $gateway;
+		$this->title    = $title;
+	}
+
+	/**
+	 * The referenced payment token id, or null.
+	 */
+	public function get_token_id(): ?int {
+		return $this->token_id;
+	}
+
+	/**
+	 * The gateway code, or null.
+	 */
+	public function get_gateway(): ?string {
+		return $this->gateway;
+	}
+
+	/**
+	 * The human-readable gateway title, or null.
+	 */
+	public function get_title(): ?string {
+		return $this->title;
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/class-order-ref.php b/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/class-order-ref.php
new file mode 100644
index 00000000000..575b84c7179
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/class-order-ref.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Order_Ref - an immutable reference to a WooCommerce order by id.
+ *
+ * The Core zone never loads a live order object; it holds a reference and
+ * commands effects through the Orders host binding in the integration layer.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Order_Ref value object.
+ *
+ * Immutable identity wrapper.
+ */
+final class Order_Ref {
+
+	/**
+	 * Order id.
+	 *
+	 * @var int
+	 */
+	private $id;
+
+	/**
+	 * Build an order reference.
+	 *
+	 * @param int $id Order id.
+	 * @throws \InvalidArgumentException If the order id is not greater than 0.
+	 */
+	public function __construct( int $id ) {
+		if ( $id <= 0 ) {
+			throw new \InvalidArgumentException( 'Order id must be greater than 0.' );
+		}
+		$this->id = $id;
+	}
+
+	/**
+	 * The referenced order id.
+	 */
+	public function get_id(): int {
+		return $this->id;
+	}
+
+	/**
+	 * Value equality by id.
+	 *
+	 * @param Order_Ref $other Reference to compare against.
+	 */
+	public function equals( Order_Ref $other ): bool {
+		return $this->id === $other->id;
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/class-pricing-policy.php b/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/class-pricing-policy.php
new file mode 100644
index 00000000000..d5004faf678
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/class-pricing-policy.php
@@ -0,0 +1,185 @@
+<?php
+/**
+ * Pricing_Policy - typed value object for a plan's recurring price adjustments
+ * and one-time fees.
+ *
+ * Mirrors the `pricing_policy` JSON column shape. Shape:
+ *   {
+ *     policies: [
+ *       { type: 'percentage'|'fixed_amount'|'price', value: float, starting_cycle?: int },
+ *       ...
+ *     ],
+ *     one_time_fees: [
+ *       { kind: string, amount: float, taxable: bool, tax_class: string|null },
+ *       ...
+ *     ]
+ *   }
+ *
+ * `tax_class` empty-string semantics: `''` means the store's "Standard" class
+ * (the implicit default), not "no class." `null` is reserved for a fee that is
+ * genuinely untaxed. The two are not interchangeable - round-trip preserves
+ * whichever was supplied.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Pricing_Policy value object.
+ *
+ * Immutable. The plan column itself is nullable when neither policies nor fees
+ * apply; the value object never represents that absence - it always holds two
+ * arrays, possibly both empty.
+ */
+final class Pricing_Policy {
+
+	/**
+	 * Recurring price adjustments, applied in array order.
+	 *
+	 * @var array<int, array{type: string, value: float, starting_cycle: ?int}>
+	 */
+	private $policies;
+
+	/**
+	 * One-time fees charged at contract creation.
+	 *
+	 * @var array<int, array{kind: string, amount: float, taxable: bool, tax_class: string|null}>
+	 */
+	private $one_time_fees;
+
+	/**
+	 * Build a pricing policy.
+	 *
+	 * @param array<int, array{type: string, value: float, starting_cycle?: int}>                   $policies      Recurring price adjustments.
+	 * @param array<int, array{kind: string, amount: float, taxable: bool, tax_class: string|null}> $one_time_fees One-time fees.
+	 */
+	public function __construct( array $policies, array $one_time_fees ) {
+		$this->policies      = $policies;
+		$this->one_time_fees = $one_time_fees;
+	}
+
+	/**
+	 * Hydrate from the JSON-decoded `pricing_policy` column shape.
+	 *
+	 * Missing top-level keys default to empty arrays. Numeric values are
+	 * normalized to float so a whole-number round-trip does not silently drift
+	 * from float to int and break type-strict comparisons downstream.
+	 *
+	 * @param array<string, mixed> $data Decoded pricing_policy row.
+	 */
+	public static function from_array( array $data ): self {
+		$raw_policies = is_array( $data['policies'] ?? null ) ? $data['policies'] : array();
+		$policies     = array_values(
+			array_filter(
+				array_map(
+					static function ( $entry ): ?array {
+						if ( ! is_array( $entry ) ) {
+							return null;
+						}
+						if ( isset( $entry['value'] ) && is_numeric( $entry['value'] ) ) {
+							$entry['value'] = (float) $entry['value'];
+						}
+						return $entry;
+					},
+					$raw_policies
+				),
+				static function ( $entry ): bool {
+					return is_array( $entry );
+				}
+			)
+		);
+
+		$fees = array_map(
+			static function ( array $entry ): array {
+				if ( isset( $entry['amount'] ) && is_numeric( $entry['amount'] ) ) {
+					$entry['amount'] = (float) $entry['amount'];
+				}
+				return $entry;
+			},
+			$data['one_time_fees'] ?? array()
+		);
+
+		return new self( $policies, $fees );
+	}
+
+	/**
+	 * Recurring price adjustments. Each entry: `{type, value, starting_cycle?}`.
+	 *
+	 * @return array<int, array{type: string, value: float, starting_cycle?: int}>
+	 */
+	public function get_policies(): array {
+		return $this->policies;
+	}
+
+	/**
+	 * One-time fees charged at contract creation.
+	 *
+	 * @return array<int, array{kind: string, amount: float, taxable: bool, tax_class: string|null}>
+	 */
+	public function get_one_time_fees(): array {
+		return $this->one_time_fees;
+	}
+
+	/**
+	 * Apply the recurring policy chain to a base price for the given cycle.
+	 *
+	 * Semantics:
+	 *  - Empty `policies` returns `$base_price` unchanged.
+	 *  - `type: 'percentage'`   -> `base_price * (100 - value) / 100`.
+	 *  - `type: 'fixed_amount'` -> `max(0, base_price - value)` (clamped at zero).
+	 *  - `type: 'price'`        -> `value` (replaces base price entirely).
+	 *  - `starting_cycle` gate: skip the entry when `$cycle < starting_cycle`.
+	 *    A missing `starting_cycle` means the entry applies to all cycles.
+	 *  - Entries are applied in array order; later entries operate on the result.
+	 *
+	 * One-time fees are intentionally not applied here.
+	 *
+	 * @param float $base_price The product's base price for this cycle.
+	 * @param int   $cycle      1-indexed cycle number (1 = first billing cycle).
+	 */
+	public function calculate_price( float $base_price, int $cycle = 1 ): float {
+		$price = $base_price;
+
+		foreach ( $this->policies as $policy ) {
+			if ( isset( $policy['starting_cycle'] ) && $cycle < (int) $policy['starting_cycle'] ) {
+				continue;
+			}
+
+			$type  = (string) ( $policy['type'] ?? '' );
+			$value = (float) ( $policy['value'] ?? 0 );
+
+			switch ( $type ) {
+				case 'percentage':
+					$price = $price * ( 100 - $value ) / 100;
+					break;
+				case 'fixed_amount':
+					$price = max( 0.0, $price - $value );
+					break;
+				case 'price':
+					$price = $value;
+					break;
+				default:
+					break;
+			}
+		}
+
+		return $price;
+	}
+
+	/**
+	 * Serialize back to the JSON column shape. Lossless round-trip with from_array().
+	 *
+	 * @return array<string, mixed>
+	 */
+	public function to_array(): array {
+		return array(
+			'policies'      => $this->policies,
+			'one_time_fees' => $this->one_time_fees,
+		);
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/class-contract-repository.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/class-contract-repository.php
new file mode 100644
index 00000000000..57236da893d
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/class-contract-repository.php
@@ -0,0 +1,271 @@
+<?php
+/**
+ * Contract_Repository - persistence for {@see Contract} entities.
+ *
+ * Lives in the integration layer: it owns the $wpdb access and spans the four
+ * contract tables (contract row, items, addresses, meta), hydrating the Core
+ * entity from clean arrays.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage;
+
+use Automattic\WooCommerce\SubscriptionsEngine\Core\Entity\Contract;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Contract repository.
+ */
+final class Contract_Repository {
+
+	/**
+	 * Address columns persisted to the addresses table.
+	 *
+	 * @var array<int, string>
+	 */
+	private const ADDRESS_COLUMNS = array(
+		'first_name',
+		'last_name',
+		'company',
+		'address_1',
+		'address_2',
+		'city',
+		'state',
+		'postcode',
+		'country',
+		'email',
+		'phone',
+	);
+
+	/**
+	 * Insert a new contract and its items, addresses, and meta.
+	 *
+	 * @param Contract $contract Contract to insert.
+	 * @return int The new contract id.
+	 * @throws \RuntimeException If the contract insert fails.
+	 */
+	public function insert( Contract $contract ): int {
+		global $wpdb;
+
+		$now  = gmdate( 'Y-m-d H:i:s' );
+		$data = $contract->to_storage();
+
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+		$inserted = $wpdb->insert(
+			Schema_Installer::get_table_name( Schema_Installer::TABLE_CONTRACTS ),
+			array_merge(
+				$data,
+				array(
+					'date_created_gmt' => $now,
+					'date_updated_gmt' => $now,
+				)
+			)
+		);
+
+		if ( false === $inserted ) {
+			throw new \RuntimeException( 'Failed to insert contract.' );
+		}
+
+		$id = (int) $wpdb->insert_id;
+		$contract->set_id( $id );
+
+		$this->insert_items( $id, $contract->get_items() );
+		$this->insert_addresses( $id, $contract->get_addresses() );
+		$this->insert_meta( $id, $contract->get_meta() );
+
+		return $id;
+	}
+
+	/**
+	 * Fetch a contract by id, including its items, addresses, and meta.
+	 *
+	 * @param int $id Contract id.
+	 * @return Contract|null Hydrated contract, or null if not found.
+	 */
+	public function find( int $id ): ?Contract {
+		global $wpdb;
+
+		$contracts = Schema_Installer::get_table_name( Schema_Installer::TABLE_CONTRACTS );
+
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+		$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$contracts} WHERE id = %d", $id ), ARRAY_A );
+
+		if ( null === $row ) {
+			return null;
+		}
+
+		return Contract::from_storage(
+			$row,
+			$this->find_items( $id ),
+			$this->find_addresses( $id ),
+			$this->find_meta( $id )
+		);
+	}
+
+	/**
+	 * Delete a contract and its child rows.
+	 *
+	 * @param int $id Contract id.
+	 * @return bool True when the contract row was removed.
+	 */
+	public function delete( int $id ): bool {
+		global $wpdb;
+
+		foreach ( array(
+			Schema_Installer::TABLE_CONTRACT_ITEMS,
+			Schema_Installer::TABLE_CONTRACT_ADDRESSES,
+			Schema_Installer::TABLE_CONTRACT_META,
+		) as $child ) {
+			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+			$wpdb->delete( Schema_Installer::get_table_name( $child ), array( 'contract_id' => $id ) );
+		}
+
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+		$deleted = $wpdb->delete( Schema_Installer::get_table_name( Schema_Installer::TABLE_CONTRACTS ), array( 'id' => $id ) );
+
+		return (bool) $deleted;
+	}
+
+	/**
+	 * Insert line items for a contract.
+	 *
+	 * @param int                              $contract_id Contract id.
+	 * @param array<int, array<string, mixed>> $items       Item rows.
+	 */
+	private function insert_items( int $contract_id, array $items ): void {
+		global $wpdb;
+
+		foreach ( $items as $item ) {
+			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+			$wpdb->insert(
+				Schema_Installer::get_table_name( Schema_Installer::TABLE_CONTRACT_ITEMS ),
+				array(
+					'contract_id'  => $contract_id,
+					'item_name'    => (string) ( $item['item_name'] ?? '' ),
+					'item_type'    => (string) ( $item['item_type'] ?? 'line_item' ),
+					'product_id'   => isset( $item['product_id'] ) ? (int) $item['product_id'] : null,
+					'variation_id' => isset( $item['variation_id'] ) ? (int) $item['variation_id'] : null,
+					'quantity'     => (string) ( $item['quantity'] ?? '1' ),
+					'subtotal'     => (string) ( $item['subtotal'] ?? '0' ),
+					'total'        => (string) ( $item['total'] ?? '0' ),
+					'taxes'        => isset( $item['taxes'] ) ? wp_json_encode( $item['taxes'] ) : null,
+				)
+			);
+		}
+	}
+
+	/**
+	 * Insert addresses for a contract.
+	 *
+	 * @param int                                 $contract_id Contract id.
+	 * @param array<string, array<string, mixed>> $addresses   Address rows keyed by type.
+	 */
+	private function insert_addresses( int $contract_id, array $addresses ): void {
+		global $wpdb;
+
+		foreach ( $addresses as $type => $address ) {
+			$record = array(
+				'contract_id'  => $contract_id,
+				'address_type' => (string) $type,
+			);
+
+			foreach ( self::ADDRESS_COLUMNS as $column ) {
+				$record[ $column ] = isset( $address[ $column ] ) ? (string) $address[ $column ] : null;
+			}
+
+			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+			$wpdb->insert( Schema_Installer::get_table_name( Schema_Installer::TABLE_CONTRACT_ADDRESSES ), $record );
+		}
+	}
+
+	/**
+	 * Insert meta for a contract.
+	 *
+	 * @param int                   $contract_id Contract id.
+	 * @param array<string, string> $meta        Meta as key => value.
+	 */
+	private function insert_meta( int $contract_id, array $meta ): void {
+		global $wpdb;
+
+		foreach ( $meta as $key => $value ) {
+			// These are the engine's own contract-meta columns, not post/order
+			// meta; the slow-meta-query heuristic does not apply.
+			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_meta_value
+			$wpdb->insert(
+				Schema_Installer::get_table_name( Schema_Installer::TABLE_CONTRACT_META ),
+				array(
+					'contract_id' => $contract_id,
+					'meta_key'    => (string) $key,
+					'meta_value'  => (string) $value,
+				)
+			);
+		}
+	}
+
+	/**
+	 * Load line items for a contract.
+	 *
+	 * @param int $contract_id Contract id.
+	 * @return array<int, array<string, mixed>>
+	 */
+	private function find_items( int $contract_id ): array {
+		global $wpdb;
+
+		$table = Schema_Installer::get_table_name( Schema_Installer::TABLE_CONTRACT_ITEMS );
+
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+		$rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table} WHERE contract_id = %d ORDER BY id ASC", $contract_id ), ARRAY_A );
+
+		return is_array( $rows ) ? $rows : array();
+	}
+
+	/**
+	 * Load addresses for a contract, keyed by address type.
+	 *
+	 * @param int $contract_id Contract id.
+	 * @return array<string, array<string, mixed>>
+	 */
+	private function find_addresses( int $contract_id ): array {
+		global $wpdb;
+
+		$table = Schema_Installer::get_table_name( Schema_Installer::TABLE_CONTRACT_ADDRESSES );
+
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+		$rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table} WHERE contract_id = %d", $contract_id ), ARRAY_A );
+
+		$by_type = array();
+		foreach ( is_array( $rows ) ? $rows : array() as $row ) {
+			$by_type[ (string) $row['address_type'] ] = $row;
+		}
+
+		return $by_type;
+	}
+
+	/**
+	 * Load meta for a contract as key => value.
+	 *
+	 * @param int $contract_id Contract id.
+	 * @return array<string, string>
+	 */
+	private function find_meta( int $contract_id ): array {
+		global $wpdb;
+
+		$table = Schema_Installer::get_table_name( Schema_Installer::TABLE_CONTRACT_META );
+
+		// These are the engine's own contract-meta columns, not post/order meta;
+		// the slow-meta-query heuristic does not apply.
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_meta_value
+		$rows = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM {$table} WHERE contract_id = %d", $contract_id ), ARRAY_A );
+
+		$meta = array();
+		foreach ( is_array( $rows ) ? $rows : array() as $row ) {
+			$meta[ (string) $row['meta_key'] ] = (string) $row['meta_value'];
+		}
+
+		return $meta;
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/class-plan-group-repository.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/class-plan-group-repository.php
new file mode 100644
index 00000000000..19ebaa156bb
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/class-plan-group-repository.php
@@ -0,0 +1,115 @@
+<?php
+/**
+ * Plan_Group_Repository - persistence for {@see Plan_Group} entities.
+ *
+ * The engine's tables are private API; consumers reach plan groups through the public surface.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage;
+
+use Automattic\WooCommerce\SubscriptionsEngine\Core\Entity\Plan_Group;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Plan_Group repository.
+ */
+final class Plan_Group_Repository {
+
+	/**
+	 * Insert a new plan group and stamp its id back onto the entity.
+	 *
+	 * @param Plan_Group $group Group to insert.
+	 * @return int The new group id.
+	 * @throws \RuntimeException If the insert fails.
+	 */
+	public function insert( Plan_Group $group ): int {
+		global $wpdb;
+
+		$now  = gmdate( 'Y-m-d H:i:s' );
+		$data = $group->to_storage();
+
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+		$inserted = $wpdb->insert(
+			Schema_Installer::get_table_name( Schema_Installer::TABLE_PLAN_GROUPS ),
+			array(
+				'name'             => $data['name'],
+				'merchant_code'    => $data['merchant_code'],
+				'options_display'  => wp_json_encode( $data['options_display'] ),
+				'app_id'           => $data['app_id'],
+				'date_created_gmt' => $now,
+				'date_updated_gmt' => $now,
+			)
+		);
+
+		if ( false === $inserted ) {
+			throw new \RuntimeException( 'Failed to insert plan group.' );
+		}
+
+		$id = (int) $wpdb->insert_id;
+		$group->set_id( $id );
+
+		return $id;
+	}
+
+	/**
+	 * Fetch a plan group by id.
+	 *
+	 * @param int $id Group id.
+	 * @return Plan_Group|null Hydrated group, or null if not found.
+	 */
+	public function find( int $id ): ?Plan_Group {
+		global $wpdb;
+
+		$table = Schema_Installer::get_table_name( Schema_Installer::TABLE_PLAN_GROUPS );
+
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+		$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $id ), ARRAY_A );
+
+		if ( null === $row ) {
+			return null;
+		}
+
+		$row['options_display'] = self::decode_json( $row['options_display'] );
+
+		return Plan_Group::from_storage( $row );
+	}
+
+	/**
+	 * Delete a plan group by id.
+	 *
+	 * @param int $id Group id.
+	 * @return bool True when a row was removed.
+	 */
+	public function delete( int $id ): bool {
+		global $wpdb;
+
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+		$deleted = $wpdb->delete(
+			Schema_Installer::get_table_name( Schema_Installer::TABLE_PLAN_GROUPS ),
+			array( 'id' => $id )
+		);
+
+		return (bool) $deleted;
+	}
+
+	/**
+	 * Decode a JSON column into an array, tolerating null/empty values.
+	 *
+	 * @param mixed $value Raw column value.
+	 * @return array<mixed>
+	 */
+	private static function decode_json( $value ): array {
+		if ( ! is_string( $value ) || '' === $value ) {
+			return array();
+		}
+
+		$decoded = json_decode( $value, true );
+
+		return is_array( $decoded ) ? $decoded : array();
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/class-plan-repository.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/class-plan-repository.php
new file mode 100644
index 00000000000..e56c650cfac
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/class-plan-repository.php
@@ -0,0 +1,173 @@
+<?php
+/**
+ * Plan_Repository - persistence for {@see Plan} entities.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage;
+
+use Automattic\WooCommerce\SubscriptionsEngine\Core\Entity\Plan;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Plan repository.
+ */
+final class Plan_Repository {
+
+	/**
+	 * Policy columns stored as JSON.
+	 *
+	 * @var array<int, string>
+	 */
+	private const JSON_COLUMNS = array( 'options', 'billing_policy', 'delivery_policy', 'pricing_policy' );
+
+	/**
+	 * Insert a new plan and stamp its id back onto the entity.
+	 *
+	 * @param Plan $plan Plan to insert.
+	 * @return int The new plan id.
+	 * @throws \RuntimeException If the insert fails.
+	 */
+	public function insert( Plan $plan ): int {
+		global $wpdb;
+
+		$now  = gmdate( 'Y-m-d H:i:s' );
+		$data = $plan->to_storage();
+
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+		$inserted = $wpdb->insert(
+			Schema_Installer::get_table_name( Schema_Installer::TABLE_PLANS ),
+			array(
+				'group_id'         => $data['group_id'],
+				'name'             => $data['name'],
+				'description'      => $data['description'],
+				'options'          => wp_json_encode( $data['options'] ),
+				'billing_policy'   => wp_json_encode( $data['billing_policy'] ),
+				'delivery_policy'  => null !== $data['delivery_policy'] ? wp_json_encode( $data['delivery_policy'] ) : null,
+				'inventory_policy' => null,
+				'pricing_policy'   => null !== $data['pricing_policy'] ? wp_json_encode( $data['pricing_policy'] ) : null,
+				'category'         => $data['category'],
+				'extension_slug'   => $data['extension_slug'],
+				'date_created_gmt' => $now,
+				'date_updated_gmt' => $now,
+			)
+		);
+
+		if ( false === $inserted ) {
+			throw new \RuntimeException( 'Failed to insert plan.' );
+		}
+
+		$id = (int) $wpdb->insert_id;
+		$plan->set_id( $id );
+
+		return $id;
+	}
+
+	/**
+	 * Fetch a plan by id.
+	 *
+	 * @param int $id Plan id.
+	 * @return Plan|null Hydrated plan, or null if not found.
+	 */
+	public function find( int $id ): ?Plan {
+		global $wpdb;
+
+		$table = Schema_Installer::get_table_name( Schema_Installer::TABLE_PLANS );
+
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+		$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $id ), ARRAY_A );
+
+		if ( null === $row ) {
+			return null;
+		}
+
+		foreach ( self::JSON_COLUMNS as $column ) {
+			$row[ $column ] = self::decode_json( $row[ $column ] ?? null );
+		}
+
+		return Plan::from_storage( $row );
+	}
+
+	/**
+	 * Persist changes to an existing plan.
+	 *
+	 * @param Plan $plan Plan to update. Must have an id.
+	 * @return bool True on success.
+	 * @throws \RuntimeException If the plan has no id.
+	 */
+	public function update( Plan $plan ): bool {
+		global $wpdb;
+
+		$id = $plan->get_id();
+		if ( null === $id ) {
+			throw new \RuntimeException( 'Cannot update a plan that has no id.' );
+		}
+
+		$data = $plan->to_storage();
+
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+		$updated = $wpdb->update(
+			Schema_Installer::get_table_name( Schema_Installer::TABLE_PLANS ),
+			array(
+				'name'             => $data['name'],
+				'description'      => $data['description'],
+				'options'          => wp_json_encode( $data['options'] ),
+				'billing_policy'   => wp_json_encode( $data['billing_policy'] ),
+				'delivery_policy'  => null !== $data['delivery_policy'] ? wp_json_encode( $data['delivery_policy'] ) : null,
+				'pricing_policy'   => null !== $data['pricing_policy'] ? wp_json_encode( $data['pricing_policy'] ) : null,
+				'category'         => $data['category'],
+				'extension_slug'   => $data['extension_slug'],
+				'date_updated_gmt' => gmdate( 'Y-m-d H:i:s' ),
+			),
+			array( 'id' => $id )
+		);
+
+		return false !== $updated;
+	}
+
+	/**
+	 * Delete a plan by id.
+	 *
+	 * @param int $id Plan id.
+	 * @return bool True when a row was removed.
+	 */
+	public function delete( int $id ): bool {
+		global $wpdb;
+
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+		$deleted = $wpdb->delete(
+			Schema_Installer::get_table_name( Schema_Installer::TABLE_PLANS ),
+			array( 'id' => $id )
+		);
+
+		return (bool) $deleted;
+	}
+
+	/**
+	 * Decode a JSON column into an array.
+	 *
+	 * A SQL NULL column stays null so nullable policy columns
+	 * (delivery_policy, pricing_policy) round-trip back to null rather than to
+	 * an empty value object. A present-but-empty value decodes to an array.
+	 *
+	 * @param mixed $value Raw column value.
+	 * @return array<mixed>|null
+	 */
+	private static function decode_json( $value ): ?array {
+		if ( null === $value ) {
+			return null;
+		}
+
+		if ( ! is_string( $value ) || '' === $value ) {
+			return array();
+		}
+
+		$decoded = json_decode( $value, true );
+
+		return is_array( $decoded ) ? $decoded : array();
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/class-schema-installer.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/class-schema-installer.php
new file mode 100644
index 00000000000..3a553fc899f
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/class-schema-installer.php
@@ -0,0 +1,296 @@
+<?php
+/**
+ * Schema_Installer - owns the engine's baseline database tables.
+ *
+ * Creates and drops the plan tables (`wc_selling_plan_groups`,
+ * `wc_selling_plans`) and the contract tables (`wc_subscription_contracts`,
+ * `wc_subscription_contract_items`, `wc_subscription_contract_addresses`,
+ * `wc_subscription_contract_meta`). Mirrors the order/HPOS conventions:
+ * BIGINT UNSIGNED ids, `*_gmt` datetime columns, JSON columns for policy
+ * bundles, no foreign-key constraints.
+ *
+ * Schema is additive-only: columns shipped here are permanent.
+ *
+ * The engine is bundled rather than independently activated, so install runs
+ * through {@see self::maybe_install()} (a version-gated check on boot), not a
+ * plugin activation hook.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Schema installer and table-name resolver.
+ */
+final class Schema_Installer {
+
+	/**
+	 * Schema version. Bump when the CREATE TABLE statements change so the
+	 * version-gated install runs dbDelta again.
+	 *
+	 * 1.0.0 - baseline plan and contract tables, including the nullable `extension_slug`
+	 *         column on plans and contracts.
+	 */
+	const VERSION = '1.0.0';
+
+	/**
+	 * Option key tracking the installed schema version.
+	 */
+	const VERSION_OPTION = 'wc_subscriptions_engine_db_version';
+
+	/**
+	 * Logical table identifiers - keys map to unprefixed table names.
+	 */
+	const TABLE_PLAN_GROUPS        = 'plan_groups';
+	const TABLE_PLANS              = 'plans';
+	const TABLE_CONTRACTS          = 'contracts';
+	const TABLE_CONTRACT_ITEMS     = 'contract_items';
+	const TABLE_CONTRACT_ADDRESSES = 'contract_addresses';
+	const TABLE_CONTRACT_META      = 'contract_meta';
+
+	/**
+	 * Resolve a logical identifier to its prefixed table name.
+	 *
+	 * @param string $logical One of the TABLE_* constants.
+	 * @return string Prefixed table name.
+	 * @throws \InvalidArgumentException If $logical is unknown.
+	 */
+	public static function get_table_name( string $logical ): string {
+		global $wpdb;
+
+		$names = self::get_table_names( $wpdb->prefix );
+
+		if ( ! isset( $names[ $logical ] ) ) {
+			throw new \InvalidArgumentException(
+				sprintf( 'Unknown subscriptions-engine table identifier: %s', esc_html( $logical ) )
+			);
+		}
+
+		return $names[ $logical ];
+	}
+
+	/**
+	 * Install or upgrade the tables when the stored version is behind the code.
+	 *
+	 * Cheap to call on every boot: it is a single option read in the common case.
+	 */
+	public static function maybe_install(): void {
+		if ( self::is_current() ) {
+			return;
+		}
+
+		self::install();
+	}
+
+	/**
+	 * Install (or upgrade) the tables. Idempotent - dbDelta handles the diff.
+	 */
+	public static function install(): void {
+		global $wpdb;
+
+		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
+
+		$collate = $wpdb->get_charset_collate();
+		$names   = self::get_table_names( $wpdb->prefix );
+
+		foreach ( self::get_table_definitions( $names, $collate ) as $sql ) {
+			dbDelta( $sql );
+		}
+
+		update_option( self::VERSION_OPTION, self::VERSION );
+	}
+
+	/**
+	 * Drop the tables and clear schema metadata.
+	 *
+	 * Intended for uninstall paths only, never deactivation.
+	 */
+	public static function uninstall(): void {
+		global $wpdb;
+
+		// TODO: Determine what we should do with the tables when uninstalling - WOOSUBS-1718.
+
+		delete_option( self::VERSION_OPTION );
+	}
+
+	/**
+	 * Whether the installed schema version matches Schema_Installer::VERSION.
+	 */
+	public static function is_current(): bool {
+		return self::VERSION === get_option( self::VERSION_OPTION );
+	}
+
+	/**
+	 * Map of logical => prefixed table names, keyed by TABLE_* constants.
+	 *
+	 * Contract tables use the `wc_subscription_*` prefix (what the data
+	 * represents), while the namespace boundary is about code ownership.
+	 *
+	 * @param string $prefix Usually `$wpdb->prefix`.
+	 * @return array<string, string>
+	 */
+	private static function get_table_names( string $prefix ): array {
+		return array(
+			self::TABLE_PLAN_GROUPS        => $prefix . 'wc_selling_plan_groups',
+			self::TABLE_PLANS              => $prefix . 'wc_selling_plans',
+			self::TABLE_CONTRACTS          => $prefix . 'wc_subscription_contracts',
+			self::TABLE_CONTRACT_ITEMS     => $prefix . 'wc_subscription_contract_items',
+			self::TABLE_CONTRACT_ADDRESSES => $prefix . 'wc_subscription_contract_addresses',
+			self::TABLE_CONTRACT_META      => $prefix . 'wc_subscription_contract_meta',
+		);
+	}
+
+	/**
+	 * CREATE TABLE statements, formatted for dbDelta.
+	 *
+	 * The dbDelta function is fussy: each column on its own line, two spaces
+	 * between name and type, `KEY` (not `INDEX`), no trailing comma before
+	 * PRIMARY KEY. Do not reformat these without re-testing dbDelta diffing - it
+	 * parses with regex.
+	 *
+	 * @param array<string, string> $names   Map of logical => prefixed table names.
+	 * @param string                $collate Charset/collate clause from $wpdb.
+	 * @return array<int, string>
+	 */
+	private static function get_table_definitions( array $names, string $collate ): array {
+		$plan_groups        = $names[ self::TABLE_PLAN_GROUPS ];
+		$plans              = $names[ self::TABLE_PLANS ];
+		$contracts          = $names[ self::TABLE_CONTRACTS ];
+		$contract_items     = $names[ self::TABLE_CONTRACT_ITEMS ];
+		$contract_addresses = $names[ self::TABLE_CONTRACT_ADDRESSES ];
+		$contract_meta      = $names[ self::TABLE_CONTRACT_META ];
+
+		// `merchant_code` is UNIQUE (not just KEY) for DB-enforced idempotency on
+		// consumer-supplied codes. NULL values are allowed and treated as distinct,
+		// so consumers that do not use merchant codes are unaffected.
+		$plan_groups_sql = "CREATE TABLE {$plan_groups} (
+  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+  name VARCHAR(255) NOT NULL,
+  merchant_code VARCHAR(64) NULL,
+  options_display JSON NULL,
+  app_id VARCHAR(64) NULL,
+  date_created_gmt DATETIME NOT NULL,
+  date_updated_gmt DATETIME NOT NULL,
+  PRIMARY KEY  (id),
+  UNIQUE KEY merchant_code (merchant_code),
+  KEY app_id (app_id)
+) {$collate};";
+
+		// `extension_slug` records the registered slug of the extension that created the
+		// plan. Nullable while owner identifier/registration semantics are still
+		// open; tightened additively once decided.
+		$plans_sql = "CREATE TABLE {$plans} (
+  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+  group_id BIGINT UNSIGNED NOT NULL,
+  name VARCHAR(255) NOT NULL,
+  description TEXT NULL,
+  options JSON NOT NULL,
+  billing_policy JSON NOT NULL,
+  delivery_policy JSON NULL,
+  inventory_policy JSON NULL,
+  pricing_policy JSON NULL,
+  category VARCHAR(32) NOT NULL DEFAULT 'SUBSCRIPTION',
+  extension_slug VARCHAR(64) NULL,
+  date_created_gmt DATETIME NOT NULL,
+  date_updated_gmt DATETIME NOT NULL,
+  PRIMARY KEY  (id),
+  KEY group_id (group_id),
+  KEY category (category),
+  KEY extension_slug (extension_slug)
+) {$collate};";
+
+		// `currency` is first-class (forward-compat for multi-currency recurring;
+		// today always the store base currency). `schedule_source` distinguishes
+		// contracts whose renewals this engine owns from gateway-owned schedules.
+		// `extension_slug` mirrors the plans column. Totals follow the order PHP-property
+		// naming rather than the HPOS storage-column names.
+		$contracts_sql = "CREATE TABLE {$contracts} (
+  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+  status VARCHAR(20) NOT NULL,
+  customer_id BIGINT UNSIGNED NOT NULL,
+  currency CHAR(3) NOT NULL,
+  selling_plan_id BIGINT UNSIGNED NOT NULL,
+  origin_order_id BIGINT UNSIGNED NOT NULL,
+  extension_slug VARCHAR(64) NULL,
+  payment_method VARCHAR(100) NULL,
+  payment_method_title VARCHAR(200) NULL,
+  payment_token_id BIGINT UNSIGNED NULL,
+  billing_total DECIMAL(26,8) NOT NULL DEFAULT 0,
+  discount_total DECIMAL(26,8) NOT NULL DEFAULT 0,
+  shipping_total DECIMAL(26,8) NOT NULL DEFAULT 0,
+  tax_total DECIMAL(26,8) NOT NULL DEFAULT 0,
+  start_gmt DATETIME NOT NULL,
+  next_payment_gmt DATETIME NULL,
+  last_payment_gmt DATETIME NULL,
+  last_attempt_gmt DATETIME NULL,
+  trial_end_gmt DATETIME NULL,
+  end_gmt DATETIME NULL,
+  cycle_count INT UNSIGNED NOT NULL DEFAULT 0,
+  schedule_source VARCHAR(20) NOT NULL DEFAULT 'primitive',
+  date_created_gmt DATETIME NOT NULL,
+  date_updated_gmt DATETIME NOT NULL,
+  PRIMARY KEY  (id),
+  KEY customer_status (customer_id, status),
+  KEY due (next_payment_gmt, status),
+  KEY origin_order (origin_order_id),
+  KEY extension_slug (extension_slug)
+) {$collate};";
+
+		$contract_items_sql = "CREATE TABLE {$contract_items} (
+  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+  contract_id BIGINT UNSIGNED NOT NULL,
+  item_name VARCHAR(255) NOT NULL,
+  item_type VARCHAR(32) NOT NULL,
+  product_id BIGINT UNSIGNED NULL,
+  variation_id BIGINT UNSIGNED NULL,
+  quantity DECIMAL(12,4) NOT NULL DEFAULT 1,
+  subtotal DECIMAL(26,8) NOT NULL DEFAULT 0,
+  total DECIMAL(26,8) NOT NULL DEFAULT 0,
+  taxes JSON NULL,
+  PRIMARY KEY  (id),
+  KEY contract (contract_id)
+) {$collate};";
+
+		// One billing + one shipping address per contract: composite PK on
+		// (contract_id, address_type). Mirrors the order-addresses column shape.
+		$contract_addresses_sql = "CREATE TABLE {$contract_addresses} (
+  contract_id BIGINT UNSIGNED NOT NULL,
+  address_type VARCHAR(20) NOT NULL,
+  first_name TEXT NULL,
+  last_name TEXT NULL,
+  company TEXT NULL,
+  address_1 TEXT NULL,
+  address_2 TEXT NULL,
+  city TEXT NULL,
+  state TEXT NULL,
+  postcode TEXT NULL,
+  country TEXT NULL,
+  email VARCHAR(320) NULL,
+  phone VARCHAR(100) NULL,
+  PRIMARY KEY  (contract_id, address_type)
+) {$collate};";
+
+		$contract_meta_sql = "CREATE TABLE {$contract_meta} (
+  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+  contract_id BIGINT UNSIGNED NOT NULL,
+  meta_key VARCHAR(255) NOT NULL,
+  meta_value LONGTEXT NULL,
+  PRIMARY KEY  (id),
+  KEY contract_key (contract_id, meta_key(100))
+) {$collate};";
+
+		return array(
+			$plan_groups_sql,
+			$plans_sql,
+			$contracts_sql,
+			$contract_items_sql,
+			$contract_addresses_sql,
+			$contract_meta_sql,
+		);
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/class-bootstrap.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/class-bootstrap.php
new file mode 100644
index 00000000000..3ca7a6452b8
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/class-bootstrap.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Bootstrap - wires the engine's integration layer into WordPress.
+ *
+ * The engine is bundled rather than independently activated, so it cannot rely
+ * on a plugin activation hook to install its schema. Instead it performs a
+ * version-gated install check on boot: cheap in the common case (a single
+ * option read) and self-healing if the tables are missing or behind.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine\Integration
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Integration;
+
+use Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage\Schema_Installer;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Integration-layer bootstrap.
+ */
+final class Bootstrap {
+
+	/**
+	 * Whether hooks have already been registered, to keep init idempotent when
+	 * more than one consumer boots the engine in the same request.
+	 *
+	 * @var bool
+	 */
+	private static $initialized = false;
+
+	/**
+	 * Register the engine's WordPress hooks.
+	 */
+	public static function init(): void {
+		if ( self::$initialized ) {
+			return;
+		}
+
+		self::$initialized = true;
+
+		if ( did_action( 'init' ) ) {
+			self::maybe_install_schema();
+		} else {
+			add_action( 'init', array( __CLASS__, 'maybe_install_schema' ) );
+		}
+	}
+
+	/**
+	 * Install or upgrade the engine schema when it is missing or behind.
+	 */
+	public static function maybe_install_schema(): void {
+		Schema_Installer::maybe_install();
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/class-package.php b/packages/php/woocommerce-subscriptions-engine/src/class-package.php
new file mode 100644
index 00000000000..236adeb6978
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/src/class-package.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Main package class for the WooCommerce Subscriptions Engine.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine;
+
+use Automattic\WooCommerce\SubscriptionsEngine\Integration\Bootstrap;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Package entry point.
+ *
+ * The engine is a library bundled into WooCommerce core and consumed by the
+ * Lite and Premium packages; it is not a standalone, independently activated
+ * plugin. Consumers call {@see self::init()} during their own boot to wire the
+ * integration layer.
+ */
+final class Package {
+
+	/**
+	 * Package version.
+	 */
+	const VERSION = '0.0.1';
+
+	/**
+	 * Boot the package's integration layer.
+	 */
+	public static function init(): void {
+		Bootstrap::init();
+	}
+
+	/**
+	 * Return the version of the package.
+	 */
+	public static function get_version(): string {
+		return self::VERSION;
+	}
+
+	/**
+	 * Return the absolute path to the package root.
+	 */
+	public static function get_path(): string {
+		return dirname( __DIR__ );
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/Contract_Repository_Test.php b/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/Contract_Repository_Test.php
new file mode 100644
index 00000000000..baa7f384517
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/Contract_Repository_Test.php
@@ -0,0 +1,137 @@
+<?php
+/**
+ * Integration tests for Contract_Repository.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Tests\Integration\Integration\Storage;
+
+use Engine_Integration_Test_Case;
+use Automattic\WooCommerce\SubscriptionsEngine\Core\Entity\Contract;
+use Automattic\WooCommerce\SubscriptionsEngine\Core\Entity\Contract_Status;
+use Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage\Contract_Repository;
+
+/**
+ * @covers \Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage\Contract_Repository
+ */
+class Contract_Repository_Test extends Engine_Integration_Test_Case {
+
+	private function make_contract(): Contract {
+		return Contract::create(
+			array(
+				'customer_id'          => 42,
+				'currency'             => 'USD',
+				'selling_plan_id'      => 7,
+				'origin_order_id'      => 1001,
+				'extension_slug'       => 'lite',
+				'payment_method'       => 'woocommerce_payments',
+				'payment_method_title' => 'Credit card',
+				'payment_token_id'     => 55,
+				'billing_total'        => '19.99',
+				'start_gmt'            => '2026-06-15 00:00:00',
+				'next_payment_gmt'     => '2026-07-15 00:00:00',
+				'items'                => array(
+					array(
+						'item_name'  => 'Coffee bag',
+						'item_type'  => 'line_item',
+						'product_id' => 200,
+						'quantity'   => '1',
+						'subtotal'   => '19.99',
+						'total'      => '19.99',
+					),
+				),
+				'addresses'            => array(
+					Contract::ADDRESS_BILLING  => array(
+						'first_name' => 'Ada',
+						'last_name'  => 'Lovelace',
+						'country'    => 'US',
+						'email'      => 'ada@example.test',
+					),
+					Contract::ADDRESS_SHIPPING => array(
+						'first_name' => 'Ada',
+						'last_name'  => 'Lovelace',
+						'country'    => 'US',
+					),
+				),
+				'meta'                 => array(
+					'source_channel' => 'pdp',
+				),
+			)
+		);
+	}
+
+	public function test_contract_round_trips_with_children(): void {
+		$repo = new Contract_Repository();
+
+		$id = $repo->insert( $this->make_contract() );
+		$this->assertGreaterThan( 0, $id );
+
+		$fetched = $repo->find( $id );
+
+		$this->assertInstanceOf( Contract::class, $fetched );
+		$this->assertSame( $id, $fetched->get_id() );
+		$this->assertSame( 42, $fetched->get_customer_id() );
+		$this->assertSame( 'USD', $fetched->get_currency() );
+		$this->assertSame( 'lite', $fetched->get_extension_slug() );
+		$this->assertSame( Contract_Status::ACTIVE, $fetched->get_status() );
+		$this->assertSame( '2026-07-15 00:00:00', $fetched->get_next_payment_gmt() );
+
+		// Payment instrument reference.
+		$instrument = $fetched->get_payment_instrument();
+		$this->assertSame( 55, $instrument->get_token_id() );
+		$this->assertSame( 'woocommerce_payments', $instrument->get_gateway() );
+
+		// Items.
+		$items = $fetched->get_items();
+		$this->assertCount( 1, $items );
+		$this->assertSame( 'Coffee bag', $items[0]['item_name'] );
+
+		// Addresses.
+		$addresses = $fetched->get_addresses();
+		$this->assertArrayHasKey( Contract::ADDRESS_BILLING, $addresses );
+		$this->assertArrayHasKey( Contract::ADDRESS_SHIPPING, $addresses );
+		$this->assertSame( 'Ada', $addresses[ Contract::ADDRESS_BILLING ]['first_name'] );
+
+		// Meta.
+		$this->assertSame( 'pdp', $fetched->get_meta()['source_channel'] );
+	}
+
+	public function test_extension_slug_defaults_to_null_when_unset(): void {
+		$repo = new Contract_Repository();
+
+		$id = $repo->insert(
+			Contract::create(
+				array(
+					'customer_id'     => 1,
+					'currency'        => 'EUR',
+					'selling_plan_id' => 2,
+					'origin_order_id' => 3,
+					'start_gmt'       => '2026-06-15 00:00:00',
+				)
+			)
+		);
+
+		$this->assertNull( $repo->find( $id )->get_extension_slug() );
+	}
+
+	public function test_delete_removes_contract_and_children(): void {
+		global $wpdb;
+
+		$repo = new Contract_Repository();
+		$id   = $repo->insert( $this->make_contract() );
+
+		$this->assertTrue( $repo->delete( $id ) );
+		$this->assertNull( $repo->find( $id ) );
+
+		$items_table = \Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage\Schema_Installer::get_table_name(
+			\Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage\Schema_Installer::TABLE_CONTRACT_ITEMS
+		);
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+		$remaining = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$items_table} WHERE contract_id = %d", $id ) );
+
+		$this->assertSame( '0', $remaining );
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/Plan_Repository_Test.php b/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/Plan_Repository_Test.php
new file mode 100644
index 00000000000..134a7485f27
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/Plan_Repository_Test.php
@@ -0,0 +1,187 @@
+<?php
+/**
+ * Integration tests for Plan_Repository (and Plan_Group_Repository).
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Tests\Integration\Integration\Storage;
+
+use Engine_Integration_Test_Case;
+use Automattic\WooCommerce\SubscriptionsEngine\Core\Entity\Plan;
+use Automattic\WooCommerce\SubscriptionsEngine\Core\Entity\Plan_Group;
+use Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject\Billing_Policy;
+use Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject\Pricing_Policy;
+use Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage\Plan_Group_Repository;
+use Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage\Plan_Repository;
+
+/**
+ * @covers \Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage\Plan_Repository
+ * @covers \Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage\Plan_Group_Repository
+ */
+class Plan_Repository_Test extends Engine_Integration_Test_Case {
+
+	private function make_group(): int {
+		$group = Plan_Group::create(
+			array(
+				'name'          => 'Coffee club',
+				'merchant_code' => 'coffee-club',
+			)
+		);
+
+		return ( new Plan_Group_Repository() )->insert( $group );
+	}
+
+	public function test_plan_group_round_trips(): void {
+		$repo = new Plan_Group_Repository();
+
+		$id = $repo->insert(
+			Plan_Group::create(
+				array(
+					'name'            => 'Boxes',
+					'merchant_code'   => 'boxes',
+					'options_display' => array( array( 'name' => 'Size' ) ),
+					'app_id'          => 'wc-subscriptions',
+				)
+			)
+		);
+
+		$fetched = $repo->find( $id );
+
+		$this->assertInstanceOf( Plan_Group::class, $fetched );
+		$this->assertSame( $id, $fetched->get_id() );
+		$this->assertSame( 'Boxes', $fetched->get_name() );
+		$this->assertSame( 'boxes', $fetched->get_merchant_code() );
+		$this->assertSame( 'wc-subscriptions', $fetched->get_app_id() );
+		$this->assertSame( array( array( 'name' => 'Size' ) ), $fetched->get_options_display() );
+	}
+
+	public function test_plan_round_trips_with_policies_and_extension_slug(): void {
+		$group_id = $this->make_group();
+		$repo     = new Plan_Repository();
+
+		$plan = Plan::create(
+			$group_id,
+			array(
+				'name'           => 'Monthly',
+				'description'    => 'A monthly plan',
+				'options'        => array(
+					array(
+						'name'  => 'Monthly',
+						'value' => 'monthly',
+					),
+				),
+				'billing_policy' => Billing_Policy::from_array(
+					array(
+						'period'     => 'month',
+						'interval'   => 1,
+						'max_cycles' => 12,
+					)
+				),
+				'pricing_policy' => Pricing_Policy::from_array(
+					array(
+						'policies' => array(
+							array(
+								'type'  => 'percentage',
+								'value' => 10,
+							),
+						),
+					)
+				),
+				'extension_slug' => 'lite',
+			)
+		);
+
+		$id = $repo->insert( $plan );
+		$this->assertGreaterThan( 0, $id );
+		$this->assertSame( $id, $plan->get_id() );
+
+		$fetched = $repo->find( $id );
+
+		$this->assertInstanceOf( Plan::class, $fetched );
+		$this->assertSame( 'Monthly', $fetched->get_name() );
+		$this->assertSame( 'A monthly plan', $fetched->get_description() );
+		$this->assertSame( $group_id, $fetched->get_group_id() );
+		$this->assertSame( 'lite', $fetched->get_extension_slug() );
+		$this->assertSame( 'month', $fetched->get_billing_policy()->get_period() );
+		$this->assertSame( 12, $fetched->get_billing_policy()->get_max_cycles() );
+		$this->assertNotNull( $fetched->get_pricing_policy() );
+		$this->assertSame( 90.0, $fetched->calculate_price( 100.0 ) );
+	}
+
+	public function test_plan_without_optional_policies_round_trips(): void {
+		$group_id = $this->make_group();
+		$repo     = new Plan_Repository();
+
+		$id = $repo->insert(
+			Plan::create(
+				$group_id,
+				array(
+					'name'           => 'Bare',
+					'billing_policy' => Billing_Policy::from_array(
+						array(
+							'period'   => 'week',
+							'interval' => 2,
+						)
+					),
+				)
+			)
+		);
+
+		$fetched = $repo->find( $id );
+
+		$this->assertInstanceOf( Plan::class, $fetched );
+		$this->assertNull( $fetched->get_pricing_policy() );
+		$this->assertNull( $fetched->get_delivery_policy() );
+		$this->assertNull( $fetched->get_extension_slug() );
+	}
+
+	public function test_update_persists_changes(): void {
+		$group_id = $this->make_group();
+		$repo     = new Plan_Repository();
+
+		$plan = Plan::create(
+			$group_id,
+			array(
+				'name'           => 'Before',
+				'billing_policy' => Billing_Policy::from_array(
+					array(
+						'period'   => 'month',
+						'interval' => 1,
+					)
+				),
+			)
+		);
+		$id   = $repo->insert( $plan );
+
+		$plan->set_name( 'After' );
+		$this->assertTrue( $repo->update( $plan ) );
+
+		$this->assertSame( 'After', $repo->find( $id )->get_name() );
+	}
+
+	public function test_delete_removes_the_row(): void {
+		$group_id = $this->make_group();
+		$repo     = new Plan_Repository();
+
+		$id = $repo->insert(
+			Plan::create(
+				$group_id,
+				array(
+					'name'           => 'Doomed',
+					'billing_policy' => Billing_Policy::from_array(
+						array(
+							'period'   => 'month',
+							'interval' => 1,
+						)
+					),
+				)
+			)
+		);
+
+		$this->assertTrue( $repo->delete( $id ) );
+		$this->assertNull( $repo->find( $id ) );
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/Schema_Installer_Test.php b/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/Schema_Installer_Test.php
new file mode 100644
index 00000000000..ea184e5c921
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/Schema_Installer_Test.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * Integration tests for Schema_Installer.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Tests\Integration\Integration\Storage;
+
+use Engine_Integration_Test_Case;
+use Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage\Schema_Installer;
+
+/**
+ * @covers \Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage\Schema_Installer
+ */
+class Schema_Installer_Test extends Engine_Integration_Test_Case {
+
+	/**
+	 * The six baseline tables the installer owns.
+	 *
+	 * @return array<int, array<int, string>>
+	 */
+	public function table_provider(): array {
+		return array(
+			array( Schema_Installer::TABLE_PLAN_GROUPS ),
+			array( Schema_Installer::TABLE_PLANS ),
+			array( Schema_Installer::TABLE_CONTRACTS ),
+			array( Schema_Installer::TABLE_CONTRACT_ITEMS ),
+			array( Schema_Installer::TABLE_CONTRACT_ADDRESSES ),
+			array( Schema_Installer::TABLE_CONTRACT_META ),
+		);
+	}
+
+	/**
+	 * @dataProvider table_provider
+	 *
+	 * @param string $logical Logical table identifier.
+	 */
+	public function test_each_baseline_table_exists( string $logical ): void {
+		global $wpdb;
+
+		$table = Schema_Installer::get_table_name( $logical );
+
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+		$found = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
+
+		$this->assertSame( $table, $found, "Expected table {$table} to exist." );
+	}
+
+	public function test_version_option_is_set_after_install(): void {
+		$this->assertTrue( Schema_Installer::is_current() );
+		$this->assertSame( Schema_Installer::VERSION, get_option( Schema_Installer::VERSION_OPTION ) );
+	}
+
+	public function test_install_is_idempotent(): void {
+		// Running install again must not error or change the recorded version.
+		Schema_Installer::install();
+
+		$this->assertSame( Schema_Installer::VERSION, get_option( Schema_Installer::VERSION_OPTION ) );
+	}
+
+	public function test_unknown_table_identifier_throws(): void {
+		$this->expectException( \InvalidArgumentException::class );
+		Schema_Installer::get_table_name( 'not_a_table' );
+	}
+
+	public function test_plans_table_has_extension_slug_column(): void {
+		global $wpdb;
+
+		$table = Schema_Installer::get_table_name( Schema_Installer::TABLE_PLANS );
+
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+		$column = $wpdb->get_var( $wpdb->prepare( "SHOW COLUMNS FROM {$table} LIKE %s", 'extension_slug' ) );
+
+		$this->assertSame( 'extension_slug', $column );
+	}
+
+	public function test_contracts_table_has_extension_slug_column(): void {
+		global $wpdb;
+
+		$table = Schema_Installer::get_table_name( Schema_Installer::TABLE_CONTRACTS );
+
+		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+		$column = $wpdb->get_var( $wpdb->prepare( "SHOW COLUMNS FROM {$table} LIKE %s", 'extension_slug' ) );
+
+		$this->assertSame( 'extension_slug', $column );
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/tests/integration/bootstrap.php b/packages/php/woocommerce-subscriptions-engine/tests/integration/bootstrap.php
new file mode 100644
index 00000000000..8c067ef9c8b
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/tests/integration/bootstrap.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * Integration-test bootstrap for the WooCommerce Subscriptions Engine.
+ *
+ * Loads the WordPress test framework, the engine plugin file, and installs the
+ * baseline schema once up front so per-test transaction rollback (provided by
+ * WP_UnitTestCase) keeps each test isolated without re-running DDL.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine
+ */
+
+declare( strict_types=1 );
+
+use Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage\Schema_Installer;
+
+// phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- Bootstrap file mixes a class and procedural setup.
+
+/**
+ * Bootstrap runner for the integration suite.
+ */
+class Subscriptions_Engine_Tests_Bootstrap {
+
+	/**
+	 * Singleton instance.
+	 *
+	 * @var Subscriptions_Engine_Tests_Bootstrap|null
+	 */
+	protected static $instance = null;
+
+	/**
+	 * Path to the WordPress tests directory.
+	 *
+	 * @var string
+	 */
+	public $wp_tests_dir;
+
+	/**
+	 * Path to this tests directory.
+	 *
+	 * @var string
+	 */
+	public $tests_dir;
+
+	/**
+	 * Path to the package root.
+	 *
+	 * @var string
+	 */
+	public $plugin_dir;
+
+	/**
+	 * Set up the integration testing environment.
+	 */
+	public function __construct() {
+		$this->tests_dir  = __DIR__;
+		$this->plugin_dir = dirname( dirname( $this->tests_dir ) );
+
+		$this->wp_tests_dir = getenv( 'WP_TESTS_DIR' ) ? getenv( 'WP_TESTS_DIR' ) : sys_get_temp_dir() . '/wordpress-tests-lib';
+
+		require_once $this->wp_tests_dir . '/includes/functions.php';
+
+		tests_add_filter( 'muplugins_loaded', array( $this, 'load_plugin' ) );
+
+		if ( ! defined( 'WP_TESTS_PHPUNIT_POLYFILLS_PATH' ) ) {
+			define( 'WP_TESTS_PHPUNIT_POLYFILLS_PATH', __DIR__ . '/../../vendor/yoast/phpunit-polyfills/phpunitpolyfills-autoload.php' );
+		}
+
+		require_once $this->wp_tests_dir . '/includes/bootstrap.php';
+
+		// Install once, outside any test transaction, so the init-hook installer
+		// short-circuits during tests and DDL never breaks rollback isolation.
+		Schema_Installer::install();
+
+		require_once $this->plugin_dir . '/tests/integration/class-engine-integration-test-case.php';
+	}
+
+	/**
+	 * Load the engine plugin file.
+	 */
+	public function load_plugin(): void {
+		require_once $this->plugin_dir . '/subscriptions-engine.php';
+	}
+
+	/**
+	 * Get the singleton instance.
+	 *
+	 * @return Subscriptions_Engine_Tests_Bootstrap
+	 */
+	public static function instance(): self {
+		if ( null === self::$instance ) {
+			self::$instance = new self();
+		}
+
+		return self::$instance;
+	}
+}
+
+Subscriptions_Engine_Tests_Bootstrap::instance();
diff --git a/packages/php/woocommerce-subscriptions-engine/tests/integration/class-engine-integration-test-case.php b/packages/php/woocommerce-subscriptions-engine/tests/integration/class-engine-integration-test-case.php
new file mode 100644
index 00000000000..053cae2bcfb
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/tests/integration/class-engine-integration-test-case.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * Base test case for engine integration tests.
+ *
+ * Schema is installed once in the bootstrap; WP_UnitTestCase wraps each test in
+ * a transaction and rolls it back, so test rows do not leak between tests.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine
+ */
+
+declare( strict_types=1 );
+
+/**
+ * Engine integration test case.
+ */
+abstract class Engine_Integration_Test_Case extends WP_UnitTestCase {
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/tests/unit/Core/Entity/Contract_Status_Test.php b/packages/php/woocommerce-subscriptions-engine/tests/unit/Core/Entity/Contract_Status_Test.php
new file mode 100644
index 00000000000..3146d3bafb0
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/tests/unit/Core/Entity/Contract_Status_Test.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Unit tests for the Contract_Status state machine.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Tests\Unit\Core\Entity;
+
+use PHPUnit\Framework\TestCase;
+use Automattic\WooCommerce\SubscriptionsEngine\Core\Entity\Contract_Status;
+
+/**
+ * @covers \Automattic\WooCommerce\SubscriptionsEngine\Core\Entity\Contract_Status
+ */
+class Contract_Status_Test extends TestCase {
+
+	public function test_known_statuses_are_valid(): void {
+		$this->assertTrue( Contract_Status::is_valid( Contract_Status::ACTIVE ) );
+		$this->assertFalse( Contract_Status::is_valid( 'nonsense' ) );
+	}
+
+	public function test_active_can_move_to_hold_and_back(): void {
+		$this->assertTrue( Contract_Status::can_transition( Contract_Status::ACTIVE, Contract_Status::ON_HOLD ) );
+		$this->assertTrue( Contract_Status::can_transition( Contract_Status::ON_HOLD, Contract_Status::ACTIVE ) );
+	}
+
+	public function test_cancelled_and_expired_are_terminal(): void {
+		$this->assertTrue( Contract_Status::is_terminal( Contract_Status::CANCELLED ) );
+		$this->assertTrue( Contract_Status::is_terminal( Contract_Status::EXPIRED ) );
+
+		foreach ( Contract_Status::all() as $target ) {
+			$this->assertFalse( Contract_Status::can_transition( Contract_Status::CANCELLED, $target ) );
+			$this->assertFalse( Contract_Status::can_transition( Contract_Status::EXPIRED, $target ) );
+		}
+	}
+
+	public function test_pending_cancellation_only_reaches_active_or_cancelled(): void {
+		$this->assertTrue( Contract_Status::can_transition( Contract_Status::PENDING_CANCELLATION, Contract_Status::ACTIVE ) );
+		$this->assertTrue( Contract_Status::can_transition( Contract_Status::PENDING_CANCELLATION, Contract_Status::CANCELLED ) );
+		$this->assertFalse( Contract_Status::can_transition( Contract_Status::PENDING_CANCELLATION, Contract_Status::ON_HOLD ) );
+	}
+
+	public function test_unknown_statuses_never_transition(): void {
+		$this->assertFalse( Contract_Status::can_transition( 'nonsense', Contract_Status::ACTIVE ) );
+		$this->assertFalse( Contract_Status::can_transition( Contract_Status::ACTIVE, 'nonsense' ) );
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/tests/unit/Core/Entity/Plan_Test.php b/packages/php/woocommerce-subscriptions-engine/tests/unit/Core/Entity/Plan_Test.php
new file mode 100644
index 00000000000..af3d1ffbe21
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/tests/unit/Core/Entity/Plan_Test.php
@@ -0,0 +1,141 @@
+<?php
+/**
+ * Unit tests for the Plan entity (pure-Core behavior: validation + pricing).
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Tests\Unit\Core\Entity;
+
+use InvalidArgumentException;
+use PHPUnit\Framework\TestCase;
+use Automattic\WooCommerce\SubscriptionsEngine\Core\Entity\Plan;
+use Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject\Billing_Policy;
+use Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject\Pricing_Policy;
+
+/**
+ * @covers \Automattic\WooCommerce\SubscriptionsEngine\Core\Entity\Plan
+ */
+class Plan_Test extends TestCase {
+
+	private function billing(): Billing_Policy {
+		return Billing_Policy::from_array(
+			array(
+				'period'   => 'month',
+				'interval' => 1,
+			)
+		);
+	}
+
+	public function test_create_defaults_category_and_extension_slug(): void {
+		$plan = Plan::create(
+			5,
+			array(
+				'name'           => 'Monthly box',
+				'billing_policy' => $this->billing(),
+			)
+		);
+
+		$this->assertNull( $plan->get_id() );
+		$this->assertSame( 5, $plan->get_group_id() );
+		$this->assertSame( Plan::DEFAULT_CATEGORY, $plan->get_category() );
+		$this->assertNull( $plan->get_extension_slug() );
+	}
+
+	public function test_calculate_price_delegates_to_pricing_policy(): void {
+		$plan = Plan::create(
+			1,
+			array(
+				'name'           => 'Discounted',
+				'billing_policy' => $this->billing(),
+				'pricing_policy' => Pricing_Policy::from_array(
+					array(
+						'policies' => array(
+							array(
+								'type'  => 'percentage',
+								'value' => 20,
+							),
+						),
+					)
+				),
+			)
+		);
+
+		$this->assertSame( 80.0, $plan->calculate_price( 100.0 ) );
+	}
+
+	public function test_calculate_price_without_pricing_policy_returns_base(): void {
+		$plan = Plan::create(
+			1,
+			array(
+				'name'           => 'Plain',
+				'billing_policy' => $this->billing(),
+			)
+		);
+
+		$this->assertSame( 42.0, $plan->calculate_price( 42.0 ) );
+	}
+
+	public function test_invalid_pricing_policy_type_is_rejected(): void {
+		$this->expectException( InvalidArgumentException::class );
+
+		Plan::create(
+			1,
+			array(
+				'name'           => 'Bad',
+				'billing_policy' => $this->billing(),
+				'pricing_policy' => Pricing_Policy::from_array(
+					array(
+						'policies' => array(
+							array(
+								'type'  => 'mystery',
+								'value' => 1,
+							),
+						),
+					)
+				),
+			)
+		);
+	}
+
+	public function test_percentage_over_one_hundred_is_rejected(): void {
+		$this->expectException( InvalidArgumentException::class );
+
+		Plan::create(
+			1,
+			array(
+				'name'           => 'Too much',
+				'billing_policy' => $this->billing(),
+				'pricing_policy' => Pricing_Policy::from_array(
+					array(
+						'policies' => array(
+							array(
+								'type'  => 'percentage',
+								'value' => 150,
+							),
+						),
+					)
+				),
+			)
+		);
+	}
+
+	public function test_to_storage_exposes_extension_slug_and_decoded_policies(): void {
+		$plan = Plan::create(
+			3,
+			array(
+				'name'           => 'Owned',
+				'billing_policy' => $this->billing(),
+				'extension_slug' => 'lite',
+			)
+		);
+
+		$storage = $plan->to_storage();
+
+		$this->assertSame( 'lite', $storage['extension_slug'] );
+		$this->assertSame( 3, $storage['group_id'] );
+		$this->assertIsArray( $storage['billing_policy'] );
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/tests/unit/Core/ValueObject/Billing_Policy_Test.php b/packages/php/woocommerce-subscriptions-engine/tests/unit/Core/ValueObject/Billing_Policy_Test.php
new file mode 100644
index 00000000000..4fe498aca89
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/tests/unit/Core/ValueObject/Billing_Policy_Test.php
@@ -0,0 +1,243 @@
+<?php
+/**
+ * Unit tests for Billing_Policy.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Tests\Unit\Core\ValueObject;
+
+use DateTimeImmutable;
+use DateTimeZone;
+use DomainException;
+use PHPUnit\Framework\TestCase;
+use Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject\Billing_Policy;
+
+/**
+ * @covers \Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject\Billing_Policy
+ */
+class Billing_Policy_Test extends TestCase {
+
+	public function test_round_trips_through_array(): void {
+		$data = array(
+			'period'         => 'month',
+			'interval'       => 2,
+			'min_cycles'     => 1,
+			'max_cycles'     => 12,
+			'trial_duration' => array(
+				'length' => 14,
+				'unit'   => 'day',
+			),
+		);
+
+		$policy = Billing_Policy::from_array( $data );
+
+		$this->assertSame( 'month', $policy->get_period() );
+		$this->assertSame( 2, $policy->get_interval() );
+		$this->assertSame( 1, $policy->get_min_cycles() );
+		$this->assertSame( 12, $policy->get_max_cycles() );
+		$this->assertSame( $data['trial_duration'], $policy->get_trial_duration() );
+		$this->assertSame( $data, $policy->to_array() );
+	}
+
+	public function test_missing_nullable_keys_default_to_null(): void {
+		$policy = Billing_Policy::from_array(
+			array(
+				'period'   => 'week',
+				'interval' => 1,
+			)
+		);
+
+		$this->assertNull( $policy->get_min_cycles() );
+		$this->assertNull( $policy->get_max_cycles() );
+		$this->assertNull( $policy->get_trial_duration() );
+	}
+
+	public function test_compute_next_renewal_adds_one_cadence_in_utc(): void {
+		$policy = Billing_Policy::from_array(
+			array(
+				'period'   => 'month',
+				'interval' => 1,
+			)
+		);
+
+		$anchor = new DateTimeImmutable( '2026-01-15 10:00:00', new DateTimeZone( 'UTC' ) );
+		$next   = $policy->compute_next_renewal_from( $anchor );
+
+		$this->assertSame( '2026-02-15 10:00:00', $next->format( 'Y-m-d H:i:s' ) );
+		$this->assertSame( 'UTC', $next->getTimezone()->getName() );
+	}
+
+	public function test_compute_first_renewal_honours_trial(): void {
+		$policy = Billing_Policy::from_array(
+			array(
+				'period'         => 'month',
+				'interval'       => 1,
+				'trial_duration' => array(
+					'length' => 7,
+					'unit'   => 'day',
+				),
+			)
+		);
+
+		$start = new DateTimeImmutable( '2026-01-01 00:00:00', new DateTimeZone( 'UTC' ) );
+		$first = $policy->compute_first_renewal_from( $start );
+
+		$this->assertSame( '2026-01-08 00:00:00', $first->format( 'Y-m-d H:i:s' ) );
+	}
+
+	public function test_compute_first_renewal_without_trial_matches_next(): void {
+		$policy = Billing_Policy::from_array(
+			array(
+				'period'   => 'year',
+				'interval' => 1,
+			)
+		);
+
+		$start = new DateTimeImmutable( '2026-03-10 12:00:00', new DateTimeZone( 'UTC' ) );
+
+		$this->assertEquals(
+			$policy->compute_next_renewal_from( $start ),
+			$policy->compute_first_renewal_from( $start )
+		);
+	}
+
+	public function test_invalid_period_throws(): void {
+		$policy = Billing_Policy::from_array(
+			array(
+				'period'   => 'fortnight',
+				'interval' => 1,
+			)
+		);
+
+		$this->expectException( DomainException::class );
+		$policy->compute_next_renewal_from( new DateTimeImmutable( '2026-01-01', new DateTimeZone( 'UTC' ) ) );
+	}
+
+	public function test_non_positive_interval_throws(): void {
+		$policy = Billing_Policy::from_array(
+			array(
+				'period'   => 'month',
+				'interval' => 0,
+			)
+		);
+
+		$this->expectException( DomainException::class );
+		$policy->compute_next_renewal_from( new DateTimeImmutable( '2026-01-01', new DateTimeZone( 'UTC' ) ) );
+	}
+
+	/**
+	 * @dataProvider provide_min_and_max_cycles_validation_cases
+	 * @param string|null $expected_exception_message The expected exception message, or null if no exception is expected.
+	 * @param int|null    $min_cycles                 The minimum number of cycles.
+	 * @param int|null    $max_cycles                 The maximum number of cycles.
+	 */
+	public function test_min_and_max_cycles_validation( ?string $expected_exception_message, ?int $min_cycles, ?int $max_cycles ): void {
+		if ( null !== $expected_exception_message ) {
+			$this->expectException( DomainException::class );
+			$this->expectExceptionMessage( $expected_exception_message );
+		}
+
+		$policy = Billing_Policy::from_array(
+			array(
+				'period'     => 'month',
+				'interval'   => 1,
+				'min_cycles' => $min_cycles,
+				'max_cycles' => $max_cycles,
+			)
+		);
+
+		if ( null === $expected_exception_message ) {
+			$this->assertInstanceOf( Billing_Policy::class, $policy );
+			$this->assertSame( $min_cycles, $policy->get_min_cycles() );
+			$this->assertSame( $max_cycles, $policy->get_max_cycles() );
+		}
+	}
+
+	public function provide_min_and_max_cycles_validation_cases(): array {
+		return array(
+			'min_cycles is 0, max_cycles is null'        => array(
+				'expected_exception_message' => null,
+				'min_cycles'                 => 0,
+				'max_cycles'                 => null,
+			),
+			'min_cycles is 0, max_cycles is positive'    => array(
+				'expected_exception_message' => null,
+				'min_cycles'                 => 0,
+				'max_cycles'                 => 10,
+			),
+			'min_cycles is 0, max_cycles is less than 0' => array(
+				'expected_exception_message' => 'Billing_Policy: max_cycles must be 0 or greater, got -4.',
+				'min_cycles'                 => 0,
+				'max_cycles'                 => -4,
+			),
+			'max_cycles is 0, min_cycles is null'        => array(
+				'expected_exception_message' => null,
+				'min_cycles'                 => null,
+				'max_cycles'                 => 0,
+			),
+			'max_cycles is 0, min_cycles is positive'    => array(
+				'expected_exception_message' => 'Billing_Policy: min_cycles cannot exceed max_cycles, got 5 and 0.',
+				'min_cycles'                 => 5,
+				'max_cycles'                 => 0,
+			),
+			'max_cycles is 0, min_cycles is greater than max_cycles' => array(
+				'expected_exception_message' => 'Billing_Policy: min_cycles cannot exceed max_cycles, got 5 and 0.',
+				'min_cycles'                 => 5,
+				'max_cycles'                 => 0,
+			),
+			'max_cycles is positive, min_cycles is null' => array(
+				'expected_exception_message' => null,
+				'min_cycles'                 => null,
+				'max_cycles'                 => 10,
+			),
+			'max_cycles is positive, min_cycles is positive' => array(
+				'expected_exception_message' => null,
+				'min_cycles'                 => 1,
+				'max_cycles'                 => 10,
+			),
+			'max_cycles is positive, min_cycles is the same as max_cycles' => array(
+				'expected_exception_message' => null,
+				'min_cycles'                 => 10,
+				'max_cycles'                 => 10,
+			),
+			'min_cycles is positive, max_cycles is null' => array(
+				'expected_exception_message' => null,
+				'min_cycles'                 => 1,
+				'max_cycles'                 => null,
+			),
+			'min_cycles is positive, max_cycles is positive' => array(
+				'expected_exception_message' => null,
+				'min_cycles'                 => 1,
+				'max_cycles'                 => 10,
+			),
+			'min_cycles is positive, max_cycles is less than min_cycles' => array(
+				'expected_exception_message' => 'Billing_Policy: min_cycles cannot exceed max_cycles, got 10 and 9.',
+				'min_cycles'                 => 10,
+				'max_cycles'                 => 9,
+			),
+			'min_cycles is negative, max_cycles is null' => array(
+				'expected_exception_message' => 'Billing_Policy: min_cycles must be 0 or greater, got -1.',
+				'min_cycles'                 => -1,
+				'max_cycles'                 => null,
+			),
+			'min_cycles is negative, max_cycles is positive' => array(
+				'expected_exception_message' => 'Billing_Policy: min_cycles must be 0 or greater, got -1.',
+				'min_cycles'                 => -1,
+				'max_cycles'                 => 10,
+			),
+			'min_cycles is negative, max_cycles is less than min_cycles' => array(
+				'expected_exception_message' => 'Billing_Policy: min_cycles must be 0 or greater, got -1.',
+				'min_cycles'                 => -1,
+				'max_cycles'                 => -1,
+			),
+			'min_cycles is positive, max_cycles is negative' => array(
+				'expected_exception_message' => 'Billing_Policy: max_cycles must be 0 or greater, got -1.',
+				'min_cycles'                 => 1,
+				'max_cycles'                 => -1,
+			),
+		);
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/tests/unit/Core/ValueObject/Pricing_Policy_Test.php b/packages/php/woocommerce-subscriptions-engine/tests/unit/Core/ValueObject/Pricing_Policy_Test.php
new file mode 100644
index 00000000000..a65c7bdcda3
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/tests/unit/Core/ValueObject/Pricing_Policy_Test.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Unit tests for Pricing_Policy.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\SubscriptionsEngine\Tests\Unit\Core\ValueObject;
+
+use PHPUnit\Framework\TestCase;
+use Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject\Pricing_Policy;
+
+/**
+ * @covers \Automattic\WooCommerce\SubscriptionsEngine\Core\ValueObject\Pricing_Policy
+ */
+class Pricing_Policy_Test extends TestCase {
+
+	public function test_empty_policy_returns_base_price(): void {
+		$policy = Pricing_Policy::from_array( array() );
+
+		$this->assertSame( 25.0, $policy->calculate_price( 25.0 ) );
+		$this->assertSame( array(), $policy->get_policies() );
+		$this->assertSame( array(), $policy->get_one_time_fees() );
+	}
+
+	public function test_percentage_discount_applies(): void {
+		$policy = Pricing_Policy::from_array(
+			array(
+				'policies' => array(
+					array(
+						'type'  => 'percentage',
+						'value' => 10,
+					),
+				),
+			)
+		);
+
+		$this->assertSame( 90.0, $policy->calculate_price( 100.0 ) );
+	}
+
+	public function test_fixed_amount_is_clamped_at_zero(): void {
+		$policy = Pricing_Policy::from_array(
+			array(
+				'policies' => array(
+					array(
+						'type'  => 'fixed_amount',
+						'value' => 30,
+					),
+				),
+			)
+		);
+
+		$this->assertSame( 0.0, $policy->calculate_price( 20.0 ) );
+	}
+
+	public function test_price_replaces_base_and_starting_cycle_gates(): void {
+		$policy = Pricing_Policy::from_array(
+			array(
+				'policies' => array(
+					array(
+						'type'           => 'price',
+						'value'          => 5,
+						'starting_cycle' => 2,
+					),
+				),
+			)
+		);
+
+		// Cycle 1 is before the rule's starting cycle, so the base price stands.
+		$this->assertSame( 50.0, $policy->calculate_price( 50.0, 1 ) );
+		// Cycle 2 onward the rule fires and replaces the price.
+		$this->assertSame( 5.0, $policy->calculate_price( 50.0, 2 ) );
+	}
+
+	public function test_whole_number_values_normalize_to_float(): void {
+		$policy = Pricing_Policy::from_array(
+			array(
+				'policies'      => array(
+					array(
+						'type'  => 'percentage',
+						'value' => 10,
+					),
+				),
+				'one_time_fees' => array(
+					array(
+						'kind'    => 'enrollment',
+						'amount'  => 15,
+						'taxable' => true,
+					),
+				),
+			)
+		);
+
+		$this->assertIsFloat( $policy->get_policies()[0]['value'] );
+		$this->assertIsFloat( $policy->get_one_time_fees()[0]['amount'] );
+	}
+}
diff --git a/packages/php/woocommerce-subscriptions-engine/tests/unit/bootstrap.php b/packages/php/woocommerce-subscriptions-engine/tests/unit/bootstrap.php
new file mode 100644
index 00000000000..e46142ef049
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/tests/unit/bootstrap.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * Unit-test bootstrap for the WooCommerce Subscriptions Engine.
+ *
+ * Deliberately autoloader-only: it defines ABSPATH so the `defined( 'ABSPATH' )
+ * || exit;` guards pass, but it stubs NO WordPress functions. The Core zone is
+ * WordPress-free, so its classes must load and run here with nothing but the
+ * Composer autoloader. If a Core class ever reaches for a WP/Woo symbol, these
+ * tests fatal - which is the executable form of the zoning rule.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine
+ */
+
+declare( strict_types=1 );
+
+// Satisfy the file-access guards without providing any WordPress behavior.
+if ( ! defined( 'ABSPATH' ) ) {
+	define( 'ABSPATH', __DIR__ . '/' );
+}
+
+require_once __DIR__ . '/../../vendor/autoload.php';
diff --git a/packages/php/woocommerce-subscriptions-engine/woocommerce-subscriptions-engine.php b/packages/php/woocommerce-subscriptions-engine/woocommerce-subscriptions-engine.php
new file mode 100644
index 00000000000..59ec28a7194
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/woocommerce-subscriptions-engine.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * This file is part of the WooCommerce Subscriptions Engine package.
+ *
+ * @package Automattic\WooCommerce\SubscriptionsEngine
+ */
+
+/**
+ * Plugin Name: WooCommerce Subscriptions Engine
+ * Plugin URI: https://woocommerce.com/
+ * Description: An empty subscriptions-engine definition file to set up the wp-env test environment.
+ * Version: 0.0.1
+ * Author: WooCommerce
+ * Author URI: https://woocommerce.com
+ * Requires at least: 6.7
+ * Requires PHP: 7.4
+ */
+
+defined( 'ABSPATH' ) || exit;
+
+$autoload_entry_point = __DIR__ . '/vendor/autoload.php';
+if ( file_exists( $autoload_entry_point ) ) {
+	require_once $autoload_entry_point;
+}
+// When the package is distributed as part of WooCommerce core, it will provide autoloading of necessary dependencies.
+
+if ( class_exists( \Automattic\WooCommerce\SubscriptionsEngine\Package::class ) ) {
+	\Automattic\WooCommerce\SubscriptionsEngine\Package::init();
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 34b9be0e708..28038a26b63 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2563,6 +2563,12 @@ importers:
         specifier: 5.1.x
         version: 5.1.4(webpack@5.97.1)

+  packages/php/woocommerce-subscriptions-engine:
+    devDependencies:
+      '@wordpress/env':
+        specifier: 11.0.1-next.v.20260206T143.0
+        version: 11.0.1-next.v.20260206T143.0(@types/node@24.12.2)
+
   plugins/woocommerce:
     dependencies:
       '@woocommerce/admin-library':
@@ -35847,7 +35853,7 @@ snapshots:
       jest-matcher-utils: 29.7.0
       jest-mock: 29.7.0

-  '@wordpress/jest-console@8.44.0(jest@29.7.0(@types/node@24.12.2)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))':
+  '@wordpress/jest-console@8.44.0(jest@29.7.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))':
     dependencies:
       jest: 29.7.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3))
       jest-matcher-utils: 29.7.0
@@ -35865,7 +35871,7 @@ snapshots:
   '@wordpress/jest-preset-default@12.22.0(@babel/core@7.25.7)(jest@29.7.0(@types/node@24.12.2)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))':
     dependencies:
       '@babel/core': 7.25.7
-      '@wordpress/jest-console': 8.44.0(jest@29.7.0(@types/node@24.12.2)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))
+      '@wordpress/jest-console': 8.44.0(jest@29.7.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))
       babel-jest: 29.7.0(@babel/core@7.25.7)
       jest: 29.7.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3))
     transitivePeerDependencies:
@@ -35883,7 +35889,7 @@ snapshots:
   '@wordpress/jest-preset-default@12.44.0(@babel/core@7.25.7)(jest@29.7.0(@types/node@24.12.2)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))':
     dependencies:
       '@babel/core': 7.25.7
-      '@wordpress/jest-console': 8.44.0(jest@29.7.0(@types/node@24.12.2)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))
+      '@wordpress/jest-console': 8.44.0(jest@29.7.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)))
       babel-jest: 29.7.0(@babel/core@7.25.7)
       jest: 29.7.0(@types/node@24.12.2)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3))
     transitivePeerDependencies:
@@ -37985,7 +37991,7 @@ snapshots:
       ini: 4.1.2
       minimisted: 2.0.1
       octokit: 3.1.2
-      pako: 1.0.10
+      pako: 1.0.11
       pify: 4.0.1
       readable-stream: 3.6.2
       sha.js: 2.4.12