Rules become data instead of code: a clinic can say "laser under 18 requires parental consent" without a deploy. Engine - Policy / PolicyVersionLog entities, closed field/operator/effect lists per category (PolicySchema), condition validation at write time - PolicyResolver: priority -> specificity -> age, combining effects by veto / max / sum / union - A missing fact fails its clause instead of silently passing it - Policies are drafts until activated, and are versioned rather than edited Wiring - selection -> ServiceSelectionValidator - eligibility + spacing -> BookingPolicyGuard, at hold time not confirm time - resource + timing -> AppointmentPlanBuilder, including template-less services - pricing -> PricingEngine, alongside (not replacing) the manual discount The condition column is named condition_json: `condition` is a MariaDB keyword and broke every INSERT. Tests: 17 in tests/Policy including NoPolicyRegressionTest, which pins that a clinic with no policies sees byte-identical output to task 08. Docs: docs/api/policy.md (real captured JSON) + docs/architecture/policy-engine.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
45 lines
3.2 KiB
PHP
45 lines
3.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace DoctrineMigrations;
|
|
|
|
use Doctrine\DBAL\Schema\Schema;
|
|
use Doctrine\Migrations\AbstractMigration;
|
|
|
|
/**
|
|
* The six-category policy engine of section 8: conditions drawn from a closed list,
|
|
* priority-ordered, versioned, and with no arbitrary code.
|
|
*
|
|
* Policies are versioned rather than edited in place. An appointment booked yesterday
|
|
* references the version that actually applied to it; rewriting a policy would make
|
|
* that reference point at text the appointment never saw.
|
|
*/
|
|
final class Version20260731061814 extends AbstractMigration
|
|
{
|
|
public function getDescription(): string
|
|
{
|
|
return 'Add the six-category policy engine with version history';
|
|
}
|
|
|
|
public function up(Schema $schema): void
|
|
{
|
|
$this->addSql('CREATE TABLE policies (id INT AUTO_INCREMENT NOT NULL, uuid VARCHAR(36) NOT NULL, category VARCHAR(20) NOT NULL, name VARCHAR(200) NOT NULL, condition_json JSON NOT NULL, effects JSON NOT NULL, priority SMALLINT DEFAULT 0 NOT NULL, valid_from INT DEFAULT NULL, valid_to INT DEFAULT NULL, version SMALLINT DEFAULT 1 NOT NULL, active TINYINT DEFAULT 0 NOT NULL, created_at INT NOT NULL, updated_at INT NOT NULL, entity_type VARCHAR(10) NOT NULL, entity_id INT NOT NULL, address_id INT DEFAULT NULL, service_item_id INT DEFAULT NULL, catalog_category_id INT DEFAULT NULL, UNIQUE INDEX UNIQ_E08BBFCED17F50A6 (uuid), INDEX IDX_E08BBFCEF5B7AF75 (address_id), INDEX IDX_E08BBFCEDDEB00C2 (service_item_id), INDEX IDX_E08BBFCE3F2BC4C (catalog_category_id), INDEX idx_policy_tenant_category (entity_type, entity_id, category, active), INDEX idx_policy_validity (valid_from, valid_to), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4');
|
|
$this->addSql('CREATE TABLE policy_version_logs (id INT AUTO_INCREMENT NOT NULL, version SMALLINT NOT NULL, snapshot JSON NOT NULL, created_at INT NOT NULL, policy_id INT NOT NULL, INDEX IDX_B27F258B2D29E3C6 (policy_id), UNIQUE INDEX uniq_policy_version (policy_id, version), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4');
|
|
$this->addSql('ALTER TABLE policies ADD CONSTRAINT FK_E08BBFCEF5B7AF75 FOREIGN KEY (address_id) REFERENCES doctor_addresses (id) ON DELETE CASCADE');
|
|
$this->addSql('ALTER TABLE policies ADD CONSTRAINT FK_E08BBFCEDDEB00C2 FOREIGN KEY (service_item_id) REFERENCES service_items (id) ON DELETE CASCADE');
|
|
$this->addSql('ALTER TABLE policies ADD CONSTRAINT FK_E08BBFCE3F2BC4C FOREIGN KEY (catalog_category_id) REFERENCES service_catalog_categories (id) ON DELETE CASCADE');
|
|
$this->addSql('ALTER TABLE policy_version_logs ADD CONSTRAINT FK_B27F258B2D29E3C6 FOREIGN KEY (policy_id) REFERENCES policies (id) ON DELETE CASCADE');
|
|
}
|
|
|
|
public function down(Schema $schema): void
|
|
{
|
|
$this->addSql('ALTER TABLE policies DROP FOREIGN KEY FK_E08BBFCEF5B7AF75');
|
|
$this->addSql('ALTER TABLE policies DROP FOREIGN KEY FK_E08BBFCEDDEB00C2');
|
|
$this->addSql('ALTER TABLE policies DROP FOREIGN KEY FK_E08BBFCE3F2BC4C');
|
|
$this->addSql('ALTER TABLE policy_version_logs DROP FOREIGN KEY FK_B27F258B2D29E3C6');
|
|
$this->addSql('DROP TABLE policies');
|
|
$this->addSql('DROP TABLE policy_version_logs');
|
|
}
|
|
}
|