Skip to content

feat: Full Stack v12 — safe traffic ramp-up, variation preview & experiment exclusion - #418

Merged
abbaseya merged 33 commits into
mainfrom
feat/fullstack-v12
Jul 24, 2026
Merged

feat: Full Stack v12 — safe traffic ramp-up, variation preview & experiment exclusion#418
abbaseya merged 33 commits into
mainfrom
feat/fullstack-v12

Conversation

@abbaseya

Copy link
Copy Markdown
Collaborator

Full Stack v12 — next major SDK release

This branch delivers three major capabilities, shipped consistently across all Full Stack SDKs. All three are inert until activated on the backend/UI side, so this deploys safely in any order.

1. Anchored (ramping) bucketing layout

Makes changing an experiment's total traffic allocation safe on a running experiment.

  • Each variation is pinned at its own fixed offset (cumulative-weight anchor); its range width equals its allocation. Raising the total grows every arm at its own edge (even growth); lowering ejects evenly; nobody already bucketed gets reshuffled.
  • Gated on the experience's existing serving version property: > 11 → anchored pass (contract v12); ≤ 11 / missing → packed pass, bit-identical to today. No schema change, zero migration.
  • Hash, seed, stored-decision guard, forced path, and events are untouched. Still stateless, deterministic, and identical across every SDK — enforced by shared cross-SDK golden vectors.
  • Changing variation weights mid-flight stays unsupported (unsafe in every layout); the split lock is unchanged.

2. Experiment preview

Lets users preview any variation — including drafts and paused ones — in their own environment, with zero trace.

  • New debugToken config option (24h QA token) unlocks the full config, including draft/paused experiences.
  • Stateless preview links (web) and deep links (mobile) via convert_preview={expId}.{varId} force a single variation with no side effects: no tracking events, no visitor-state writes, no impact on reporting.
  • Rides the existing public serving primitive — no backend serving changes required.

3. Mutual exclusion / inclusion audience rule

Lets teams control which experiments a visitor can be in at the same time, through the normal audience-targeting rules.

  • New audience rule bucketed_into_experience_key, resolved against SDK-stored bucketing state.
  • negated: true → "NOT in experiment X" (mutual exclusion); default → "in experiment X" (inclusion).
  • No new application inputs; evaluates before bucketing and composes identically with both packed and anchored layouts. Old SDKs fail closed.

Notes

  • Backend registration of the new rule type and the version 12 stamp, plus the related convert-ui work (preview link/QR button, rule picker) and shared docs, are tracked and coordinated separately.
  • Full existing test suites remain green; the anchored pass adds the shared golden-vector acceptance tests.

abbaseya and others added 29 commits July 3, 2026 20:39
…face signatures

Beads: ai-driven-product-dev-p5at
Agent: sdk-js
Foundation only — no algorithm/behavior change. Build+lint green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Beads: ai-driven-product-dev-n7x9
18 Mocha/Chai tests, spec-derived expected numbers as independent oracle.
AC2/AC4/AC5 coverage. Fail cleanly (methods not implemented yet).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Beads: ai-driven-product-dev-n7x9
getBucketRanges/selectBucketAnchored/getBucketForVisitorAnchored per normative
spec. Packed path frozen. 28/28 bucketing tests pass (18 anchored + 10 packed),
TS2420 self-resolved on default yarn test:mocha.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Beads: ai-driven-product-dev-pm33
9 Mocha/Chai tests: AC1 gate branching, AC4 stops/ta:0, AC8 guard precedence,
AC9 no schema drift. 4 fail (gate not implemented), 5 pass (packed-path + invariant locks).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Beads: ai-driven-product-dev-pm33
Number(experience.version) > 8 -> anchored (via _buildVariationAllocations +
getBucketForVisitorAnchored); else packed unchanged (_buildPackedBuckets extracted
verbatim). Guard/hash/seed/event payloads untouched. 37/37 data suite pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + runner

Beads: ai-driven-product-dev-jnz9
59 vectors (all spec categories: incident-flip, raise-superset, per-sliver,
lower-eject, stopped-arm, ta:0, NaN, boundary, v8===v9 coincidence). Expected
values computed then frozen; every spec-given band matched. Runner 60 passing.
Full monorepo suite green (480 Mocha + 36 Playwright). Cross-SDK contract for the
5 sibling SDKs; fixture at packages/bucketing/tests/cross-sdk-bucketing-vectors.json.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d test files

