From 0fc21f0b78f6475991312e3127aa870a7c93ef20 Mon Sep 17 00:00:00 2001 From: Renn F Date: Mon, 12 Oct 2026 17:00:36 +0200 Subject: [PATCH 1/3] release(3.2.3): User-Agent guard-agent-php, lockstep with the engines at 4.3.3 --- CHANGELOG.md | 12 ++++++++++++ bin/test_agent.php | 1 + src/Version.php | 6 +++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1873347..9919f75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Release Notes +v3.2.3 (2026-10-12) +------------------- + +User-Agent identifies the PHP agent (v3.2.3) +-------------------------------------------- + +### Changed + +- The ingestion `User-Agent` header is now `guard-agent-php/` (was `guard-agent/`, identical to the Python agent), so the Guard SaaS can tell the PHP agent apart from the Python one. Lockstep release with the agent family at 3.2.3. + +___ + v3.2.2 (2026-10-09) ------------------- diff --git a/bin/test_agent.php b/bin/test_agent.php index 3c437c2..c2feb73 100644 --- a/bin/test_agent.php +++ b/bin/test_agent.php @@ -1698,6 +1698,7 @@ function assertStaticOnlyClass(T $t, string $class): void $t->same('proj-smoke', $record['x_project_id'] ?? null, 'smoke: X-Project-Id header sent'); $t->same('install-smoke-1', $record['x_agent_install_id'] ?? null, 'smoke: X-Agent-Install-Id header sent'); $t->same(Version::USER_AGENT, $record['user_agent'] ?? null, 'smoke: User-Agent header sent'); +$t->same('guard-agent-php/' . Version::VERSION, $record['user_agent'] ?? null, 'smoke: User-Agent names the PHP port'); // The mock runs with requireSigned=true, so a signature computed over the gzip // bytes would have been rejected with 401; 200 proves the HMAC covers the diff --git a/src/Version.php b/src/Version.php index 5a3d7cb..b028d2f 100644 --- a/src/Version.php +++ b/src/Version.php @@ -11,8 +11,8 @@ */ final class Version { - public const VERSION = '3.2.2'; + public const VERSION = '3.2.3'; - /** User-Agent value sent with every request (mirrors guard-agent). */ - public const USER_AGENT = 'guard-agent/' . self::VERSION; + /** User-Agent value sent with every request (identifies the PHP port). */ + public const USER_AGENT = 'guard-agent-php/' . self::VERSION; } From 662a3e48dd813a029fd5258f1d9af2fa9a86c125 Mon Sep 17 00:00:00 2001 From: Renn F Date: Mon, 12 Oct 2026 19:08:36 +0200 Subject: [PATCH 2/3] fix(version): report the installed guard-core-php version as guard_core_version --- CHANGELOG.md | 1 + bin/test_agent.php | 6 ++++++ src/Config/AgentConfigResolver.php | 22 +++++++++++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9919f75..356714c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ User-Agent identifies the PHP agent (v3.2.3) ### Changed +- When `guardCoreVersion` is not set, the agent fills it from Composer's runtime API if `rennf93/guard-core-php` is installed (leading `v` stripped); otherwise it stays null. A value set by the caller wins. - The ingestion `User-Agent` header is now `guard-agent-php/` (was `guard-agent/`, identical to the Python agent), so the Guard SaaS can tell the PHP agent apart from the Python one. Lockstep release with the agent family at 3.2.3. ___ diff --git a/bin/test_agent.php b/bin/test_agent.php index c2feb73..3fee78b 100644 --- a/bin/test_agent.php +++ b/bin/test_agent.php @@ -1010,6 +1010,12 @@ function assertStaticOnlyClass(T $t, string $class): void // ===================================================================== // 2. Wire models // ===================================================================== +$t->section('guard core version'); +$t->same('4.3.3', AgentConfigResolver::bareVersion('v4.3.3'), 'guard core version: leading v stripped'); +$t->same(null, AgentConfigResolver::bareVersion(null), 'guard core version: null stays null'); +$t->same(null, AgentConfigResolver::resolve(['apiKey' => 'test-api-key-123'])->guardCoreVersion, 'guard core version: null when guard-core-php is not installed'); +$t->same('9.9.9', AgentConfigResolver::resolve(['apiKey' => 'test-api-key-123', 'guardCoreVersion' => '9.9.9'])->guardCoreVersion, 'guard core version: a user-set value wins'); + $t->section('models'); $event = makeEvent([ diff --git a/src/Config/AgentConfigResolver.php b/src/Config/AgentConfigResolver.php index bbbb110..7ea2597 100644 --- a/src/Config/AgentConfigResolver.php +++ b/src/Config/AgentConfigResolver.php @@ -23,6 +23,25 @@ private function __construct() { } + public static function installedGuardCoreVersion(): ?string + { + if (!class_exists(\Composer\InstalledVersions::class) + || !\Composer\InstalledVersions::isInstalled('rennf93/guard-core-php')) { + return null; + } + + return self::bareVersion(\Composer\InstalledVersions::getPrettyVersion('rennf93/guard-core-php')); + } + + public static function bareVersion(?string $prettyVersion): ?string + { + if ($prettyVersion === null || $prettyVersion === '') { + return null; + } + + return ltrim($prettyVersion, 'vV'); + } + /** * @param array $input * @@ -83,7 +102,8 @@ public static function resolve(array $input): AgentConfig ?? AgentConfig::DEFAULT_SENSITIVE_HEADERS, maxPayloadSize: self::intInput($input, 'maxPayloadSize', 'max_payload_size', 1024), guardVersion: self::optionalStringInput($input, 'guardVersion', 'guard_version'), - guardCoreVersion: self::optionalStringInput($input, 'guardCoreVersion', 'guard_core_version'), + guardCoreVersion: self::optionalStringInput($input, 'guardCoreVersion', 'guard_core_version') + ?? self::installedGuardCoreVersion(), compressionEnabled: self::boolInput($input, 'compressionEnabled', 'compression_enabled', true), compressionThreshold: self::intInput($input, 'compressionThreshold', 'compression_threshold', 1024), installId: self::optionalStringInput($input, 'installId', 'install_id'), From a65710edaf2f6c957c8f6704b676991d868469e7 Mon Sep 17 00:00:00 2001 From: Renn F Date: Mon, 12 Oct 2026 19:37:10 +0200 Subject: [PATCH 3/3] test(version): cover the installed-package resolution path --- bin/test_agent.php | 2 ++ src/Config/AgentConfigResolver.php | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bin/test_agent.php b/bin/test_agent.php index 3fee78b..bd704de 100644 --- a/bin/test_agent.php +++ b/bin/test_agent.php @@ -1012,6 +1012,8 @@ function assertStaticOnlyClass(T $t, string $class): void // ===================================================================== $t->section('guard core version'); $t->same('4.3.3', AgentConfigResolver::bareVersion('v4.3.3'), 'guard core version: leading v stripped'); +$rootPackage = \Composer\InstalledVersions::getRootPackage()['name']; +$t->same(ltrim(\Composer\InstalledVersions::getPrettyVersion($rootPackage) ?? '', 'vV'), AgentConfigResolver::installedGuardCoreVersion($rootPackage), 'guard core version: an installed package resolves from Composer'); $t->same(null, AgentConfigResolver::bareVersion(null), 'guard core version: null stays null'); $t->same(null, AgentConfigResolver::resolve(['apiKey' => 'test-api-key-123'])->guardCoreVersion, 'guard core version: null when guard-core-php is not installed'); $t->same('9.9.9', AgentConfigResolver::resolve(['apiKey' => 'test-api-key-123', 'guardCoreVersion' => '9.9.9'])->guardCoreVersion, 'guard core version: a user-set value wins'); diff --git a/src/Config/AgentConfigResolver.php b/src/Config/AgentConfigResolver.php index 7ea2597..99fe79b 100644 --- a/src/Config/AgentConfigResolver.php +++ b/src/Config/AgentConfigResolver.php @@ -23,14 +23,14 @@ private function __construct() { } - public static function installedGuardCoreVersion(): ?string + public static function installedGuardCoreVersion(string $package = 'rennf93/guard-core-php'): ?string { if (!class_exists(\Composer\InstalledVersions::class) - || !\Composer\InstalledVersions::isInstalled('rennf93/guard-core-php')) { + || !\Composer\InstalledVersions::isInstalled($package)) { return null; } - return self::bareVersion(\Composer\InstalledVersions::getPrettyVersion('rennf93/guard-core-php')); + return self::bareVersion(\Composer\InstalledVersions::getPrettyVersion($package)); } public static function bareVersion(?string $prettyVersion): ?string