diff --git a/src/Client.php b/src/Client.php index 27f55b0e..6d62259b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -232,9 +232,9 @@ public function describeServiceCatalogCall(string $callId): array { $response = $this->serviceCatalogRead('/service-catalog/calls', id: $callId); if (($response['payloads_in_observer_namespace'] ?? null) === true) { - $response['output_available'] = is_array($response['output_envelope'] ?? null); + $response['output_available'] = is_array($response['result_envelope'] ?? null); $response['output'] = $response['output_available'] - ? $this->decodedResponsePayload($response, 'output') + ? $this->decodedResponsePayload($response, 'result') : null; } diff --git a/src/Transport/RuntimePayloads.php b/src/Transport/RuntimePayloads.php index 249e6b31..6778acb3 100644 --- a/src/Transport/RuntimePayloads.php +++ b/src/Transport/RuntimePayloads.php @@ -40,14 +40,19 @@ public function response(array $response, string $requestPath, bool $worker): ar $paths = [...$paths, ...$this->historyPaths(['task', 'history_events']), ...$this->historyPaths(['history_events']), ...$this->exportPaths(['task', 'history_export'])]; } else { - if (preg_match('~\A/service-catalog/calls/[^/]+\z~', $path) - && ($response['payloads_in_observer_namespace'] ?? null) === true + $catalogCall = preg_match('~\A/service-catalog/calls/[^/]+\z~', $path) === 1; + $catalogOutputAllowed = ($response['payloads_in_observer_namespace'] ?? null) === true; + if ($catalogCall && $catalogOutputAllowed + && !is_array($response['result_envelope'] ?? null) && is_array($response['output_payload_reference'] ?? null)) { // Keep the opaque reference while hydrating only the authorized // terminal output of this exact call, never arbitrary metadata. - $response['output_envelope'] = ['codec' => 'avro', 'external_payload' => $response['output_payload_reference']]; + $response['result_envelope'] = ['codec' => 'avro', 'external_payload' => $response['output_payload_reference']]; } foreach (['input_envelope', 'output_envelope', 'result_envelope'] as $field) { + if ($catalogCall && !$catalogOutputAllowed) { + continue; + } $paths[] = [$field]; } if (preg_match('~\A/activities/[^/]+\z~', $path)) { diff --git a/tests/ServiceCatalogReadClientTest.php b/tests/ServiceCatalogReadClientTest.php index f2242e6c..0c7e4c66 100644 --- a/tests/ServiceCatalogReadClientTest.php +++ b/tests/ServiceCatalogReadClientTest.php @@ -159,6 +159,21 @@ public static function outputs(): array return [[null], [false], [0], [''], [['result', ['nested' => 'value']]]]; } + #[DataProvider('outputs')] + public function testCommittedInlineResultEnvelopeIsDecoded(mixed $value): void + { + $codec = new AvroPayloadCodec(); + $envelope = $codec->envelope($value); + $transport = new FakeTransport([$this->discovery(), [ + 'payloads_in_observer_namespace' => true, 'output_payload_reference' => null, + 'result_envelope' => $envelope, + ]]); + $result = (new Client('https://runtime.test', transport: $transport))->describeServiceCatalogCall('call-1'); + self::assertTrue($result['output_available']); + self::assertSame($codec->encode($value), $codec->encode($result['output'])); + self::assertCount(2, $transport->requests); + } + public function testExternalOutputUsesOnlyTheConfiguredRuntimeNamespaceAndOperatorCredential(): void { $codec = new AvroPayloadCodec(); @@ -177,6 +192,7 @@ public function testExternalOutputUsesOnlyTheConfiguredRuntimeNamespaceAndOperat if (count($requests) === 2) { return Create::promiseFor(new Response(200, [], json_encode([ 'id' => 'call-1', 'payloads_in_observer_namespace' => true, 'output_payload_reference' => $envelope, + 'result_envelope' => ['codec' => 'avro', 'external_payload' => $envelope], ], JSON_THROW_ON_ERROR))); } @@ -206,6 +222,7 @@ public function testForeignOrUnverifiedOutputsAreNotFetchedAndOwnedDownloadsHono $transport = new FakeTransport([$this->discovery(), [ 'payloads_in_observer_namespace' => $authorized, 'output_payload_reference' => $reference, + 'result_envelope' => ['codec' => 'avro', 'external_payload' => $reference], ]]); $client = new Client('https://runtime.test', transport: $transport, maxExternalPayloadBytes: 1); try {