feat(bucketing)!: anchored bucketing layout (contract v12) — PHP SDK - #50
Conversation
Copied verbatim from javascript-sdk origin/feat/anchored-bucketing-layout
(sha256 9993d0e6...). 59 vectors, versions {11,12}. Cross-SDK contract oracle.
Beads: ai-driven-product-dev-mq9v
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AC1-AC9 unit tests + golden-vector #[DataProvider] runner (59 vectors). RED: 12 v12-anchored golden failures + 7 AC failures; v11 packed passes. Beads: ai-driven-product-dev-51e7 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Anchored pass beside packed walk, gated on experience.version > 11 in DataManager fresh-bucketing branch (<=11/missing/non-numeric -> packed, bit-for-bit unchanged). New BucketingManager getBucketRanges/ selectBucketAnchored/getBucketForVisitorAnchored (+ interface). Shared hash/seed/value path unchanged. Ships inert. Golden 59/59 + AC 14/14 pass; full suite 1096 tests 0 failures; PHPStan level 6 clean. Ships inert (backend stamps version 11). Beads: ai-driven-product-dev-51e7 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… gate) Hoist repeated 3-arm variation configs into thirds()/tenEightyTen() private factories; removes the >=10-line duplicate blocks CPD would flag. Test-only: 14 AC tests + 59 golden vectors still GREEN, assertions unchanged. Beads: ai-driven-product-dev-8dsf Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request implements the anchored bucketing layout (contract v12) alongside the existing packed layout. It introduces new methods in BucketingManager and DataManager to calculate anchored ranges and select buckets, using a version gate (version > 11) to route traffic. Comprehensive unit and cross-SDK golden-vector tests are also added. The review feedback highlights a potential issue in DataManager::buildVariationAllocations() where negative traffic allocations are not safely handled, which could lead to negative weights and incorrect anchor calculations; clamping the parsed allocation to a minimum of 0.0 is recommended.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Introduces the anchored bucketing layout as a version-gated pass (experience.version > 11) beside the existing packed walk, and extends the public BucketingManagerInterface with getBucketRanges, selectBucketAnchored, and getBucketForVisitorAnchored. Cross-SDK parity with the JavaScript SDK reference is enforced by the shared golden-vector fixture. Ships inert: the backend currently stamps every experience version 11, so no served experience routes to anchored until the coordinated backend bump to 12. The packed path (version <= 11) is bit-for-bit unchanged. BREAKING CHANGE: bucketing contract v12 introduces the anchored layout and extends the public BucketingManagerInterface. Consumers implementing that interface must add the new methods. Runtime behavior is unchanged until the backend stamps experience.version > 11.
71f8fa2 to
cdafc96
Compare
…aManager Dedupes the running/non-zero-traffic predicate shared by the packed and anchored bucket builders into DataManager::isVariationActive() (addresses PR #50 review).
Summary
Adds the anchored bucketing layout (bucketing contract v12) to the PHP SDK as a parallel pass beside the existing packed walk, gated per-experience on
experience.version > 11. This is the PHP pass of a cross-SDK change; it is a structural port of the JS reference and consumes the shared cross-SDK golden-vector fixture as the parity oracle.version > 11→ anchored;version ≤ 11/ missing / non-numeric → packed (bit-for-bit unchanged).CURRENT_EXPERIENCE_VERSION = 11, so no served experience carriesversion > 11yet — zero behavioral change on ship. The backend bump to 12 is a separate, out-of-scope task (safe deploy order is SDK-first).versionfield the served config already carries.Why (the packed defect this fixes)
Under the packed layout, raising total allocation mid-flight relocates variation ranges — a customer-verified incident flipped visitors between arms (e.g. 15%→25% on 3 arms produced ~0/17/83 into the new band). The anchored layout pins each arm to a stable anchor so a raise is a superset (existing bucketings preserved) and a lower ejects evenly without flipping.
The anchored algorithm (normative; equality proven by golden vectors)
Build allocations over all variations in config order (inactive arms keep their weight for anchor stability but get zero width; explicit
ta: 0→ zero width, never 100; NaN/absentta→ 100.0 weight).totalWeight ≤ 0→ not bucketed. For each entry:anchor = (cumWeight/totalWeight) * maxTraffic,width = active ? allocation*100 : 0; select on the half-open interval[anchor, anchor+width). The shared value computation (hash, seed 9999, scaling) is reused unchanged from the packed path.Changes
packages/Bucketing/src/BucketingManager.php— newgetBucketRanges(),selectBucketAnchored(),getBucketForVisitorAnchored()(+ declared onBucketingManagerInterface).packages/Data/src/DataManager.php— packed bucket-builder extracted verbatim intobuildPackedBuckets()(unchanged output), newbuildVariationAllocations()for the anchored input shape, and theversion > 11gate wired into the fresh-bucketing branch.tests/CrossSdk/cross-sdk-bucketing-vectors.json— cross-SDK golden fixture, copied verbatim (sha256-identical) from the JS reference (59 vectors, versions {11, 12}). Consume-only oracle.tests/CrossSdk/AnchoredBucketingGoldenVectorTest.php—#[DataProvider]-driven golden runner over all 59 vectors, through the realDataManagerpath.packages/Data/tests/AnchoredBucketingLayoutTest.php— AC1–AC9 unit tests.Tests / checks
phpstan.neonrules).version ≤ 11(19 v11 regression vectors + all existing cross-SDK parity tests green).Acceptance criteria
AC1 (gate branching) · AC2 (raise is a superset) · AC3 (lower ejects evenly, never flips) · AC4 (stops don't move anchors; explicit
ta:0never 100) · AC5 (defaults & boundaries) · AC6 (packed regression lock) · AC7 (golden vectors) · AC8 (stored-decision guard wins) · AC9 (no event/API drift) — all covered and green.Notes for reviewers
DataManager::$_loggerManagerdeclared non-nullable while the constructor acceptsnull.ai-driven-product-dev_bmad-output/planning-artifacts/2026-03-13-convert-php-sdk/qs-01-anchored-bucketing-layout.md.🤖 Generated with Claude Code