Beads: ai-driven-product-dev-kz6x
Review R1 (conf 95): header comments claimed methods/gate not-yet-implemented (RED);
now shipped + passing. Rewrote to describe shipped behavior for sibling-SDK porters;
fixed stale data-manager.ts:620-637 -> :685 gate ref. Comment-only. Suites unchanged
(bucketing 28, data 97).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…SonarCloud S2871

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ent stamp is 11) + browser golden-vector test

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…erience version

Introduces an anchored bucketing layout chosen per experience by experience.version: experiences at version > 11 bucket via the anchored layout, while version <= 11, missing, or non-numeric keep the existing packed walk unchanged. Anchored ranges stay fixed to each variation's position, so raising traffic allocation grows every arm in place instead of relocating ranges and re-bucketing already-assigned visitors. Adds public API to @convertcom/js-sdk-bucketing (getBucketRanges, selectBucketAnchored, getBucketForVisitorAnchored) and a VariationAllocation type in @convertcom/js-sdk-types. Hash, seed, stored-decision guard, forced-variation path, and event payloads are unchanged.

BREAKING CHANGE: bucketing layout is now selected by experience.version; experiences at version > 11 use the new anchored layout instead of the packed cumulative walk.
Pure exported helper parsePreviewParam(value) -> {experienceId,variationId}|null,
dot-separated numeric ids, exported from js-sdk public entry. 12-row case-table test.

Beads: ai-driven-product-dev-hooy

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ygiene (AC1,AC2)

Add debugToken?:string to ConfigBase; ApiManager appends debug_token=<value> and forces
_conv_low_cache=1 to the config-fetch URL when set (proper &-joined query). Token never
sent to track endpoint nor logged. Config-matrix + hygiene tests (6) + api suite (16) green.

Beads: ai-driven-product-dev-m568

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…emo (AC3-fetch,AC8)

ApiManager.getConfigByExperience(id) fetches /config/<sdkKey>?exp=<id>&_conv_low_cache=1
(+debug_token when configured), memoized process-wide in a module-level Map keyed
sdkKey:experienceId, 60s TTL (live Date.now), in-flight promise cached to collapse
concurrent calls to one fetch. 7 tests + 23 api suite green.

Beads: ai-driven-product-dev-e0aw

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ger (AC4,AC7)

Pure DataManager.getPreviewDecision(experience, variationId) resolves the variation
directly from the passed experience.variations (not shared config), returns a
BucketedVariation-shaped decision bypassing rules/env/status/traffic/stored/hash, with
no putData and no enqueue. Unknown variation -> null. 7 tests + 104 data suite green.

Beads: ai-driven-product-dev-rr3l

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…isolation (AC3,AC5,AC6,AC7)

Add enableStorage flag (gates putData in DataManager fresh-bucketing branch). Context.setPreview
resolves via shared config or getConfigByExperience, validates variation, stores per-context
_preview. Preview context: runExperience forces getPreviewDecision for the target key, all run*
run with enableTracking/enableStorage false + BUCKETING fires suppressed, trackConversion
short-circuits. Zero /track (fetch+beacon) and zero store writes; concurrent non-preview context
unaffected. Fixed latent getData() {}-vs-null bug. Node 4 preview tests + 159 js-sdk suite +
Playwright browser gate (zero-trace on real transport) all green.

Beads: ai-driven-product-dev-t5ir

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dit R1 fix)

Thread enableStorage through matchRulesByField -> selectLocations and gate its putData
(data-manager.ts) so a preview context running a location-targeted non-target experience
writes nothing. Guard Context runCustomSegments/setDefaultSegments/updateVisitorProperties
on _preview (no visitor-state persistence on a preview context). Fix JSDoc @returns parse
error in parse-preview-param.ts. 4 new storage tests + browser store-write spy green; js-sdk
163 + data 104 + segments 7 + experience 12 suites green.

Beads: ai-driven-product-dev-y5gj

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…review (decision-audit R2)

selectLocations obtained the visitor's stored locations array by reference from getData
(live _bucketedVisitors on the memory path) and push/spliced it in place BEFORE the
enableStorage-gated putData -- mutating shared visitor state on a preview context. Now
operates on a copy when enableStorage is false (zero change to the normal persist path).
Regression test (seed locations, run under preview, assert array unchanged) + 9 preview
tests + 164 js-sdk + 104 data (incl golden-vector) + browser gate all green.

Beads: ai-driven-product-dev-ehrh

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…stale RED comments

getConfigByExperience now evicts its cache entry on fetch rejection (guarded against
clobbering a newer entry) so a transient failure no longer blocks preview retry for 60s;
regression test added. Removed stale RED-phase comments + now-false 'as any' casts from
committed preview tests (methods exist on impl+interface). 8 config + 24 api + 104 data +
9 context-preview + 164 js-sdk + browser gate 39 all green.

Beads: ai-driven-product-dev-4qqt

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pired preview-config cache entries

Adds localeCompare comparators to the two bare string .sort() calls flagged
by SonarCloud rule S2871 in api-manager-config-by-experience.tests.ts, and
sweeps expired entries from the process-wide configByExperienceCache map on
every getConfigByExperience() call so resolved-but-expired entries for
distinct experienceIds don't accumulate unbounded in long-running servers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(bucketing): anchored layout (contract v12) — JS SDK reference impl
Beads: ai-driven-product-dev-xypk
Agent: sdk-js
Files: 1 changed

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nRuleType literal

Beads: ai-driven-product-dev-rv02
Agent: sdk-js
Files: 3 changed

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…owser gate

Beads: ai-driven-product-dev-hpul
Agent: sdk-js
RED: data-unit 4 pass / 5 fail (seam missing); integration 2 pass / 4 fail
Files: 3 test files

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ted_into_experience_key seam

Beads: ai-driven-product-dev-hpul
Agent: sdk-js
GREEN: data-unit 9/9, full data pkg 90/90, integration 5/6 (AC3 = test-timing bug, fix next)
RuleManager untouched (AC7). package.json build-churn deliberately excluded.
Files: packages/data/src/data-manager.ts

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cross-instance read

Beads: ai-driven-product-dev-hpul
Agent: sdk-js
Integration now 6/6. Test-timing bug (async DataStore flush), seam unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bit-identical

Beads: ai-driven-product-dev-uosk
Agent: sdk-js
Rules suite 47/47 (38 existing + 9 new). All 8 rows assert RuleManager.isRuleMatched({})=false (Compatibility contract). RuleManager src untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
qs-02: Experiment preview (debug token + preview links)
feat(rules): mutual-exclusion audience rule (bucketed_into_experience_key) — qs-03
@abbaseya abbaseya self-assigned this Jul 16, 2026
@abbaseya
abbaseya requested a review from JosephSamirL July 16, 2026 16:27

@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 introduces several major features, including an anchored bucketing layout to prevent visitor reshuffling when traffic allocations are raised, a zero-trace preview mode for forcing specific experience variations, and a mutual-exclusion audience rule (bucketed_into_experience_key). It also adds support for a debugToken to force low-cache config fetches. The code review identifies critical issues that need to be addressed: rule composition is broken within audiences containing the mutual-exclusion rule because it bypasses the rule manager; the preview override is missing in runExperiences (plural); feature previewing is broken because the preview state is not propagated to FeatureManager; and _resolveBucketingExclusion lacks defensive handling for a missing visitorId.

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/data-manager.ts
Comment thread packages/js-sdk/src/context.ts Outdated
Comment thread packages/js-sdk/src/context.ts
Comment thread packages/data/src/data-manager.ts
@abbaseya abbaseya changed the title Full Stack v12: safe traffic ramp-up, variation preview & experiment exclusion feat: Full Stack v12 — safe traffic ramp-up, variation preview & experiment exclusion Jul 16, 2026
abbaseya and others added 2 commits July 18, 2026 22:26
…ument how to test

Reads the ?convert_preview={experienceId}.{variationId} param at each demo's
context-creation seam via the already-public parsePreviewParam helper, and
awaits context.setPreview() before the experience renders. Adds a "Testing
preview links" section to each demo README with a concrete example URL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arity)

runExperiences() now returns the forced preview decision for the previewed experience (replace-in-place, else append), matching runExperience() and the PHP/Android/Python/iOS SDKs; other experiences still decide normally and zero-trace holds. Extends the debug-token AC2 hygiene test to the preview ?exp= fetch seam, and corrects a stale test comment that claimed the (now passing) zero-trace storage regression locks must fail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L42GvyvKc4t8AXG7nnhq3f
@JosephSamirL

Copy link
Copy Markdown
Contributor

Cross-SDK release review — javascript-sdk #418

Reviewed at head a19840d1, alongside ios-sdk#45, ruby-sdk#43, android-sdk#56. Every claim below was read against the PR head; cross-repo claims were read on both sides.

Context: this SDK is the reference implementation, so several items below aren't "JS bugs" so much as decisions the other three either didn't inherit or inherited differently. Those are marked [parity] and need one owner deciding which side is normative.

1. Preview is not zero-trace — location lifecycle events still fire

selectLocations() fires SystemEvents.LOCATION_ACTIVATED (packages/data/src/data-manager.ts:986-998) and LOCATION_DEACTIVATED (:1010-1022) with no preview gate, while every BUCKETING fire added in this PR is wrapped in if (!this._preview) (packages/js-sdk/src/context.ts:260, :320, :410, :431, :491).

