Skip to content

feat(bucketing)!: anchored bucketing layout (contract v12) — PHP SDK - #50

Merged
abbaseya merged 6 commits into
feat/fullstack-v12from
feat/anchored-bucketing-layout
Jul 7, 2026
Merged

feat(bucketing)!: anchored bucketing layout (contract v12) — PHP SDK#50
abbaseya merged 6 commits into
feat/fullstack-v12from
feat/anchored-bucketing-layout

Conversation

@abbaseya

@abbaseya abbaseya commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

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.

  • Gate: version > 11 → anchored; version ≤ 11 / missing / non-numeric → packed (bit-for-bit unchanged).
  • Ships INERT: the backend stamps CURRENT_EXPERIENCE_VERSION = 11, so no served experience carries version > 11 yet — zero behavioral change on ship. The backend bump to 12 is a separate, out-of-scope task (safe deploy order is SDK-first).
  • No API/OpenAPI/event/public-return changes — this is an SDK-internal algorithm change on a version field 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/absent ta → 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 — new getBucketRanges(), selectBucketAnchored(), getBucketForVisitorAnchored() (+ declared on BucketingManagerInterface).
  • packages/Data/src/DataManager.php — packed bucket-builder extracted verbatim into buildPackedBuckets() (unchanged output), new buildVariationAllocations() for the anchored input shape, and the version > 11 gate 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 real DataManager path.
  • packages/Data/tests/AnchoredBucketingLayoutTest.php — AC1–AC9 unit tests.

Tests / checks

  • Golden vectors 59/59 + AC 14/14 pass; full existing suite 1096 tests, 4167 assertions, 0 failures (34 pre-existing unrelated skips).
  • PHPStan level 6 clean (no suppressions added; global phpstan.neon rules).
  • Packed path bit-for-bit unchanged for 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:0 never 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

  • Cross-SDK consistency is enforced solely by the shared golden fixture — there is no cross-SDK build-order or merge dependency; this PR is independent of the JS reference and the other sibling SDK passes.
  • One pre-existing, out-of-scope defect was discovered and ticketed separately (not fixed here): DataManager::$_loggerManager declared non-nullable while the constructor accepts null.
  • Spec of record: ai-driven-product-dev _bmad-output/planning-artifacts/2026-03-13-convert-php-sdk/qs-01-anchored-bucketing-layout.md.

🤖 Generated with Claude Code

abbaseya and others added 4 commits July 3, 2026 22:44
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>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/Data/src/DataManager.php
@abbaseya abbaseya self-assigned this Jul 3, 2026
@abbaseya
abbaseya requested a review from DmytroConvert July 3, 2026 21:31
@abbaseya abbaseya changed the title feat(bucketing): anchored bucketing layout (contract v12) — PHP SDK feat(bucketing)!: anchored bucketing layout (contract v12) — PHP SDK Jul 3, 2026
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.
@abbaseya
abbaseya force-pushed the feat/anchored-bucketing-layout branch from 71f8fa2 to cdafc96 Compare July 3, 2026 21:55
Comment thread packages/Data/src/DataManager.php Outdated
…aManager

Dedupes the running/non-zero-traffic predicate shared by the packed and
anchored bucket builders into DataManager::isVariationActive() (addresses
PR #50 review).
@abbaseya
abbaseya requested a review from DmytroConvert July 3, 2026 22:27
@abbaseya
abbaseya changed the base branch from main to feat/fullstack-v12 July 4, 2026 15:03
@abbaseya
abbaseya merged commit 145f7bb into feat/fullstack-v12 Jul 7, 2026
11 checks passed
@abbaseya
abbaseya deleted the feat/anchored-bucketing-layout branch July 15, 2026 23:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants