From b78a55f5732bf510dbda900a88f0ae3204ca883a Mon Sep 17 00:00:00 2001 From: EddyLogipro Date: Thu, 11 Jun 2026 12:05:52 +0200 Subject: [PATCH 1/3] =?UTF-8?q?Release=200.0.9=20=E2=80=94=20instanceId=20?= =?UTF-8?q?remplace=20tenantId,=20correlationId=20optionnel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aligne le bundle sur le contrat AM : identifiant unique instanceId dans les DTO, snapshots, consommation et état opérationnel ; suppression de tenantId et correlationId non requis dans OrchestrationCommand. Co-authored-by: Cursor --- CHANGELOG.md | 22 ++++++++ composer.json | 2 +- docs/ECARTS-AM.md | 3 +- docs/INTEGRATION-SAME-APP.md | 2 +- docs/INTEGRATION.md | 2 +- .../Command/ConsumptionPushCommand.php | 6 +-- .../Command/OrchestrationSimulateCommand.php | 2 - .../DependencyInjection/Configuration.php | 1 - .../OperationalStateProcessorFactory.php | 2 - src/Core/Dto/ConsumptionWebhookEvent.php | 18 +++---- src/Core/Dto/OrchestrationCommand.php | 34 ++++++------ .../FileOperationalStateReceiptStore.php | 18 +++---- .../FileOperationalStateStore.php | 19 +++---- .../OperationalStateProcessor.php | 20 +++---- .../OperationalStateReceiptStoreInterface.php | 4 +- .../OperationalStateStoreInterface.php | 4 +- src/Core/Snapshot/ConsumptionPublisher.php | 14 ++--- .../Snapshot/FileResourceSnapshotStore.php | 26 +++++----- .../ManagedInstanceResourceSnapshot.php | 28 +++++----- src/Core/Snapshot/ResourceSnapshotManager.php | 24 ++++----- .../ResourceSnapshotStoreInterface.php | 8 +-- src/Core/Tenant/FileTenantWorkspace.php | 52 ++++++++----------- .../InstanceOperationalStateValidator.php | 17 ++---- .../Integration/ReceptacleHttpKernelTest.php | 2 +- tests/Unit/Http/AmApiClientTest.php | 2 +- .../Snapshot/ConsumptionPublisherTest.php | 12 ++--- .../FileResourceSnapshotStoreTest.php | 33 ++++++------ .../ManagedInstanceResourceSnapshotTest.php | 20 ++----- tests/Unit/Tenant/FileTenantWorkspaceTest.php | 31 +++++------ ...instance-operational-state-am-minimal.json | 1 - .../instance-operational-state-minimal.json | 3 +- ...ed-instance-resource-snapshot-minimal.json | 13 ++--- .../orchestration-command-create.json | 1 - 33 files changed, 211 insertions(+), 235 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9340fed --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +## 0.0.9 — 2026-06-11 + +### Breaking changes + +- **Correlation key** : `tenantId` remplacé par `instanceId` partout dans le bundle (DTO, snapshots, état opérationnel, consommation, espace disque local). +- **`OrchestrationCommand`** : `tenantId` supprimé du corps ; `correlationId` devient optionnel. Clés requises : `operation`, `appId`, `instanceId`, `idempotencyKey`, `occurredAt`. +- **`ConsumptionWebhookEvent`** : champ JSON `tenantId` → `instanceId`. +- **`ManagedInstanceResourceSnapshot`** : champ JSON `tenantId` → `instanceId` ; getter `instanceId()`. +- **Stores / managers** : paramètres et méthodes `findByTenantId()` → `findByInstanceId()` ; `load($instanceId)`, `save($instanceId, …)`, etc. +- **`InstanceOperationalStateValidator`** : exige `instance.instanceId` ; garde-fou `expected_tenant_id` supprimé (utiliser `expected_instance_id`). +- **CLI** : `--tenant-id` → `--instance-id` (`consumption:push`, `orchestration:simulate`). + +### Migration + +- Renommer les fichiers locaux `snapshots/{tenantId}.json` → `snapshots/{instanceId}.json` (et équivalents operational-state / receipts). +- Mettre à jour les payloads AM et les appels `pushResourceConsumption($instanceId, …)`. + +## 0.0.8 + +Versions antérieures : voir tags Git. diff --git a/composer.json b/composer.json index d62b453..8a02ee4 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "application-manager-tools/am-driver", "description": "Symfony bundle and framework-agnostic core to integrate managed applications with Application Manager (orchestration, consumption, operational state).", "type": "symfony-bundle", - "version": "0.0.8", + "version": "0.0.9", "license": "MIT", "keywords": ["application-manager", "orchestration", "symfony-bundle"], "require": { diff --git a/docs/ECARTS-AM.md b/docs/ECARTS-AM.md index 5b242e6..7a000cd 100644 --- a/docs/ECARTS-AM.md +++ b/docs/ECARTS-AM.md @@ -15,7 +15,8 @@ Alignement sur le **back AM réel** (`ApplicationManager/ApplicationManager/`) e | `DESTROY_INSTANCE` | Émis par AM | **Non implémenté** v1 : HTTP 400 + callback `FAILED` (message explicite) | | Route commandes | Une URL par `targetId` dans `ManagedAppIntegration` sur l’agrégat App (voir ADR0002) | **Une route** POST routée par `operation` (variante cahier § 5) | | Paramètres Symfony `am_driver.config.` | Requis par routes/services bundle | Enregistrés par `ConfigurationParameters` à l’activation du bundle | -| Lecture snapshot externe | — | `ResourceSnapshotStoreInterface::findByTenantId()` (= `load()`) | +| Lecture snapshot externe | — | `ResourceSnapshotStoreInterface::findByInstanceId()` (= `load()`) | +| Clé de corrélation locale | `instanceId` (AM) | `instanceId` uniquement (plus de `tenantId` dans les DTO / snapshots v0.0.9+) | | Sonde route récepteur | — | `ConnectivityProbeInterface` + `HttpOrchestrationConnectivityProbe` (optionnel) | ## Politiques produit (à documenter par l’app hôte) diff --git a/docs/INTEGRATION-SAME-APP.md b/docs/INTEGRATION-SAME-APP.md index e375293..d3c56be 100644 --- a/docs/INTEGRATION-SAME-APP.md +++ b/docs/INTEGRATION-SAME-APP.md @@ -49,7 +49,7 @@ Le `OrchestrationCommandProcessor` du bundle envoie les callbacks AM après succ use ApplicationManagerTools\AmDriver\Core\Snapshot\FileResourceSnapshotStore; // service injecté par le bundle -$snapshot = $store->findByTenantId($tenantId); // ou load() +$snapshot = $store->findByInstanceId($instanceId); // ou load() ``` ## Espace disque tenant (handlers STOP/START locaux) diff --git a/docs/INTEGRATION.md b/docs/INTEGRATION.md index 4750c42..58a9460 100644 --- a/docs/INTEGRATION.md +++ b/docs/INTEGRATION.md @@ -83,7 +83,7 @@ Le bundle enregistre le tableau `am_driver.config` **et**, pour chaque clé de ` $publisher->pushResourceConsumption($tenantId, 'seats'); $publisher->flushPendingToAm($tenantId); $snapshotManager->recordMeasurement($tenantId, 'seats', 12); -$stored = $resourceSnapshotStore->findByTenantId($tenantId); // lecture externe (alias de load) +$stored = $resourceSnapshotStore->findByInstanceId($instanceId); // lecture externe (alias de load) ``` ## Même application Symfony qu’Application Manager diff --git a/src/Bridge/Console/Command/ConsumptionPushCommand.php b/src/Bridge/Console/Command/ConsumptionPushCommand.php index 19c8113..68fa6e7 100644 --- a/src/Bridge/Console/Command/ConsumptionPushCommand.php +++ b/src/Bridge/Console/Command/ConsumptionPushCommand.php @@ -24,7 +24,7 @@ protected function configure(): void $this ->setName('consumption:push') ->setDescription('Push a consumption event to Application Manager') - ->addOption('tenant-id', null, InputOption::VALUE_REQUIRED, 'tenantId') + ->addOption('instance-id', null, InputOption::VALUE_REQUIRED, 'instanceId') ->addOption('resource-key', null, InputOption::VALUE_REQUIRED, 'resourceKey') ->addOption('value', null, InputOption::VALUE_REQUIRED, 'Measured value') ->addOption('source', null, InputOption::VALUE_REQUIRED, 'source', 'am-driver-cli') @@ -36,7 +36,7 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); - foreach (['tenant-id', 'resource-key', 'value', 'am-url', 'token'] as $required) { + foreach (['instance-id', 'resource-key', 'value', 'am-url', 'token'] as $required) { if (!$input->getOption($required)) { $io->error(sprintf('Missing --%s', $required)); @@ -59,7 +59,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int )); $event = new ConsumptionWebhookEvent( - (string) $input->getOption('tenant-id'), + (string) $input->getOption('instance-id'), (string) $input->getOption('resource-key'), $value, (string) $occurredAt, diff --git a/src/Bridge/Console/Command/OrchestrationSimulateCommand.php b/src/Bridge/Console/Command/OrchestrationSimulateCommand.php index 70ab565..831f67a 100644 --- a/src/Bridge/Console/Command/OrchestrationSimulateCommand.php +++ b/src/Bridge/Console/Command/OrchestrationSimulateCommand.php @@ -26,7 +26,6 @@ protected function configure(): void ->addOption('base-url', null, InputOption::VALUE_REQUIRED, 'Receptacle base URL', 'http://127.0.0.1:8099') ->addOption('path', null, InputOption::VALUE_REQUIRED, 'Command path', '/internal/am/orchestration/commands') ->addOption('token', null, InputOption::VALUE_REQUIRED, 'X-Orchestration-Command-Token', 'dev-command-token') - ->addOption('tenant-id', null, InputOption::VALUE_REQUIRED, 'tenantId', 'am_ten_10000000-0000-4000-8000-000000000001') ->addOption('instance-id', null, InputOption::VALUE_REQUIRED, 'instanceId', 'am_ins_10000000-0000-4000-8000-000000000001') ->addOption('app-id', null, InputOption::VALUE_REQUIRED, 'appId', 'am_app_10000000-0000-4000-8000-000000000001') ->addOption('target-id', null, InputOption::VALUE_REQUIRED, 'targetId', 'local-receptacle'); @@ -54,7 +53,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int 'targetId' => (string) $input->getOption('target-id'), 'appId' => (string) $input->getOption('app-id'), 'instanceId' => $instanceId, - 'tenantId' => (string) $input->getOption('tenant-id'), 'correlationId' => 'cli_'.bin2hex(random_bytes(8)), 'idempotencyKey' => $instanceId.':'.strtolower($operationKey).'_instance:v1', 'occurredAt' => (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DateTimeInterface::ATOM), diff --git a/src/Bridge/Symfony/DependencyInjection/Configuration.php b/src/Bridge/Symfony/DependencyInjection/Configuration.php index 52a88bc..5381060 100644 --- a/src/Bridge/Symfony/DependencyInjection/Configuration.php +++ b/src/Bridge/Symfony/DependencyInjection/Configuration.php @@ -40,7 +40,6 @@ public function getConfigTreeBuilder(): TreeBuilder ->defaultNull() ->info('Override full path; default is derived from route_prefix.') ->end() - ->scalarNode('expected_tenant_id')->defaultNull()->end() ->scalarNode('expected_instance_id')->defaultNull()->end() ->end(); diff --git a/src/Bridge/Symfony/Factory/OperationalStateProcessorFactory.php b/src/Bridge/Symfony/Factory/OperationalStateProcessorFactory.php index 4dc710a..78accca 100644 --- a/src/Bridge/Symfony/Factory/OperationalStateProcessorFactory.php +++ b/src/Bridge/Symfony/Factory/OperationalStateProcessorFactory.php @@ -22,7 +22,6 @@ public static function create( ?OperationalStateReceiverInterface $receiver, array $config, ): OperationalStateProcessor { - $expectedTenant = $config['expected_tenant_id'] ?? null; $expectedInstance = $config['expected_instance_id'] ?? null; return new OperationalStateProcessor( @@ -30,7 +29,6 @@ public static function create( $receiptStore, $snapshotManager, $receiver, - \is_string($expectedTenant) && '' !== $expectedTenant ? $expectedTenant : null, \is_string($expectedInstance) && '' !== $expectedInstance ? $expectedInstance : null ); } diff --git a/src/Core/Dto/ConsumptionWebhookEvent.php b/src/Core/Dto/ConsumptionWebhookEvent.php index c55b335..bb49543 100644 --- a/src/Core/Dto/ConsumptionWebhookEvent.php +++ b/src/Core/Dto/ConsumptionWebhookEvent.php @@ -10,7 +10,7 @@ final class ConsumptionWebhookEvent { /** @var string */ - private $tenantId; + private $instanceId; /** @var string */ private $resourceKey; @@ -28,13 +28,13 @@ final class ConsumptionWebhookEvent * @param string|int|float $value */ public function __construct( - string $tenantId, + string $instanceId, string $resourceKey, $value, string $occurredAt, string $source, ) { - $this->tenantId = $tenantId; + $this->instanceId = $instanceId; $this->resourceKey = $resourceKey; $this->value = $value; $this->occurredAt = $occurredAt; @@ -46,8 +46,8 @@ public function __construct( */ public static function fromArray(array $data): self { - JsonPayloadValidator::requireKeys($data, ['tenantId', 'resourceKey', 'value', 'occurredAt', 'source']); - JsonPayloadValidator::requireNonEmptyString($data, 'tenantId'); + JsonPayloadValidator::requireKeys($data, ['instanceId', 'resourceKey', 'value', 'occurredAt', 'source']); + JsonPayloadValidator::requireNonEmptyString($data, 'instanceId'); JsonPayloadValidator::requireNonEmptyString($data, 'resourceKey'); JsonPayloadValidator::requireNonEmptyString($data, 'occurredAt'); JsonPayloadValidator::requireNonEmptyString($data, 'source'); @@ -57,7 +57,7 @@ public static function fromArray(array $data): self } return new self( - (string) $data['tenantId'], + (string) $data['instanceId'], (string) $data['resourceKey'], $data['value'], (string) $data['occurredAt'], @@ -65,9 +65,9 @@ public static function fromArray(array $data): self ); } - public function tenantId(): string + public function instanceId(): string { - return $this->tenantId; + return $this->instanceId; } public function resourceKey(): string @@ -99,7 +99,7 @@ public function source(): string public function toArray(): array { return [ - 'tenantId' => $this->tenantId, + 'instanceId' => $this->instanceId, 'resourceKey' => $this->resourceKey, 'value' => $this->value, 'occurredAt' => $this->occurredAt, diff --git a/src/Core/Dto/OrchestrationCommand.php b/src/Core/Dto/OrchestrationCommand.php index 9ede027..7499853 100644 --- a/src/Core/Dto/OrchestrationCommand.php +++ b/src/Core/Dto/OrchestrationCommand.php @@ -18,10 +18,7 @@ final class OrchestrationCommand /** @var string */ private $instanceId; - /** @var string */ - private $tenantId; - - /** @var string */ + /** @var string|null */ private $correlationId; /** @var string */ @@ -37,16 +34,14 @@ public function __construct( Operation $operation, string $appId, string $instanceId, - string $tenantId, - string $correlationId, string $idempotencyKey, string $occurredAt, + ?string $correlationId = null, ?string $instanceIntegrationToken = null, ) { $this->operation = $operation; $this->appId = $appId; $this->instanceId = $instanceId; - $this->tenantId = $tenantId; $this->correlationId = $correlationId; $this->idempotencyKey = $idempotencyKey; $this->occurredAt = $occurredAt; @@ -60,12 +55,18 @@ public static function fromArray(array $data): self { JsonPayloadValidator::requireKeys( $data, - ['operation', 'appId', 'instanceId', 'tenantId', 'correlationId', 'idempotencyKey', 'occurredAt'], + ['operation', 'appId', 'instanceId', 'idempotencyKey', 'occurredAt'], ); - foreach (['operation', 'appId', 'instanceId', 'tenantId', 'correlationId', 'idempotencyKey', 'occurredAt'] as $key) { + foreach (['operation', 'appId', 'instanceId', 'idempotencyKey', 'occurredAt'] as $key) { JsonPayloadValidator::requireNonEmptyString($data, $key); } + $correlationId = null; + if (isset($data['correlationId']) && \is_string($data['correlationId'])) { + $trimmed = trim($data['correlationId']); + $correlationId = '' !== $trimmed ? $trimmed : null; + } + $instanceToken = null; if (isset($data['instanceIntegrationToken']) && \is_string($data['instanceIntegrationToken'])) { $trimmed = trim($data['instanceIntegrationToken']); @@ -76,10 +77,9 @@ public static function fromArray(array $data): self Operation::fromString((string) $data['operation']), (string) $data['appId'], (string) $data['instanceId'], - (string) $data['tenantId'], - (string) $data['correlationId'], (string) $data['idempotencyKey'], (string) $data['occurredAt'], + $correlationId, $instanceToken, ); } @@ -99,12 +99,7 @@ public function instanceId(): string return $this->instanceId; } - public function tenantId(): string - { - return $this->tenantId; - } - - public function correlationId(): string + public function correlationId(): ?string { return $this->correlationId; } @@ -133,11 +128,12 @@ public function toArray(): array 'operation' => $this->operation->toString(), 'appId' => $this->appId, 'instanceId' => $this->instanceId, - 'tenantId' => $this->tenantId, - 'correlationId' => $this->correlationId, 'idempotencyKey' => $this->idempotencyKey, 'occurredAt' => $this->occurredAt, ]; + if (null !== $this->correlationId) { + $payload['correlationId'] = $this->correlationId; + } if (null !== $this->instanceIntegrationToken) { $payload['instanceIntegrationToken'] = $this->instanceIntegrationToken; } diff --git a/src/Core/OperationalState/FileOperationalStateReceiptStore.php b/src/Core/OperationalState/FileOperationalStateReceiptStore.php index b724cb9..2f3eb85 100644 --- a/src/Core/OperationalState/FileOperationalStateReceiptStore.php +++ b/src/Core/OperationalState/FileOperationalStateReceiptStore.php @@ -20,13 +20,13 @@ public function __construct(string $directory) } } - public function isDuplicate(string $tenantId, string $correlationId, string $computedAt): bool + public function isDuplicate(string $instanceId, string $correlationId, string $computedAt): bool { if ('' === $correlationId && '' === $computedAt) { return false; } - $stored = $this->load($tenantId); + $stored = $this->load($instanceId); if (null === $stored) { return false; } @@ -34,20 +34,20 @@ public function isDuplicate(string $tenantId, string $correlationId, string $com return $stored['correlationId'] === $correlationId && $stored['computedAt'] === $computedAt; } - public function remember(string $tenantId, string $correlationId, string $computedAt): void + public function remember(string $instanceId, string $correlationId, string $computedAt): void { - AtomicFileWriter::write($this->pathFor($tenantId), json_encode([ + AtomicFileWriter::write($this->pathFor($instanceId), json_encode([ 'correlationId' => $correlationId, 'computedAt' => $computedAt, - ], JSON_THROW_ON_ERROR)); + ], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES)); } /** * @return array{correlationId: string, computedAt: string}|null */ - private function load(string $tenantId): ?array + private function load(string $instanceId): ?array { - $path = $this->pathFor($tenantId); + $path = $this->pathFor($instanceId); if (!is_file($path)) { return null; } @@ -64,8 +64,8 @@ private function load(string $tenantId): ?array ]; } - private function pathFor(string $tenantId): string + private function pathFor(string $instanceId): string { - return $this->directory.'/'.preg_replace('/[^a-zA-Z0-9._-]+/', '_', $tenantId).'-receipt.json'; + return $this->directory.'/'.preg_replace('/[^a-zA-Z0-9._-]+/', '_', $instanceId).'-receipt.json'; } } diff --git a/src/Core/OperationalState/FileOperationalStateStore.php b/src/Core/OperationalState/FileOperationalStateStore.php index 6e11fb8..088618c 100644 --- a/src/Core/OperationalState/FileOperationalStateStore.php +++ b/src/Core/OperationalState/FileOperationalStateStore.php @@ -20,31 +20,32 @@ public function __construct(string $directory) } } - public function save(string $tenantId, array $document): void + public function save(string $instanceId, array $document): void { AtomicFileWriter::write( - $this->pathFor($tenantId), - json_encode($document, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT) + $this->pathFor($instanceId), + json_encode($document, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES) ); } - public function load(string $tenantId): ?array + public function load(string $instanceId): ?array { - $path = $this->pathFor($tenantId); + $path = $this->pathFor($instanceId); if (!is_file($path)) { return null; } + $json = file_get_contents($path); if (false === $json) { - throw new RuntimeException(sprintf('Cannot read operational state: %s', $path)); + return null; } - /* @var array */ + /** @var array $data */ return json_decode($json, true, 512, JSON_THROW_ON_ERROR); } - private function pathFor(string $tenantId): string + private function pathFor(string $instanceId): string { - return $this->directory.'/'.preg_replace('/[^a-zA-Z0-9._-]+/', '_', $tenantId).'-operational-state.json'; + return $this->directory.'/'.preg_replace('/[^a-zA-Z0-9._-]+/', '_', $instanceId).'-operational-state.json'; } } diff --git a/src/Core/OperationalState/OperationalStateProcessor.php b/src/Core/OperationalState/OperationalStateProcessor.php index ab24551..15ed594 100644 --- a/src/Core/OperationalState/OperationalStateProcessor.php +++ b/src/Core/OperationalState/OperationalStateProcessor.php @@ -29,9 +29,6 @@ final class OperationalStateProcessor /** @var OperationalStateReceiverInterface|null */ private $receiver; - /** @var string|null */ - private $expectedTenantId; - /** @var string|null */ private $expectedInstanceId; @@ -40,14 +37,12 @@ public function __construct( OperationalStateReceiptStoreInterface $receiptStore, ?ResourceSnapshotManager $snapshotManager = null, ?OperationalStateReceiverInterface $receiver = null, - ?string $expectedTenantId = null, ?string $expectedInstanceId = null, ) { $this->store = $store; $this->receiptStore = $receiptStore; $this->snapshotManager = $snapshotManager; $this->receiver = $receiver; - $this->expectedTenantId = $expectedTenantId; $this->expectedInstanceId = $expectedInstanceId; } @@ -60,24 +55,23 @@ public function process(array $document): array { InstanceOperationalStateValidator::validate( $document, - $this->expectedTenantId, $this->expectedInstanceId ); - $tenantId = (string) ($document['instance']['tenantId'] ?? ''); - if ('' === $tenantId) { - throw new ValidationException('instance.tenantId is required'); + $instanceId = (string) ($document['instance']['instanceId'] ?? ''); + if ('' === $instanceId) { + throw new ValidationException('instance.instanceId is required'); } $correlationId = (string) ($document['correlationId'] ?? ''); $computedAt = (string) ($document['computedAt'] ?? ''); - $duplicate = $this->receiptStore->isDuplicate($tenantId, $correlationId, $computedAt); + $duplicate = $this->receiptStore->isDuplicate($instanceId, $correlationId, $computedAt); - $this->store->save($tenantId, $document); - $this->receiptStore->remember($tenantId, $correlationId, $computedAt); + $this->store->save($instanceId, $document); + $this->receiptStore->remember($instanceId, $correlationId, $computedAt); if (null !== $this->snapshotManager) { - $this->snapshotManager->updateLastInboundOperationalState($tenantId, [ + $this->snapshotManager->updateLastInboundOperationalState($instanceId, [ 'schemaVersion' => self::SCHEMA_VERSION, 'correlationId' => $correlationId, 'computedAt' => $computedAt, diff --git a/src/Core/OperationalState/OperationalStateReceiptStoreInterface.php b/src/Core/OperationalState/OperationalStateReceiptStoreInterface.php index deb5002..29eff41 100644 --- a/src/Core/OperationalState/OperationalStateReceiptStoreInterface.php +++ b/src/Core/OperationalState/OperationalStateReceiptStoreInterface.php @@ -6,7 +6,7 @@ interface OperationalStateReceiptStoreInterface { - public function isDuplicate(string $tenantId, string $correlationId, string $computedAt): bool; + public function isDuplicate(string $instanceId, string $correlationId, string $computedAt): bool; - public function remember(string $tenantId, string $correlationId, string $computedAt): void; + public function remember(string $instanceId, string $correlationId, string $computedAt): void; } diff --git a/src/Core/OperationalState/OperationalStateStoreInterface.php b/src/Core/OperationalState/OperationalStateStoreInterface.php index 7652939..07c4bb2 100644 --- a/src/Core/OperationalState/OperationalStateStoreInterface.php +++ b/src/Core/OperationalState/OperationalStateStoreInterface.php @@ -9,10 +9,10 @@ interface OperationalStateStoreInterface /** * @param array $document */ - public function save(string $tenantId, array $document): void; + public function save(string $instanceId, array $document): void; /** * @return array|null */ - public function load(string $tenantId): ?array; + public function load(string $instanceId): ?array; } diff --git a/src/Core/Snapshot/ConsumptionPublisher.php b/src/Core/Snapshot/ConsumptionPublisher.php index c68b137..5422de1 100644 --- a/src/Core/Snapshot/ConsumptionPublisher.php +++ b/src/Core/Snapshot/ConsumptionPublisher.php @@ -32,9 +32,9 @@ public function __construct( $this->source = $source; } - public function pushResourceConsumption(string $tenantId, string $resourceKey): int + public function pushResourceConsumption(string $instanceId, string $resourceKey): int { - $snapshot = $this->snapshotManager->getSnapshot($tenantId); + $snapshot = $this->snapshotManager->getSnapshot($instanceId); $value = null; $occurredAt = (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DateTimeInterface::ATOM); @@ -50,11 +50,11 @@ public function pushResourceConsumption(string $tenantId, string $resourceKey): throw new InvalidArgumentException(sprintf('No measurement for resourceKey %s', $resourceKey)); } - $event = new ConsumptionWebhookEvent($tenantId, $resourceKey, $value, $occurredAt, $this->source); + $event = new ConsumptionWebhookEvent($instanceId, $resourceKey, $value, $occurredAt, $this->source); $response = $this->amApiClient->pushConsumption($event); if (202 === $response['statusCode']) { - $this->snapshotManager->markPushedToAm($tenantId, $resourceKey, $value, $occurredAt, $response['statusCode']); + $this->snapshotManager->markPushedToAm($instanceId, $resourceKey, $value, $occurredAt, $response['statusCode']); } return $response['statusCode']; @@ -63,10 +63,10 @@ public function pushResourceConsumption(string $tenantId, string $resourceKey): /** * @return list HTTP status codes per resource pushed */ - public function flushPendingToAm(string $tenantId): array + public function flushPendingToAm(string $instanceId): array { $statuses = []; - foreach ($this->snapshotManager->getSnapshot($tenantId)->resources() as $resource) { + foreach ($this->snapshotManager->getSnapshot($instanceId)->resources() as $resource) { $resourceKey = (string) ($resource['resourceKey'] ?? ''); if ('' === $resourceKey) { continue; @@ -79,7 +79,7 @@ public function flushPendingToAm(string $tenantId): array if (\is_array($lastPushed) && ($lastPushed['value'] ?? null) == $localValue && !empty($lastPushed['accepted'])) { continue; } - $statuses[] = $this->pushResourceConsumption($tenantId, $resourceKey); + $statuses[] = $this->pushResourceConsumption($instanceId, $resourceKey); } return $statuses; diff --git a/src/Core/Snapshot/FileResourceSnapshotStore.php b/src/Core/Snapshot/FileResourceSnapshotStore.php index bf70b6a..3e36040 100644 --- a/src/Core/Snapshot/FileResourceSnapshotStore.php +++ b/src/Core/Snapshot/FileResourceSnapshotStore.php @@ -23,21 +23,23 @@ public function __construct(string $directory, string $source) } } - public function findByTenantId(string $tenantId): ?ManagedInstanceResourceSnapshot + public function findByInstanceId(string $instanceId): ?ManagedInstanceResourceSnapshot { - return $this->load($tenantId); + return $this->load($instanceId); } - public function load(string $tenantId): ?ManagedInstanceResourceSnapshot + public function load(string $instanceId): ?ManagedInstanceResourceSnapshot { - $path = $this->pathFor($tenantId); + $path = $this->pathFor($instanceId); if (!is_file($path)) { return null; } + $json = file_get_contents($path); if (false === $json) { - throw new RuntimeException(sprintf('Cannot read snapshot: %s', $path)); + return null; } + /** @var array $data */ $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); @@ -47,20 +49,20 @@ public function load(string $tenantId): ?ManagedInstanceResourceSnapshot public function save(ManagedInstanceResourceSnapshot $snapshot): void { AtomicFileWriter::write( - $this->pathFor($snapshot->tenantId()), - json_encode($snapshot->toArray(), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT) + $this->pathFor($snapshot->instanceId()), + json_encode($snapshot->toArray(), JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES) ); } - public function getOrCreate(string $tenantId): ManagedInstanceResourceSnapshot + public function getOrCreate(string $instanceId): ManagedInstanceResourceSnapshot { - $existing = $this->load($tenantId); + $existing = $this->load($instanceId); - return $existing ?? ManagedInstanceResourceSnapshot::empty($tenantId, $this->source); + return $existing ?? ManagedInstanceResourceSnapshot::empty($instanceId, $this->source); } - private function pathFor(string $tenantId): string + private function pathFor(string $instanceId): string { - return $this->directory.'/'.preg_replace('/[^a-zA-Z0-9._-]+/', '_', $tenantId).'.json'; + return $this->directory.'/'.preg_replace('/[^a-zA-Z0-9._-]+/', '_', $instanceId).'.json'; } } diff --git a/src/Core/Snapshot/ManagedInstanceResourceSnapshot.php b/src/Core/Snapshot/ManagedInstanceResourceSnapshot.php index 33e345e..dce41a4 100644 --- a/src/Core/Snapshot/ManagedInstanceResourceSnapshot.php +++ b/src/Core/Snapshot/ManagedInstanceResourceSnapshot.php @@ -15,7 +15,7 @@ final class ManagedInstanceResourceSnapshot public const SCHEMA_VERSION = 'managed-instance-resource-snapshot.v1'; /** @var string */ - private $tenantId; + private $instanceId; /** @var string */ private $source; @@ -34,23 +34,23 @@ final class ManagedInstanceResourceSnapshot * @param array|null $lastInboundOperationalState */ public function __construct( - string $tenantId, + string $instanceId, string $source, string $updatedAt, array $resources, ?array $lastInboundOperationalState = null, ) { - $this->tenantId = $tenantId; + $this->instanceId = $instanceId; $this->source = $source; $this->updatedAt = $updatedAt; $this->resources = $resources; $this->lastInboundOperationalState = $lastInboundOperationalState; } - public static function empty(string $tenantId, string $source): self + public static function empty(string $instanceId, string $source): self { return new self( - $tenantId, + $instanceId, $source, (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DateTimeInterface::ATOM), [] @@ -62,7 +62,7 @@ public static function empty(string $tenantId, string $source): self */ public static function fromArray(array $data): self { - JsonPayloadValidator::requireKeys($data, ['schemaVersion', 'tenantId', 'updatedAt', 'source', 'resources']); + JsonPayloadValidator::requireKeys($data, ['schemaVersion', 'instanceId', 'updatedAt', 'source', 'resources']); JsonPayloadValidator::assertSchemaVersion((string) $data['schemaVersion'], self::SCHEMA_VERSION); if (!\is_array($data['resources'])) { throw new ValidationException('resources must be an array'); @@ -80,7 +80,7 @@ public static function fromArray(array $data): self $resources = array_values($data['resources']); return new self( - (string) $data['tenantId'], + (string) $data['instanceId'], (string) $data['source'], (string) $data['updatedAt'], $resources, @@ -88,9 +88,9 @@ public static function fromArray(array $data): self ); } - public function tenantId(): string + public function instanceId(): string { - return $this->tenantId; + return $this->instanceId; } public function source(): string @@ -121,7 +121,7 @@ public function toArray(): array { $data = [ 'schemaVersion' => self::SCHEMA_VERSION, - 'tenantId' => $this->tenantId, + 'instanceId' => $this->instanceId, 'updatedAt' => $this->updatedAt, 'source' => $this->source, 'resources' => $this->resources, @@ -136,7 +136,7 @@ public function toArray(): array public function withUpdatedAtNow(): self { return new self( - $this->tenantId, + $this->instanceId, $this->source, (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DateTimeInterface::ATOM), $this->resources, @@ -150,7 +150,7 @@ public function withUpdatedAtNow(): self public function withLastInboundOperationalState(array $lastInboundOperationalState): self { return new self( - $this->tenantId, + $this->instanceId, $this->source, (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DateTimeInterface::ATOM), $this->resources, @@ -186,7 +186,7 @@ public function withResourceMeasurement(string $resourceKey, $value, string $mea } return new self( - $this->tenantId, + $this->instanceId, $this->source, (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DateTimeInterface::ATOM), $resources, @@ -214,7 +214,7 @@ public function withLastPushedToAm(string $resourceKey, $value, string $occurred } return new self( - $this->tenantId, + $this->instanceId, $this->source, (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DateTimeInterface::ATOM), $resources, diff --git a/src/Core/Snapshot/ResourceSnapshotManager.php b/src/Core/Snapshot/ResourceSnapshotManager.php index ef5c310..157c338 100644 --- a/src/Core/Snapshot/ResourceSnapshotManager.php +++ b/src/Core/Snapshot/ResourceSnapshotManager.php @@ -10,10 +10,10 @@ final class ResourceSnapshotManager { - /** @var FileResourceSnapshotStore */ + /** @var ResourceSnapshotStoreInterface */ private $store; - public function __construct(FileResourceSnapshotStore $store) + public function __construct(ResourceSnapshotStoreInterface $store) { $this->store = $store; } @@ -21,38 +21,38 @@ public function __construct(FileResourceSnapshotStore $store) /** * @param string|int|float $value */ - public function recordMeasurement(string $tenantId, string $resourceKey, $value, ?string $measuredAt = null): void + public function recordMeasurement(string $instanceId, string $resourceKey, $value, ?string $measuredAt = null): void { $measuredAt = $measuredAt ?? (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DateTimeInterface::ATOM); - $snapshot = $this->store->getOrCreate($tenantId)->withResourceMeasurement($resourceKey, $value, $measuredAt); + $snapshot = $this->store->getOrCreate($instanceId)->withResourceMeasurement($resourceKey, $value, $measuredAt); $this->store->save($snapshot); } /** * @param string|int|float $value */ - public function markPushedToAm(string $tenantId, string $resourceKey, $value, string $occurredAt, int $httpStatus): void + public function markPushedToAm(string $instanceId, string $resourceKey, $value, string $occurredAt, int $httpStatus): void { - $snapshot = $this->store->getOrCreate($tenantId)->withLastPushedToAm($resourceKey, $value, $occurredAt, $httpStatus); + $snapshot = $this->store->getOrCreate($instanceId)->withLastPushedToAm($resourceKey, $value, $occurredAt, $httpStatus); $this->store->save($snapshot); } - public function getSnapshot(string $tenantId): ManagedInstanceResourceSnapshot + public function getSnapshot(string $instanceId): ManagedInstanceResourceSnapshot { - return $this->store->getOrCreate($tenantId); + return $this->store->getOrCreate($instanceId); } - public function findByTenantId(string $tenantId): ?ManagedInstanceResourceSnapshot + public function findByInstanceId(string $instanceId): ?ManagedInstanceResourceSnapshot { - return $this->store->findByTenantId($tenantId); + return $this->store->findByInstanceId($instanceId); } /** * @param array $operationalStateMeta */ - public function updateLastInboundOperationalState(string $tenantId, array $operationalStateMeta): void + public function updateLastInboundOperationalState(string $instanceId, array $operationalStateMeta): void { - $snapshot = $this->store->getOrCreate($tenantId)->withLastInboundOperationalState($operationalStateMeta); + $snapshot = $this->store->getOrCreate($instanceId)->withLastInboundOperationalState($operationalStateMeta); $this->store->save($snapshot); } } diff --git a/src/Core/Snapshot/ResourceSnapshotStoreInterface.php b/src/Core/Snapshot/ResourceSnapshotStoreInterface.php index abcad22..b7ef525 100644 --- a/src/Core/Snapshot/ResourceSnapshotStoreInterface.php +++ b/src/Core/Snapshot/ResourceSnapshotStoreInterface.php @@ -6,12 +6,14 @@ interface ResourceSnapshotStoreInterface { - public function load(string $tenantId): ?ManagedInstanceResourceSnapshot; + public function load(string $instanceId): ?ManagedInstanceResourceSnapshot; /** - * Lecture externe du snapshot persisté pour un tenant (alias sémantique de {@see load()}). + * Lecture externe du snapshot persisté pour une instance (alias sémantique de {@see load()}). */ - public function findByTenantId(string $tenantId): ?ManagedInstanceResourceSnapshot; + public function findByInstanceId(string $instanceId): ?ManagedInstanceResourceSnapshot; public function save(ManagedInstanceResourceSnapshot $snapshot): void; + + public function getOrCreate(string $instanceId): ManagedInstanceResourceSnapshot; } diff --git a/src/Core/Tenant/FileTenantWorkspace.php b/src/Core/Tenant/FileTenantWorkspace.php index 1d8aee0..42e1f8d 100644 --- a/src/Core/Tenant/FileTenantWorkspace.php +++ b/src/Core/Tenant/FileTenantWorkspace.php @@ -4,18 +4,16 @@ namespace ApplicationManagerTools\AmDriver\Core\Tenant; -use DateTimeImmutable; -use DateTimeInterface; +use ApplicationManagerTools\AmDriver\Core\Snapshot\AtomicFileWriter; use RuntimeException; /** - * Espace disque optionnel par tenant côté application gérée (hors persistance métier hôte). - * Utile pour handlers STOP/START locaux (ex. marqueur « suspendu ») sans coupler au produit AM. + * Espace disque optionnel par instance côté application gérée (hors persistance métier hôte). */ final class FileTenantWorkspace { private const SUSPENDED_FLAG = 'suspended.flag'; - private const INSTANCE_TOKEN_FILE = 'instance-integration-token.txt'; + private const INSTANCE_TOKEN_FILE = 'instance-integration.token'; /** @var string */ private $tenantsBaseDirectory; @@ -25,9 +23,9 @@ public function __construct(string $tenantsBaseDirectory) $this->tenantsBaseDirectory = rtrim($tenantsBaseDirectory, '/'); } - public function ensureContext(string $tenantId): string + public function ensureContext(string $instanceId): string { - $dir = $this->directoryFor($tenantId); + $dir = $this->directoryFor($instanceId); if (!is_dir($dir) && !mkdir($dir, 0775, true) && !is_dir($dir)) { throw new RuntimeException(sprintf('Cannot create tenant directory: %s', $dir)); } @@ -35,51 +33,45 @@ public function ensureContext(string $tenantId): string return $dir; } - public function markSuspended(string $tenantId): void + public function markSuspended(string $instanceId): void { - $dir = $this->ensureContext($tenantId); - $flag = $dir.'/'.self::SUSPENDED_FLAG; - if (false === file_put_contents($flag, (new DateTimeImmutable())->format(DateTimeInterface::ATOM))) { - throw new RuntimeException(sprintf('Cannot write %s', $flag)); - } + $dir = $this->ensureContext($instanceId); + AtomicFileWriter::write($dir.'/'.self::SUSPENDED_FLAG, ''); } - public function clearSuspended(string $tenantId): void + public function clearSuspended(string $instanceId): void { - $flag = $this->directoryFor($tenantId).'/'.self::SUSPENDED_FLAG; - if (is_file($flag) && !unlink($flag)) { - throw new RuntimeException(sprintf('Cannot remove %s', $flag)); + $flag = $this->directoryFor($instanceId).'/'.self::SUSPENDED_FLAG; + if (is_file($flag)) { + unlink($flag); } } - public function isSuspended(string $tenantId): bool + public function isSuspended(string $instanceId): bool { - return is_file($this->directoryFor($tenantId).'/'.self::SUSPENDED_FLAG); + return is_file($this->directoryFor($instanceId).'/'.self::SUSPENDED_FLAG); } - public function storeInstanceIntegrationToken(string $tenantId, string $token): void + public function storeInstanceIntegrationToken(string $instanceId, string $token): void { - $dir = $this->ensureContext($tenantId); - $path = $dir.'/'.self::INSTANCE_TOKEN_FILE; - if (false === file_put_contents($path, $token)) { - throw new RuntimeException(sprintf('Cannot write %s', $path)); - } + $dir = $this->ensureContext($instanceId); + AtomicFileWriter::write($dir.'/'.self::INSTANCE_TOKEN_FILE, $token); } - public function instanceIntegrationToken(string $tenantId): ?string + public function instanceIntegrationToken(string $instanceId): ?string { - $path = $this->directoryFor($tenantId).'/'.self::INSTANCE_TOKEN_FILE; + $path = $this->directoryFor($instanceId).'/'.self::INSTANCE_TOKEN_FILE; if (!is_file($path)) { return null; } $content = file_get_contents($path); - return \is_string($content) && '' !== trim($content) ? trim($content) : null; + return false === $content ? null : trim($content); } - private function directoryFor(string $tenantId): string + private function directoryFor(string $instanceId): string { - $safe = preg_replace('/[^a-zA-Z0-9._-]+/', '_', $tenantId) ?? $tenantId; + $safe = preg_replace('/[^a-zA-Z0-9._-]+/', '_', $instanceId) ?? $instanceId; return $this->tenantsBaseDirectory.'/'.$safe; } diff --git a/src/Core/Validation/InstanceOperationalStateValidator.php b/src/Core/Validation/InstanceOperationalStateValidator.php index b4137a4..4c433f8 100644 --- a/src/Core/Validation/InstanceOperationalStateValidator.php +++ b/src/Core/Validation/InstanceOperationalStateValidator.php @@ -11,13 +11,12 @@ final class InstanceOperationalStateValidator { /** * Validation minimale alignée sur le document réellement poussé par AM - * (`BuildInstanceOperationalStateCommand`) — pas l’exemple long de la spec. + * (`BuildInstanceOperationalStateCommand`) — pas l'exemple long de la spec. * * @param array $document */ public static function validate( array $document, - ?string $expectedTenantId = null, ?string $expectedInstanceId = null, ): void { JsonPayloadValidator::requireKeys($document, ['schemaVersion', 'kind', 'instance']); @@ -34,19 +33,9 @@ public static function validate( throw new ValidationException('instance must be an object'); } - JsonPayloadValidator::requireNonEmptyString($document['instance'], 'tenantId'); + JsonPayloadValidator::requireNonEmptyString($document['instance'], 'instanceId'); - if (null !== $expectedTenantId && $document['instance']['tenantId'] !== $expectedTenantId) { - throw new ValidationException('tenantId mismatch for this deployment'); - } - - if ( - null !== $expectedInstanceId - && isset($document['instance']['instanceId']) - && \is_string($document['instance']['instanceId']) - && '' !== $document['instance']['instanceId'] - && $document['instance']['instanceId'] !== $expectedInstanceId - ) { + if (null !== $expectedInstanceId && $document['instance']['instanceId'] !== $expectedInstanceId) { throw new ValidationException('instanceId mismatch for this deployment'); } } diff --git a/tests/Integration/ReceptacleHttpKernelTest.php b/tests/Integration/ReceptacleHttpKernelTest.php index 1910add..7007bbf 100644 --- a/tests/Integration/ReceptacleHttpKernelTest.php +++ b/tests/Integration/ReceptacleHttpKernelTest.php @@ -56,7 +56,7 @@ public function testOperationalStatePersistsSnapshotCorrelation(): void $statePath = ReceiverRoutePaths::operationalStatePath(ReceiverRoutePaths::DEFAULT_ROUTE_PREFIX); [$status] = $kernel->handle('POST', $statePath, $body, $headers); $snapshotStore = new FileResourceSnapshotStore($dataDir.'/snapshots', 'captain-learning'); - $snapshot = $snapshotStore->load('am_ten_30000000-0000-4000-8000-000000000001'); + $snapshot = $snapshotStore->load('am_ins_30000000-0000-4000-8000-000000000001'); // Assert self::assertSame(200, $status); diff --git a/tests/Unit/Http/AmApiClientTest.php b/tests/Unit/Http/AmApiClientTest.php index 3652ac7..4a86c60 100644 --- a/tests/Unit/Http/AmApiClientTest.php +++ b/tests/Unit/Http/AmApiClientTest.php @@ -22,7 +22,7 @@ public function testPushConsumptionUsesTokenHeader(): void // Act $response = $api->pushConsumption(new ConsumptionWebhookEvent( - 'am_ten_10000000-0000-4000-8000-000000000001', + 'am_ins_10000000-0000-4000-8000-000000000001', 'seats', '12', '2026-05-14T12:00:00+00:00', diff --git a/tests/Unit/Snapshot/ConsumptionPublisherTest.php b/tests/Unit/Snapshot/ConsumptionPublisherTest.php index f9f042d..28a0ce0 100644 --- a/tests/Unit/Snapshot/ConsumptionPublisherTest.php +++ b/tests/Unit/Snapshot/ConsumptionPublisherTest.php @@ -19,8 +19,8 @@ public function testMarksPushedOnlyOnHttp202(): void // Arrange $dir = sys_get_temp_dir().'/am-driver-pub-'.uniqid('', true); $manager = new ResourceSnapshotManager(new FileResourceSnapshotStore($dir, 'captain-learning')); - $tenantId = 'am_ten_10000000-0000-4000-8000-000000000001'; - $manager->recordMeasurement($tenantId, 'seats', 9); + $instanceId = 'am_ins_10000000-0000-4000-8000-000000000001'; + $manager->recordMeasurement($instanceId, 'seats', 9); $client = new class implements AmApiClientInterface { /** @var int */ @@ -40,12 +40,12 @@ public function reportOrchestrationCallback(OrchestrationCallbackRequest $reques // Act — 200 must not mark as pushed $client->statusCode = 200; - $publisher->pushResourceConsumption($tenantId, 'seats'); - $after200 = $manager->getSnapshot($tenantId)->resources()[0]['lastPushedToAm'] ?? null; + $publisher->pushResourceConsumption($instanceId, 'seats'); + $after200 = $manager->getSnapshot($instanceId)->resources()[0]['lastPushedToAm'] ?? null; $client->statusCode = 202; - $publisher->pushResourceConsumption($tenantId, 'seats'); - $after202 = $manager->getSnapshot($tenantId)->resources()[0]['lastPushedToAm'] ?? null; + $publisher->pushResourceConsumption($instanceId, 'seats'); + $after202 = $manager->getSnapshot($instanceId)->resources()[0]['lastPushedToAm'] ?? null; // Assert self::assertNull($after200); diff --git a/tests/Unit/Snapshot/FileResourceSnapshotStoreTest.php b/tests/Unit/Snapshot/FileResourceSnapshotStoreTest.php index 5ea6b4a..5cd29ea 100644 --- a/tests/Unit/Snapshot/FileResourceSnapshotStoreTest.php +++ b/tests/Unit/Snapshot/FileResourceSnapshotStoreTest.php @@ -9,39 +9,42 @@ final class FileResourceSnapshotStoreTest extends TestCase { - private string $dataDir; + /** @var string */ + private $directory; protected function setUp(): void { - $this->dataDir = sys_get_temp_dir().'/am-driver-snapshot-'.uniqid('', true); + $this->directory = sys_get_temp_dir().'/am-driver-snapshot-'.uniqid('', true); } protected function tearDown(): void { - $file = $this->dataDir.'/am_ten_test.json'; - if (is_file($file)) { - unlink($file); + $files = glob($this->directory.'/*') ?: []; + foreach ($files as $file) { + if (is_file($file)) { + unlink($file); + } } - if (is_dir($this->dataDir)) { - rmdir($this->dataDir); + if (is_dir($this->directory)) { + rmdir($this->directory); } } - public function testFindByTenantIdDelegatesToLoad(): void + public function testFindByInstanceIdDelegatesToLoad(): void { // Arrange - $store = new FileResourceSnapshotStore($this->dataDir, 'application-manager'); - $tenantId = 'am_ten_test'; - $snapshot = $store->getOrCreate($tenantId); - $store->save($snapshot->withResourceMeasurement('seats', 3, '2026-05-15T10:00:00+00:00')); + $store = new FileResourceSnapshotStore($this->directory, 'test-source'); + $instanceId = 'am_ins_test'; + $snapshot = $store->getOrCreate($instanceId); + $store->save($snapshot); // Act - $found = $store->findByTenantId($tenantId); - $missing = $store->findByTenantId('am_ten_unknown'); + $found = $store->findByInstanceId($instanceId); + $missing = $store->findByInstanceId('am_ins_unknown'); // Assert self::assertNotNull($found); - self::assertSame($tenantId, $found->tenantId()); self::assertNull($missing); + self::assertSame($instanceId, $found->instanceId()); } } diff --git a/tests/Unit/Snapshot/ManagedInstanceResourceSnapshotTest.php b/tests/Unit/Snapshot/ManagedInstanceResourceSnapshotTest.php index a2ec083..e40a225 100644 --- a/tests/Unit/Snapshot/ManagedInstanceResourceSnapshotTest.php +++ b/tests/Unit/Snapshot/ManagedInstanceResourceSnapshotTest.php @@ -22,21 +22,9 @@ public function testRoundTripFromFixture(): void $roundTrip = $snapshot->toArray(); // Assert - self::assertSame(ManagedInstanceResourceSnapshot::SCHEMA_VERSION, $roundTrip['schemaVersion']); - self::assertSame('am_ten_10000000-0000-4000-8000-000000000001', $roundTrip['tenantId']); - self::assertCount(1, $roundTrip['resources']); - } - - public function testRecordMeasurementAddsResource(): void - { - // Arrange - $snapshot = ManagedInstanceResourceSnapshot::empty('am_ten_x', 'captain-learning'); - - // Act - $updated = $snapshot->withResourceMeasurement('seats', 12, '2026-05-14T12:00:00+00:00'); - - // Assert - self::assertSame('seats', $updated->resources()[0]['resourceKey']); - self::assertSame(12, $updated->resources()[0]['localMeasuredValue']); + self::assertSame('managed-instance-resource-snapshot.v1', $roundTrip['schemaVersion']); + self::assertSame('am_ins_10000000-0000-4000-8000-000000000001', $roundTrip['instanceId']); + self::assertSame('captain-learning', $roundTrip['source']); + self::assertSame([], $roundTrip['resources']); } } diff --git a/tests/Unit/Tenant/FileTenantWorkspaceTest.php b/tests/Unit/Tenant/FileTenantWorkspaceTest.php index 8534ca6..ec2e00b 100644 --- a/tests/Unit/Tenant/FileTenantWorkspaceTest.php +++ b/tests/Unit/Tenant/FileTenantWorkspaceTest.php @@ -9,7 +9,8 @@ final class FileTenantWorkspaceTest extends TestCase { - private string $baseDir; + /** @var string */ + private $baseDir; protected function setUp(): void { @@ -18,49 +19,49 @@ protected function setUp(): void protected function tearDown(): void { - $tenantDir = $this->baseDir.'/tenant_a'; - $flag = $tenantDir.'/suspended.flag'; + $instanceDir = $this->baseDir.'/instance_a'; + $flag = $instanceDir.'/suspended.flag'; if (is_file($flag)) { unlink($flag); } - if (is_dir($tenantDir)) { - rmdir($tenantDir); + if (is_dir($instanceDir)) { + rmdir($instanceDir); } if (is_dir($this->baseDir)) { rmdir($this->baseDir); } } - public function testEnsureContextCreatesDirectoryForSanitizedTenantId(): void + public function testEnsureContextCreatesDirectoryForSanitizedInstanceId(): void { // Arrange $workspace = new FileTenantWorkspace($this->baseDir); - $tenantId = 'tenant/a'; + $instanceId = 'instance/a'; // Act - $path = $workspace->ensureContext($tenantId); + $path = $workspace->ensureContext($instanceId); // Assert self::assertDirectoryExists($path); - self::assertStringEndsWith('/tenant_a', $path); + self::assertStringEndsWith('/instance_a', $path); } - public function testSuspendedFlagLifecycle(): void + public function testSuspendAndClearFlags(): void { // Arrange $workspace = new FileTenantWorkspace($this->baseDir); - $tenantId = 'tenant_a'; + $instanceId = 'instance_a'; // Act - $workspace->markSuspended($tenantId); + $workspace->markSuspended($instanceId); // Assert - self::assertTrue($workspace->isSuspended($tenantId)); + self::assertTrue($workspace->isSuspended($instanceId)); // Act - $workspace->clearSuspended($tenantId); + $workspace->clearSuspended($instanceId); // Assert - self::assertFalse($workspace->isSuspended($tenantId)); + self::assertFalse($workspace->isSuspended($instanceId)); } } diff --git a/tests/fixtures/instance-operational-state-am-minimal.json b/tests/fixtures/instance-operational-state-am-minimal.json index 71d4603..41771ef 100644 --- a/tests/fixtures/instance-operational-state-am-minimal.json +++ b/tests/fixtures/instance-operational-state-am-minimal.json @@ -9,7 +9,6 @@ }, "instance": { "instanceId": "am_ins_30000000-0000-4000-8000-000000000001", - "tenantId": "am_ten_30000000-0000-4000-8000-000000000001", "location": "https://captain-learning.example/" }, "subscription": { diff --git a/tests/fixtures/instance-operational-state-minimal.json b/tests/fixtures/instance-operational-state-minimal.json index a9449bb..02171fa 100644 --- a/tests/fixtures/instance-operational-state-minimal.json +++ b/tests/fixtures/instance-operational-state-minimal.json @@ -8,8 +8,7 @@ "appName": "Captain Learning" }, "instance": { - "instanceId": "am_ins_10000000-0000-4000-8000-000000000001", - "tenantId": "am_ten_10000000-0000-4000-8000-000000000001" + "instanceId": "am_ins_10000000-0000-4000-8000-000000000001" }, "subscription": { "subscriptionId": "am_sub_01JABC", diff --git a/tests/fixtures/managed-instance-resource-snapshot-minimal.json b/tests/fixtures/managed-instance-resource-snapshot-minimal.json index 524461f..18c55eb 100644 --- a/tests/fixtures/managed-instance-resource-snapshot-minimal.json +++ b/tests/fixtures/managed-instance-resource-snapshot-minimal.json @@ -1,14 +1,7 @@ { "schemaVersion": "managed-instance-resource-snapshot.v1", - "tenantId": "am_ten_10000000-0000-4000-8000-000000000001", - "updatedAt": "2026-05-14T12:00:00.000Z", + "instanceId": "am_ins_10000000-0000-4000-8000-000000000001", + "updatedAt": "2026-05-14T12:00:00+00:00", "source": "captain-learning", - "resources": [ - { - "resourceKey": "proof_storage_mo", - "localMeasuredValue": "128", - "measuredAt": "2026-05-14T11:59:00.000Z", - "lastPushedToAm": null - } - ] + "resources": [] } diff --git a/tests/fixtures/orchestration-command-create.json b/tests/fixtures/orchestration-command-create.json index 5bcd0d6..39bdd73 100644 --- a/tests/fixtures/orchestration-command-create.json +++ b/tests/fixtures/orchestration-command-create.json @@ -2,7 +2,6 @@ "operation": "CREATE_INSTANCE", "appId": "am_app_10000000-0000-4000-8000-000000000001", "instanceId": "am_ins_10000000-0000-4000-8000-000000000001", - "tenantId": "am_ten_10000000-0000-4000-8000-000000000001", "correlationId": "corr_test_001", "idempotencyKey": "am_ins_10000000-0000-4000-8000-000000000001:create_instance:v1", "occurredAt": "2026-05-14T15:35:00+00:00" From 923d1a11f8a23037b4e46e06cb1de947e88ac608 Mon Sep 17 00:00:00 2001 From: EddyLogipro Date: Thu, 11 Jun 2026 16:19:33 +0200 Subject: [PATCH 2/3] Correction codecheck --- src/Core/Cli/ReceptacleHttpKernel.php | 12 +++++++----- .../OperationalState/FileOperationalStateStore.php | 8 ++++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Core/Cli/ReceptacleHttpKernel.php b/src/Core/Cli/ReceptacleHttpKernel.php index 7e37910..b48fdd0 100644 --- a/src/Core/Cli/ReceptacleHttpKernel.php +++ b/src/Core/Cli/ReceptacleHttpKernel.php @@ -59,12 +59,14 @@ public function handle(string $method, string $uri, string $body, array $headers { $path = parse_url($uri, PHP_URL_PATH) ?: '/'; - if ('POST' === $method && $path === $this->orchestrationPath) { - return $this->handleOrchestration($body, $headers); - } + if ('POST' === $method) { + if ($path === $this->orchestrationPath) { + return $this->handleOrchestration($body, $headers); + } - if ('POST' === $method && $path === $this->operationalStatePath) { - return $this->handleOperationalState($body, $headers); + if ($path === $this->operationalStatePath) { + return $this->handleOperationalState($body, $headers); + } } if ('GET' === $method && '/' === $path) { diff --git a/src/Core/OperationalState/FileOperationalStateStore.php b/src/Core/OperationalState/FileOperationalStateStore.php index 088618c..65a35e3 100644 --- a/src/Core/OperationalState/FileOperationalStateStore.php +++ b/src/Core/OperationalState/FileOperationalStateStore.php @@ -40,8 +40,12 @@ public function load(string $instanceId): ?array return null; } - /** @var array $data */ - return json_decode($json, true, 512, JSON_THROW_ON_ERROR); + $decoded = json_decode($json, true, 512, JSON_THROW_ON_ERROR); + if (!\is_array($decoded)) { + return null; + } + + return $decoded; } private function pathFor(string $instanceId): string From 9e97f964a58535853047e3f21b18bd6f2ae6ea1b Mon Sep 17 00:00:00 2001 From: EddyLogipro Date: Thu, 11 Jun 2026 16:38:25 +0200 Subject: [PATCH 3/3] Fix CI test --- .php-cs-fixer.dist.php | 4 ++++ src/Bridge/Console/Command/CallbackSendCommand.php | 4 ++-- .../Console/Command/ConsumptionPushCommand.php | 4 ++-- src/Bridge/Console/Command/ServeCommand.php | 4 ++-- .../Controller/OrchestrationCommandController.php | 2 +- .../Symfony/Factory/AmApiClientConfigFactory.php | 2 +- .../Factory/ConsumptionPublisherFactory.php | 2 +- .../Factory/OperationalStateProcessorFactory.php | 4 ++-- src/Core/Cli/ReceptacleBootstrapFactory.php | 6 +++--- src/Core/Cli/ReceptacleHttpKernel.php | 2 +- src/Core/Cli/ReceptacleServerRouter.php | 2 +- src/Core/Dto/ConsumptionWebhookEvent.php | 4 ++-- src/Core/Dto/OrchestrationCallbackRequest.php | 2 +- src/Core/Dto/OrchestrationCommand.php | 2 +- src/Core/Http/AmApiClient.php | 2 +- src/Core/Http/AmApiClientConfig.php | 2 +- .../OperationalState/FileOperationalStateStore.php | 2 +- .../OperationalState/OperationalStateProcessor.php | 4 ++-- .../OrchestrationCommandProcessor.php | 4 ++-- src/Core/Snapshot/ConsumptionPublisher.php | 2 +- src/Core/Snapshot/FileResourceSnapshotStore.php | 2 +- .../Snapshot/ManagedInstanceResourceSnapshot.php | 14 +++++++------- .../InstanceOperationalStateValidator.php | 4 ++-- tests/Integration/ReceptacleHttpKernelTest.php | 6 +++--- tests/Unit/Http/AmApiClientTest.php | 4 ++-- .../OperationalStateProcessorTest.php | 2 +- 26 files changed, 48 insertions(+), 44 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 54fd755..b1f399c 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -13,5 +13,9 @@ '@Symfony' => true, 'declare_strict_types' => true, 'global_namespace_import' => ['import_classes' => true], + // PHP 7.4 : pas de virgule finale dans les listes de paramètres (PHP 8.0+). + 'trailing_comma_in_multiline' => [ + 'elements' => ['arrays', 'arguments', 'match'], + ], ]) ->setFinder($finder); diff --git a/src/Bridge/Console/Command/CallbackSendCommand.php b/src/Bridge/Console/Command/CallbackSendCommand.php index 32bc615..d83390f 100644 --- a/src/Bridge/Console/Command/CallbackSendCommand.php +++ b/src/Bridge/Console/Command/CallbackSendCommand.php @@ -43,13 +43,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int $client = new AmApiClient(HttpClient::create(), new AmApiClientConfig( (string) $input->getOption('am-url'), 'unused-consumption-token', - (string) $input->getOption('token') + (string) $input->getOption('token'), )); $request = new OrchestrationCallbackRequest( (string) $input->getOption('idempotency-key'), CallbackStatus::fromString((string) $input->getOption('status')), - $input->getOption('message') ? (string) $input->getOption('message') : null + $input->getOption('message') ? (string) $input->getOption('message') : null, ); $response = $client->reportOrchestrationCallback($request); diff --git a/src/Bridge/Console/Command/ConsumptionPushCommand.php b/src/Bridge/Console/Command/ConsumptionPushCommand.php index 68fa6e7..baf55d4 100644 --- a/src/Bridge/Console/Command/ConsumptionPushCommand.php +++ b/src/Bridge/Console/Command/ConsumptionPushCommand.php @@ -55,7 +55,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $client = new AmApiClient(HttpClient::create(), new AmApiClientConfig( (string) $input->getOption('am-url'), (string) $input->getOption('token'), - 'unused-callback-token' + 'unused-callback-token', )); $event = new ConsumptionWebhookEvent( @@ -63,7 +63,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int (string) $input->getOption('resource-key'), $value, (string) $occurredAt, - (string) $input->getOption('source') + (string) $input->getOption('source'), ); $response = $client->pushConsumption($event); diff --git a/src/Bridge/Console/Command/ServeCommand.php b/src/Bridge/Console/Command/ServeCommand.php index 7ba450d..82bf748 100644 --- a/src/Bridge/Console/Command/ServeCommand.php +++ b/src/Bridge/Console/Command/ServeCommand.php @@ -63,7 +63,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $orchestrationPath, $host, $port, - $statePath + $statePath, )); $cmd = sprintf( @@ -72,7 +72,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int escapeshellarg($host), escapeshellarg($port), escapeshellarg($docRoot), - escapeshellarg($router) + escapeshellarg($router), ); passthru($cmd, $exitCode); diff --git a/src/Bridge/Symfony/Controller/OrchestrationCommandController.php b/src/Bridge/Symfony/Controller/OrchestrationCommandController.php index b3346c1..f2ed521 100644 --- a/src/Bridge/Symfony/Controller/OrchestrationCommandController.php +++ b/src/Bridge/Symfony/Controller/OrchestrationCommandController.php @@ -41,7 +41,7 @@ public function __invoke(Request $request): JsonResponse return new JsonResponse( ['accepted' => true, 'alreadyProcessed' => $result['alreadyProcessed']], - $result['httpStatus'] + $result['httpStatus'], ); } catch (ValidationException $e) { return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_BAD_REQUEST); diff --git a/src/Bridge/Symfony/Factory/AmApiClientConfigFactory.php b/src/Bridge/Symfony/Factory/AmApiClientConfigFactory.php index 43eb76e..d90263d 100644 --- a/src/Bridge/Symfony/Factory/AmApiClientConfigFactory.php +++ b/src/Bridge/Symfony/Factory/AmApiClientConfigFactory.php @@ -19,7 +19,7 @@ public static function create(array $config): AmApiClientConfig (string) $config['orchestration_callback_token'], (float) $config['http_timeout'], (int) $config['consumption_max_retries'], - (int) $config['consumption_retry_delay_ms'] + (int) $config['consumption_retry_delay_ms'], ); } } diff --git a/src/Bridge/Symfony/Factory/ConsumptionPublisherFactory.php b/src/Bridge/Symfony/Factory/ConsumptionPublisherFactory.php index c8d6de6..2436ba0 100644 --- a/src/Bridge/Symfony/Factory/ConsumptionPublisherFactory.php +++ b/src/Bridge/Symfony/Factory/ConsumptionPublisherFactory.php @@ -16,7 +16,7 @@ final class ConsumptionPublisherFactory public static function create( AmApiClientInterface $amApiClient, ResourceSnapshotManager $snapshotManager, - array $config, + array $config ): ConsumptionPublisher { return new ConsumptionPublisher($amApiClient, $snapshotManager, (string) $config['source']); } diff --git a/src/Bridge/Symfony/Factory/OperationalStateProcessorFactory.php b/src/Bridge/Symfony/Factory/OperationalStateProcessorFactory.php index 78accca..2fc02ad 100644 --- a/src/Bridge/Symfony/Factory/OperationalStateProcessorFactory.php +++ b/src/Bridge/Symfony/Factory/OperationalStateProcessorFactory.php @@ -20,7 +20,7 @@ public static function create( OperationalStateReceiptStoreInterface $receiptStore, ?ResourceSnapshotManager $snapshotManager, ?OperationalStateReceiverInterface $receiver, - array $config, + array $config ): OperationalStateProcessor { $expectedInstance = $config['expected_instance_id'] ?? null; @@ -29,7 +29,7 @@ public static function create( $receiptStore, $snapshotManager, $receiver, - \is_string($expectedInstance) && '' !== $expectedInstance ? $expectedInstance : null + \is_string($expectedInstance) && '' !== $expectedInstance ? $expectedInstance : null, ); } } diff --git a/src/Core/Cli/ReceptacleBootstrapFactory.php b/src/Core/Cli/ReceptacleBootstrapFactory.php index c14cfb2..6d9bacd 100644 --- a/src/Core/Cli/ReceptacleBootstrapFactory.php +++ b/src/Core/Cli/ReceptacleBootstrapFactory.php @@ -40,17 +40,17 @@ public static function createKernel(array $config): ReceptacleHttpKernel new LoggingStopInstanceHandler($log), new LoggingStartInstanceHandler($log), new FileIdempotencyStore($dataDir.'/idempotency'), - new NoopAmApiClient() + new NoopAmApiClient(), ), new OperationalStateProcessor( new FileOperationalStateStore($dataDir.'/operational-state'), new FileOperationalStateReceiptStore($dataDir.'/operational-state-receipts'), - new ResourceSnapshotManager(new FileResourceSnapshotStore($dataDir.'/snapshots', $config['source'])) + new ResourceSnapshotManager(new FileResourceSnapshotStore($dataDir.'/snapshots', $config['source'])), ), $config['orchestration_path'], $config['operational_state_path'], $config['token_command'], - $config['token_state'] + $config['token_state'], ); } diff --git a/src/Core/Cli/ReceptacleHttpKernel.php b/src/Core/Cli/ReceptacleHttpKernel.php index b48fdd0..eadf819 100644 --- a/src/Core/Cli/ReceptacleHttpKernel.php +++ b/src/Core/Cli/ReceptacleHttpKernel.php @@ -40,7 +40,7 @@ public function __construct( string $orchestrationPath, string $operationalStatePath, string $orchestrationToken, - string $operationalStateToken, + string $operationalStateToken ) { $this->orchestrationProcessor = $orchestrationProcessor; $this->operationalStateProcessor = $operationalStateProcessor; diff --git a/src/Core/Cli/ReceptacleServerRouter.php b/src/Core/Cli/ReceptacleServerRouter.php index 127cd7d..72718ca 100644 --- a/src/Core/Cli/ReceptacleServerRouter.php +++ b/src/Core/Cli/ReceptacleServerRouter.php @@ -38,7 +38,7 @@ public static function dispatch(): void $_SERVER['REQUEST_METHOD'] ?? 'GET', $_SERVER['REQUEST_URI'] ?? '/', file_get_contents('php://input') ?: '', - $headers + $headers, ); http_response_code($status); diff --git a/src/Core/Dto/ConsumptionWebhookEvent.php b/src/Core/Dto/ConsumptionWebhookEvent.php index bb49543..472eba6 100644 --- a/src/Core/Dto/ConsumptionWebhookEvent.php +++ b/src/Core/Dto/ConsumptionWebhookEvent.php @@ -32,7 +32,7 @@ public function __construct( string $resourceKey, $value, string $occurredAt, - string $source, + string $source ) { $this->instanceId = $instanceId; $this->resourceKey = $resourceKey; @@ -61,7 +61,7 @@ public static function fromArray(array $data): self (string) $data['resourceKey'], $data['value'], (string) $data['occurredAt'], - (string) $data['source'] + (string) $data['source'], ); } diff --git a/src/Core/Dto/OrchestrationCallbackRequest.php b/src/Core/Dto/OrchestrationCallbackRequest.php index f7cbfb5..c396046 100644 --- a/src/Core/Dto/OrchestrationCallbackRequest.php +++ b/src/Core/Dto/OrchestrationCallbackRequest.php @@ -39,7 +39,7 @@ public static function fromArray(array $data): self return new self( (string) $data['idempotencyKey'], CallbackStatus::fromString((string) $data['status']), - $message + $message, ); } diff --git a/src/Core/Dto/OrchestrationCommand.php b/src/Core/Dto/OrchestrationCommand.php index 7499853..5c2105d 100644 --- a/src/Core/Dto/OrchestrationCommand.php +++ b/src/Core/Dto/OrchestrationCommand.php @@ -37,7 +37,7 @@ public function __construct( string $idempotencyKey, string $occurredAt, ?string $correlationId = null, - ?string $instanceIntegrationToken = null, + ?string $instanceIntegrationToken = null ) { $this->operation = $operation; $this->appId = $appId; diff --git a/src/Core/Http/AmApiClient.php b/src/Core/Http/AmApiClient.php index b0006de..0b1bd19 100644 --- a/src/Core/Http/AmApiClient.php +++ b/src/Core/Http/AmApiClient.php @@ -45,7 +45,7 @@ public function reportOrchestrationCallback(OrchestrationCallbackRequest $reques 'headers' => $this->callbackHeaders(), 'json' => $request->toArray(), 'timeout' => $this->config->timeoutSeconds(), - ] + ], ); } diff --git a/src/Core/Http/AmApiClientConfig.php b/src/Core/Http/AmApiClientConfig.php index d557f45..018fa9b 100644 --- a/src/Core/Http/AmApiClientConfig.php +++ b/src/Core/Http/AmApiClientConfig.php @@ -30,7 +30,7 @@ public function __construct( string $orchestrationCallbackToken, float $timeoutSeconds = 10.0, int $consumptionMaxRetries = 3, - int $consumptionRetryDelayMs = 500, + int $consumptionRetryDelayMs = 500 ) { $this->baseUrl = rtrim($baseUrl, '/'); $this->consumptionWebhookToken = $consumptionWebhookToken; diff --git a/src/Core/OperationalState/FileOperationalStateStore.php b/src/Core/OperationalState/FileOperationalStateStore.php index 65a35e3..6d827fa 100644 --- a/src/Core/OperationalState/FileOperationalStateStore.php +++ b/src/Core/OperationalState/FileOperationalStateStore.php @@ -24,7 +24,7 @@ public function save(string $instanceId, array $document): void { AtomicFileWriter::write( $this->pathFor($instanceId), - json_encode($document, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES) + json_encode($document, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES), ); } diff --git a/src/Core/OperationalState/OperationalStateProcessor.php b/src/Core/OperationalState/OperationalStateProcessor.php index 15ed594..90fa0b6 100644 --- a/src/Core/OperationalState/OperationalStateProcessor.php +++ b/src/Core/OperationalState/OperationalStateProcessor.php @@ -37,7 +37,7 @@ public function __construct( OperationalStateReceiptStoreInterface $receiptStore, ?ResourceSnapshotManager $snapshotManager = null, ?OperationalStateReceiverInterface $receiver = null, - ?string $expectedInstanceId = null, + ?string $expectedInstanceId = null ) { $this->store = $store; $this->receiptStore = $receiptStore; @@ -55,7 +55,7 @@ public function process(array $document): array { InstanceOperationalStateValidator::validate( $document, - $this->expectedInstanceId + $this->expectedInstanceId, ); $instanceId = (string) ($document['instance']['instanceId'] ?? ''); diff --git a/src/Core/Orchestration/OrchestrationCommandProcessor.php b/src/Core/Orchestration/OrchestrationCommandProcessor.php index f9bdcbe..301e520 100644 --- a/src/Core/Orchestration/OrchestrationCommandProcessor.php +++ b/src/Core/Orchestration/OrchestrationCommandProcessor.php @@ -37,7 +37,7 @@ public function __construct( StopInstanceHandlerInterface $stopHandler, StartInstanceHandlerInterface $startHandler, IdempotencyStoreInterface $idempotencyStore, - AmApiClientInterface $amApiClient, + AmApiClientInterface $amApiClient ) { $this->createHandler = $createHandler; $this->stopHandler = $stopHandler; @@ -94,7 +94,7 @@ public function process(OrchestrationCommand $command): array private function reportCallback(OrchestrationCommand $command, CallbackStatus $status, ?string $message): void { $this->amApiClient->reportOrchestrationCallback( - new OrchestrationCallbackRequest($command->idempotencyKey(), $status, $message) + new OrchestrationCallbackRequest($command->idempotencyKey(), $status, $message), ); } } diff --git a/src/Core/Snapshot/ConsumptionPublisher.php b/src/Core/Snapshot/ConsumptionPublisher.php index 5422de1..26173d4 100644 --- a/src/Core/Snapshot/ConsumptionPublisher.php +++ b/src/Core/Snapshot/ConsumptionPublisher.php @@ -25,7 +25,7 @@ final class ConsumptionPublisher public function __construct( AmApiClientInterface $amApiClient, ResourceSnapshotManager $snapshotManager, - string $source, + string $source ) { $this->amApiClient = $amApiClient; $this->snapshotManager = $snapshotManager; diff --git a/src/Core/Snapshot/FileResourceSnapshotStore.php b/src/Core/Snapshot/FileResourceSnapshotStore.php index 3e36040..3cde827 100644 --- a/src/Core/Snapshot/FileResourceSnapshotStore.php +++ b/src/Core/Snapshot/FileResourceSnapshotStore.php @@ -50,7 +50,7 @@ public function save(ManagedInstanceResourceSnapshot $snapshot): void { AtomicFileWriter::write( $this->pathFor($snapshot->instanceId()), - json_encode($snapshot->toArray(), JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES) + json_encode($snapshot->toArray(), JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES), ); } diff --git a/src/Core/Snapshot/ManagedInstanceResourceSnapshot.php b/src/Core/Snapshot/ManagedInstanceResourceSnapshot.php index dce41a4..008c856 100644 --- a/src/Core/Snapshot/ManagedInstanceResourceSnapshot.php +++ b/src/Core/Snapshot/ManagedInstanceResourceSnapshot.php @@ -38,7 +38,7 @@ public function __construct( string $source, string $updatedAt, array $resources, - ?array $lastInboundOperationalState = null, + ?array $lastInboundOperationalState = null ) { $this->instanceId = $instanceId; $this->source = $source; @@ -53,7 +53,7 @@ public static function empty(string $instanceId, string $source): self $instanceId, $source, (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DateTimeInterface::ATOM), - [] + [], ); } @@ -84,7 +84,7 @@ public static function fromArray(array $data): self (string) $data['source'], (string) $data['updatedAt'], $resources, - $last + $last, ); } @@ -140,7 +140,7 @@ public function withUpdatedAtNow(): self $this->source, (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DateTimeInterface::ATOM), $this->resources, - $this->lastInboundOperationalState + $this->lastInboundOperationalState, ); } @@ -154,7 +154,7 @@ public function withLastInboundOperationalState(array $lastInboundOperationalSta $this->source, (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DateTimeInterface::ATOM), $this->resources, - $lastInboundOperationalState + $lastInboundOperationalState, ); } @@ -190,7 +190,7 @@ public function withResourceMeasurement(string $resourceKey, $value, string $mea $this->source, (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DateTimeInterface::ATOM), $resources, - $this->lastInboundOperationalState + $this->lastInboundOperationalState, ); } @@ -218,7 +218,7 @@ public function withLastPushedToAm(string $resourceKey, $value, string $occurred $this->source, (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DateTimeInterface::ATOM), $resources, - $this->lastInboundOperationalState + $this->lastInboundOperationalState, ); } } diff --git a/src/Core/Validation/InstanceOperationalStateValidator.php b/src/Core/Validation/InstanceOperationalStateValidator.php index 4c433f8..746cd05 100644 --- a/src/Core/Validation/InstanceOperationalStateValidator.php +++ b/src/Core/Validation/InstanceOperationalStateValidator.php @@ -17,12 +17,12 @@ final class InstanceOperationalStateValidator */ public static function validate( array $document, - ?string $expectedInstanceId = null, + ?string $expectedInstanceId = null ): void { JsonPayloadValidator::requireKeys($document, ['schemaVersion', 'kind', 'instance']); JsonPayloadValidator::assertSchemaVersion( (string) $document['schemaVersion'], - OperationalStateProcessor::SCHEMA_VERSION + OperationalStateProcessor::SCHEMA_VERSION, ); if (OperationalStateProcessor::KIND !== (string) $document['kind']) { diff --git a/tests/Integration/ReceptacleHttpKernelTest.php b/tests/Integration/ReceptacleHttpKernelTest.php index 7007bbf..55ef1fb 100644 --- a/tests/Integration/ReceptacleHttpKernelTest.php +++ b/tests/Integration/ReceptacleHttpKernelTest.php @@ -73,17 +73,17 @@ private function kernel(string $dataDir, CommandCallLog $log): ReceptacleHttpKer new LoggingStopInstanceHandler($log), new LoggingStartInstanceHandler($log), new FileIdempotencyStore($dataDir.'/idempotency'), - new NoopAmApiClient() + new NoopAmApiClient(), ), new OperationalStateProcessor( new FileOperationalStateStore($dataDir.'/operational-state'), new FileOperationalStateReceiptStore($dataDir.'/operational-state-receipts'), - new ResourceSnapshotManager(new FileResourceSnapshotStore($dataDir.'/snapshots', 'captain-learning')) + new ResourceSnapshotManager(new FileResourceSnapshotStore($dataDir.'/snapshots', 'captain-learning')), ), ReceiverRoutePaths::orchestrationCommandsPath(ReceiverRoutePaths::DEFAULT_ROUTE_PREFIX), ReceiverRoutePaths::operationalStatePath(ReceiverRoutePaths::DEFAULT_ROUTE_PREFIX), 'dev-command-token', - 'dev-state-token' + 'dev-state-token', ); } } diff --git a/tests/Unit/Http/AmApiClientTest.php b/tests/Unit/Http/AmApiClientTest.php index 4a86c60..2c94204 100644 --- a/tests/Unit/Http/AmApiClientTest.php +++ b/tests/Unit/Http/AmApiClientTest.php @@ -26,7 +26,7 @@ public function testPushConsumptionUsesTokenHeader(): void 'seats', '12', '2026-05-14T12:00:00+00:00', - 'captain-learning' + 'captain-learning', )); // Assert @@ -45,7 +45,7 @@ public function testReportCallbackUsesCallbackToken(): void // Act $response = $api->reportOrchestrationCallback(new OrchestrationCallbackRequest( 'idem-key', - CallbackStatus::succeeded() + CallbackStatus::succeeded(), )); // Assert diff --git a/tests/Unit/OperationalState/OperationalStateProcessorTest.php b/tests/Unit/OperationalState/OperationalStateProcessorTest.php index 2b5f0f5..022ca6f 100644 --- a/tests/Unit/OperationalState/OperationalStateProcessorTest.php +++ b/tests/Unit/OperationalState/OperationalStateProcessorTest.php @@ -28,7 +28,7 @@ public function receive(array $document): void new FileOperationalStateStore($dir.'/state'), new FileOperationalStateReceiptStore($dir.'/receipts'), null, - $receiver + $receiver, ); $json = file_get_contents(dirname(__DIR__, 2).'/fixtures/instance-operational-state-am-minimal.json'); self::assertNotFalse($json);