Both location fires pass true as the 4th argument to EventManager.fire, which packages/event/src/event-manager.ts documents as "Allows to fire listeners which were subscribed after event is fired" — so a listener registered after the preview request still receives them. A QA preview session emits into production analytics.

Ruby, iOS, and Android never fire location lifecycle events at all, so this is JS-only.

2. Anchored anchors and bucket values live in different coordinate spaces

packages/bucketing/src/bucketing-manager.ts:

  • :102 — hash is projected onto this._max_traffic (config?.bucketing?.max_traffic || 10000, set at :48)
  • :167 — anchors are computed against the fixed DEFAULT_MAX_TRAFFIC (10000), and widths are allocation * 100 at :168

Any project that sets the public bucketing.max_traffic knob below 10000 gets hash values in [0, max_traffic) matched against anchors spread over [0, 10000), so every arm anchored at or above max_traffic becomes unreachable — total starvation of later arms, not just compression. All four SDKs copy the same fixed constant, so it reproduces everywhere.

3. Preview config cache key omits the fields that change the URL

packages/api/src/api-manager.ts:348 keys the process-wide memo as `${this._sdkKey}:${experienceId}`, but _environment (:354) and _debugToken (:357) both change the request URL.

Two SDK instances sharing an sdkKey but differing in debug token or environment therefore share each other's cached responses for the TTL. Concretely: a tokenless setPreview populates the memo, and a QA preview through the debug-token instance is served that tokenless payload — the draft experience is absent, setPreview warns PREVIEW_EXPERIENCE_NOT_FOUND, and the shareable link silently renders the original variation.

ruby-sdk#43 (lib/convert_sdk/api_manager.rb:298) and android-sdk#56 (ApiManager.kt:1070) have the identical key shape.

4. Query parameters are interpolated without encoding

api-manager.ts contains zero encodeURIComponent calls. debug_token (:321, :357), environment (:318, :354) and exp (:355) are interpolated raw.

A + in a base64-ish token becomes a space server-side; &/= split or inject params; # truncates the query. iOS uses URLQueryItem and Ruby uses URI.encode_www_form_component (api_manager.rb:447), so the same token works there and fails here — which will read as a platform-specific mystery. Android has the same gap (ApiManager.kt:1253/1257/1261).

5. [parity] Exclusion audiences resolve from the leaf alone, ignoring siblings

_isBucketingExclusionRule (data-manager.ts:1246-1265) walks the entire OR→AND→OR_WHEN tree and returns the first bucketed_into_experience_key leaf found anywhere; filterMatchedRecordsWithRule:1336-1345 then resolves the whole audience from that leaf and never calls isRuleMatched.

iOS matches this deliberately (BucketingExclusion.swift header: "sibling leaves in the same tree are never evaluated by any engine"). Ruby and Android do not — they dispatch the leaf inline in the normal walk (rule_manager.rb:145; RuleManager.kt:299-302), so sibling conditions still apply.

For an audience like country = US AND NOT bucketed-into-B, JS/iOS ignore the country condition and Ruby/Android enforce it. Same experiment, structurally different populations per platform.

6. [parity] Explicit-null traffic_allocation — JS is the outlier

_buildVariationAllocations:629: isNaN(trafficAllocation) ? 100.0 : Number(trafficAllocation). For an explicit JSON null, isNaN(null) === false and Number(null) === 0, so the arm gets weight 0 / active: false (:634-636). The packed filter at :598-602 excludes it outright.

iOS (BucketingManager.swift:96-101), Android (BucketingLayoutResolver.buildVariationAllocations) and Ruby (bucketing_manager.rb:254-257) all map null to 100.0 / active. Since anchored weights set every downstream anchor, one null shifts assignments cross-platform.

7. [parity] Naive fold vs Ruby's compensated summation

getBucketRanges totals with reduce (:153-156); Swift reduce(0.0) and Kotlin sumOf match. Ruby uses entries.sum (bucketing_manager.rb:175), which for Floats is Kahan-Babuska compensated.

Reproduced locally with [17.54, 74.13, 6.27, 2.06]: Ruby yields exactly 100.0, the naive fold 99.99999999999999. Both sides use identical value >= anchor && value < anchor + width semantics, and arm 1's anchor is 1754.0 on Ruby vs 1754.0000000000002 here — bucket value 1754 lands in the 74.13% arm on Ruby and in no arm on JS/iOS/Android.

Magnitude, measured rather than asserted: across 2 000 random 2-decimal allocation sets summing to 100, 162 had at least one of the 10 000 bucket values assign differently (322 flips total) — about 1–2 visitors per 10 000 in an affected config. Small per-visitor, but it means the four SDKs are not bit-identical on the boundary, and fractional allocations summing to 100 are exactly what mid-flight ramp-up produces.

8. Minor — setPreview's first guard doesn't clear state

context.ts:145-152 returns on missing experienceId/variationId without this._preview = null, while the other three failure paths (:172, :182, :195) all clear it. Inconsistent with this file's own contract. (iOS and Ruby never clear on failure at all — see those PRs.)

Test-gap note

All four CI suites are green, but the shared cross-sdk-bucketing-vectors.json contains none of the inputs that expose the divergences above: a v11 experience with a stopped arm at nonzero allocation, fractional allocations summing to 100, an explicit-null traffic_allocation, a string-typed version, or a mixed exclusion audience. Adding those five vectors would have caught items 5–7 plus the iOS packed-path bug pre-review.

Automated cross-repo review; every cited line read at the PR head SHA.

…rams

Cross-SDK release-review parity fixes (JS oracle): URL-encode environment/exp/debug_token in both config-fetch builders; suppress LOCATION_ACTIVATED/DEACTIVATED events while a preview is active via a preview-scoped suppressEvents flag (location matching still runs; normal enableTracking:false runs still fire events).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@abbaseya

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough cross-repo pass, Joseph — genuinely useful. I went through every item against the code and the qs specs. JS dispositions below; fixes pushed in 0cf6a10.

Fixed (0cf6a10)

  • chore(main): release js-sdk 1.0.0 #1 Preview not zero-trace (location events). selectLocations()'s LOCATION_ACTIVATED/LOCATION_DEACTIVATED fires are now gated by a new preview-scoped suppressEvents flag threaded context → getBucketing → matchRulesByField → selectLocations. Location matching still runs, and a normal enableTracking:false run still fires them (suppression is preview-scoped, not tracking-scoped) — mirroring the existing if (!this._preview) BUCKETING gate.
  • docs: linking javascript documentation #4 Query params not URL-encoded. environment, exp, debug_token are now wrapped in encodeURIComponent in both config-fetch builders.

Fixed in the sibling repo

  • pre-release-v1.3 #7 [parity] naive fold vs Ruby's compensated sum. JS's naive reduce is the reference; Ruby was the outlier and is now aligned (ruby-sdk 830f80a).

Intentionally not changed (reasons)

  • build(deps): Bump pug from 2.0.0-beta11 to 3.0.1 in /packages/demo-nodejs #2 anchored vs hash coordinate space (max_traffic). The normative qs-01 algorithm scales anchors by a fixed 10000, and bucketing.max_traffic is never served ≠ 10000, so there's no live mismatch — JS is spec-compliant. (PHP happens to parametrize it; harmless, not required.) Flagged for the spec only if max_traffic ever becomes a real knob.
  • build: upgrade dependencies #3 preview memo key omits environment/debug_token. qs-02 specifies "memoize per experienceId", which all affected SDKs implement identically — so it's spec-compliant and cross-SDK consistent, not a divergence. The collision needs two SDK instances in one process sharing an sdkKey but differing in token/environment (a real but low-probability QA setup). Left as-is pending a spec tweak.
  • chore(main): release js-sdk 1.2.0 #5 [parity] exclusion resolves from the leaf alone. Diverges only on a mixed audience (exclusion leaf + generic sibling in one tree), which is out-of-contract — mutual exclusion is modelled as a dedicated audience and no served config emits a mixed tree. On the supported shape all SDKs agree (golden fixture passes). We'll fix the reading in the spec if the backend ever emits mixed trees.
  • extract-reusable-packages #6 [parity] explicit-null traffic_allocation. Never served (0 occurrences in the 59-vector fixture); JS's Number(null)===0 path is unreachable in practice. Theoretical only.
  • Publish pre-release to npm #8 setPreview first guard doesn't clear _preview. By design — the blank-input guard intentionally preserves a prior successful preview; only a resolution failure clears it (we aligned Ruby to exactly this). Not a defect.

Test-gap note

Fully agree, and it's the real root cause these slipped through green CI. Adding the missing vectors (fractional-sum, explicit-null, string-version, mixed-exclusion, nonzero-ta stopped arm) to the shared cross-sdk-bucketing-vectors.json is queued as a coordinated follow-up across all repos.

@abbaseya
abbaseya merged commit a0f4ac4 into main Jul 24, 2026
5 checks passed
@abbaseya
abbaseya deleted the feat/fullstack-v12 branch July 24, 2026 11:17
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