--- title: "Promotion Stacking Correctness" canonical: "https://mumo.chat/p/promotion-stacking-correctness-zodyjn" machine_version: 1 models: ["GPT","Grok","Muse"] round_count: 3 published_at: "2026-07-11T17:07:23.156109+00:00" updated_at: "2026-07-11T17:15:38.47+00:00" brief: "https://mumo.chat/p/promotion-stacking-correctness-zodyjn.brief.md" --- > Machine-readable transcript of a mumo multi-model deliberation, > generated from the frozen publish snapshot. > Canonical page: https://mumo.chat/p/promotion-stacking-correctness-zodyjn > Brief (synthesis only): https://mumo.chat/p/promotion-stacking-correctness-zodyjn.brief.md # Promotion Stacking Correctness *A multi-model AI conversation on mumo* *July 11, 2026* --- ## Session Takeaway *(mumo-generated synthesis of the whole session — the rounds below are the evidence)* **The session landed on a GO decision for a two-phase evaluator that enforces global dominance and authoritative checkout repricing, provided you treat multi-global winner rules as provisional defaults pending product review.** The moderator opened by pressure-testing a proposed fix for a promotion stacking bug that risked $2.3M/week in revenue. Feedback from the panel steered discussion toward enforcing deterministic sort orders and replacing global flags with a two-phase evaluation engine before closing on strict checkout invariants and fail-loud configuration policies. ### Arcs #### SHIFTED — Two-phase evaluation guarantees global dominance over group combinations. (Rounds 1, 2) The panel moved from debating priority-based sorting to adopting a two-phase mechanism where global promotions are evaluated first and win exclusively. This shift resolved the risk of high-value staff discounts being skipped by group logic, though it silently decides that globals beat any group sum—a policy choice marked as provisional. > **Claim** — Muse: "Don't rely on sort for global exclusivity. Two-phase evaluation" > - KEEP by Grok — "Phase-1 global then phase-2 groups is the concrete mechanism that stops EMP50 from silently losing when product sets a worse priority number." > - CHALLENGE by GPT — "A global-first phase silently introduces a stronger policy: every applicable global promotion beats every group combination. That is valid only if Product explicitly defines global as dominant rather than merely incompatible; otherwise deterministic priority or valid-set selection should decide." > - CHALLENGE by Moderator > **Claim** — Grok: "Two-phase is the narrowest implementation that makes “global always wins alone” true by construction and still re-uses the existing priority + ID ordering for everything else." > - CORE by GPT — "This is the right mechanism for Friday now that global dominance is an explicit product fact rather than an inferred policy." #### HELD — Authoritative checkout repricing replaces stale-price grace periods. (Rounds 1, 3) From the start, the panel rejected honoring cached cart totals to prevent fraud and accounting errors. The final invariant mandates that payment authorization matches only the exact amount and quote version most recently confirmed by the customer after a fresh engine evaluation. > **Claim** — Grok: "The cleanest operational compromise is a cart schema version, an asynchronous reprice job that rewrites open carts under a feature flag, and a hard reprice-at-checkout that logs the delta for every order in the first 72 hours." > - CHALLENGE by GPT — "I agree on versioning and authoritative checkout repricing, but not on proactively rewriting all open carts. Lazy repricing of active carts avoids a cache storm and reduces the number of customer-visible totals changed before checkout." > **Claim** — GPT: "**Payment authorization may occur only when the cart revision, promotion revision, payable amount, and quote ID exactly match the latest quote displayed to and explicitly confirmed by the customer.**" > - CORE by Grok — "This is the precise checkout invariant; quote ID plus dual revisions is what actually binds confirmation to payment." > - CORE by Muse — "This is the checkout invariant we should ship Friday - no grace, no stale-cache payment." #### SHIFTED — Fail-loud configuration blocks runtime guessing on unknown group policies. (Rounds 2, 3) An early proposal to default unknown group behaviors to a safe singleton mode was challenged as a silent margin leak. The session converged on failing closed at activation, rejecting any promotion where the group policy is undefined to ensure no runtime improvisation occurs. > **Claim** — Muse: "rule:= rules[g] // default to Singleton if unknown for safety" > - CHALLENGE by GPT — "This contradicts the stated configuration-rejection blocker and can silently suppress legitimate lifecycle-like combinations. An unknown group rule should fail activation or evaluation loudly, never acquire guessed singleton semantics." > - CHALLENGE by Grok — "A runtime default to Singleton is still a guessed policy; unconfigured groups must fail closed at load or activation time rather than silently mutex everything." > - CHALLENGE by Moderator > **Claim** — GPT: "An unknown policy must fail validation when a configuration revision is activated; the previously valid revision remains live." > - KEEP by Grok — "Fail-closed at activation with the prior revision staying live is the only safe operational pattern; runtime guessing is gone." > - CORE by Muse — "Locks fail-loud at activation as release blocker instead of runtime guess - aligns with abandoning Singleton default." --- ## Opening Prompt Pressure-test a proposed fix to our promotion stacking engine - CORE checkout path $2.3M/week. Attack correctness. ## System: cart-pricer Go `promotions(code, type, priority INT, exclusive_group TEXT, exclusivity_scope ENUM('group','global','none'), stackable BOOL)` `cart_discounts`. Old code: `has_non_stackable bool` global flag. ## Bug SUMMER20 (20% off, exclusive_group='seasonal', exclusivity_scope='group', stackable=false, priority 10) and WELCOME10 ($10 off, group='lifecycle', scope='group', stackable=true, priority 20) - user expects both (different groups). Engine set global `has_non_stackable=true` after SUMMER20 and skipped WELCOME10. 114 tickets. Second trap: EMP50 (50% staff, group='employee', stackable=false, scope='global') - if we naively fix to group-scoped only, EMP50 would stack with seasonal, which must NOT happen. This is the missing concept. ## Proposed fix Replace global bool with: ``` nonStackableByGroup map[group]bool globalExclusiveApplied bool ``` Evaluation: 1. If `globalExclusiveApplied` -> skip any further promo (global blocks all). 2. If current `exclusivity_scope='global'` and any `applied>0` -> skip current (global cannot be added to existing cart). 3. If `nonStackableByGroup[current.group]` -> skip (group already consumed by non-stackable). 4. If current.stackable==false && len(appliedInGroup[current.group])>0 -> skip (non-stackable cannot join occupied group). 5. Else apply, update trackers. Migration: Audit existing promos where `stackable=false` meant global - we found EMP50 and PARTNER100 should be scope='global', rest 'group'. Backfill script ready. ## What I want Attack: - Where does group+global still misapply? Two non-stackable in same group different priority - which wins? One global + two groups? - Interaction with `exclusive_group` - are we now double-enforcing same concept? Can two stackable=true in same group both apply - is that desired? - Back-compat: carts in Redis with old `applied_discounts` computed under old logic. On deploy, do we reprice open carts (price jump at checkout) or version the cart? - Black Friday prep is 20 weeks out, but we have "Summer Sale" in 3 days - $2.3M/week at risk. Be concrete highest-risk. --- ## Round 1 **User:** Pressure-test a proposed fix to our promotion stacking engine - CORE checkout path $2.3M/week. Attack correctness. ## System: cart-pricer Go `promotions(code, type, priority INT, exclusive_group TEXT, exclusivity_scope ENUM('group','global','none'), stackable BOOL)` `cart_discounts`. Old code: `has_non_stackable bool` global flag. ## Bug SUMMER20 (20% off, exclusive_group='seasonal', exclusivity_scope='group', stackable=false, priority 10) and WELCOME10 ($10 off, group='lifecycle', scope='group', stackable=true, priority 20) - user expects both (different groups). Engine set global `has_non_stackable=true` after SUMMER20 and skipped WELCOME10. 114 tickets. Second trap: EMP50 (50% staff, group='employee', stackable=false, scope='global') - if we naively fix to group-scoped only, EMP50 would stack with seasonal, which must NOT happen. This is the missing concept. ## Proposed fix Replace global bool with: ``` nonStackableByGroup map[group]bool globalExclusiveApplied bool ``` Evaluation: 1. If `globalExclusiveApplied` -> skip any further promo (global blocks all). 2. If current `exclusivity_scope='global'` and any `applied>0` -> skip current (global cannot be added to existing cart). 3. If `nonStackableByGroup[current.group]` -> skip (group already consumed by non-stackable). 4. If current.stackable==false && len(appliedInGroup[current.group])>0 -> skip (non-stackable cannot join occupied group). 5. Else apply, update trackers. Migration: Audit existing promos where `stackable=false` meant global - we found EMP50 and PARTNER100 should be scope='global', rest 'group'. Backfill script ready. ## What I want Attack: - Where does group+global still misapply? Two non-stackable in same group different priority - which wins? One global + two groups? - Interaction with `exclusive_group` - are we now double-enforcing same concept? Can two stackable=true in same group both apply - is that desired? - Back-compat: carts in Redis with old `applied_discounts` computed under old logic. On deploy, do we reprice open carts (price jump at checkout) or version the cart? - Black Friday prep is 20 weeks out, but we have "Summer Sale" in 3 days - $2.3M/week at risk. Be concrete highest-risk. **GPT:** The highest-risk defect is not the map; it is that the proposal remains **order-dependent**. With the five rules as written, `[SUMMER20, EMP50]` produces SUMMER20 while `[EMP50, SUMMER20]` produces EMP50. Replacing a boolean with two trackers does not fix correctness unless candidate ordering and conflict resolution are part of the contract. ### Define winner selection before shipping Sort all eligible candidates by an explicit precedence—presumably lower `priority` wins, though that must be confirmed—then use a stable secondary key such as promotion ID. Do not rely on database, map, coupon-entry, or automatic-promotion order. Without a tie-breaker, two non-stackable promotions with equal priority can produce nondeterministic discounts across executions. Under priority-greedy semantics: - Two non-stackable promotions in the same group: the higher-priority one wins. - A higher-priority stackable promotion in a group can cause a lower-priority non-stackable promotion in that group to lose. - A global promotion wins alone if encountered first. - If any higher-priority group promotion is accepted first, the global promotion loses, and promotions from other groups may subsequently apply. That last case needs explicit product approval. “Global exclusive” can mean either “must be alone if selected” or “always beats every non-global promotion.” Those are different policies. Your current algorithm implements the first policy only when candidates are deterministically priority-sorted; it does not implement “global always wins”. If priority means campaign importance, priority-greedy is defensible. If the intended winner is the largest customer discount, smallest merchant cost, or best valid combination, greedy evaluation is wrong; you need to compare valid sets. Do not accidentally let loop order decide this business question. ### The schema currently has contradictory states I would define `exclusive_group` as a **combination bucket**, not itself as an exclusivity instruction. Under that interpretation, two `stackable=true` promotions in the same group should both apply. `stackable=false` makes the promotion conflict with every other applied promotion in its declared scope. There is no double-enforcement—but the names make it look like there is. The valid states should be constrained: - `scope='group'` requires a nonempty group. - `scope='global'` should require `stackable=false`. - `scope='none'` should require `stackable=true`. - Group values need normalization or, preferably, foreign keys/IDs rather than free text. - The same promotion must be deduplicated before evaluation. Without those constraints, values such as `scope='global', stackable=true` and `scope='none', stackable=false` have ambiguous behavior that different code paths can interpret differently. Empty or null groups are also dangerous: collapsing all missing groups into `""` can make unrelated promotions conflict, while treating every missing group as distinct can allow unintended stacking. Longer-term, I would replace the two partly overlapping fields with one policy enum: ```text STACKABLE EXCLUSIVE_WITHIN_GROUP EXCLUSIVE_GLOBAL ``` Keep `combination_group` only for the middle state. Do not do that redesign three days before Summer Sale, but make the current state constraints equivalent. ### Tighten the evaluator “Applied” must mean a promotion produced a positive, committed discount—not merely that it was eligible or appended to a result slice. A zero-value promotion consuming a group or blocking a later global promotion can suppress a real discount. Also decide whether “positive” is before or after rounding; a calculated fraction that rounds to zero should normally not consume exclusivity. I would express the core conflict symmetrically and then apply it to priority-sorted candidates: ```go func conflicts(candidate Promo, accepted []AppliedPromo) bool { for _, prior := range accepted { if candidate.Scope == Global || prior.Scope == Global { return true } if candidate.Group == prior.Group && (candidate.Scope == Group || prior.Scope == Group) && (!candidate.Stackable || !prior.Stackable) { return true } } return false } ``` The exact condition can be optimized into trackers after the semantics are proven. Using a direct pairwise reference implementation in tests or shadow mode is safer than treating the optimized maps as the specification. Also return structured rejection reasons such as `CONFLICT_GLOBAL`, `GROUP_CONSUMED`, and `LOWER_PRIORITY`. That will make both support cases and rollout monitoring substantially easier. ### Minimum correctness matrix Before enabling this, I would require golden tests covering: - `SUMMER20 + WELCOME10` → both, independent of input order after sorting. - Two non-stackable seasonal promotions → deterministic priority winner. - One stackable and one non-stackable in the same group, in both priority orders. - Two stackable promotions in the same group → both, assuming the bucket interpretation is approved. - `EMP50 + SUMMER20 + WELCOME10`, with EMP50 highest priority → EMP50 only. - The same three with SUMMER20 higher priority than EMP50 → EMP50 rejected, then SUMMER20 and WELCOME10 apply, **if** that is the approved global policy. - Equal priorities → stable ID tie-break. - Global or group promotion calculating zero after caps/rounding → does not consume exclusivity. - Missing/blank group → configuration rejection, not runtime improvisation. - Duplicate submission of the same code → one application. - Fixed and percentage discounts in different priority orders, with exact allocation and rounding snapshots. - Ineligible, expired, exhausted, and no-effect promotions do not alter trackers. The fifth and sixth tests are the executive decision hiding inside this engineering fix. ### Existing Redis carts: reprice, don’t freeze the bug Version the quote, not the customer’s cart forever. Store at least: ```text pricing_engine_version promotion_config_revision priced_at input_hash discount_breakdown ``` On cart read, mutation, and checkout, reprice if the engine version, promotion revision, or pricing inputs differ. Checkout must recompute authoritatively and must not trust a cached payable total. Persisting an old `applied_discounts` list while evaluating new promotions around it can create combinations that neither the old nor new engine would have selected. I would not eagerly rewrite every Redis cart at deploy. Lazy repricing avoids a bulk cache storm and ensures only active carts incur work. For an increased total, show “price updated” and require confirmation before payment; decreases can generally be accepted automatically while still returning the new breakdown. Preserve completed orders and already-finalized payment attempts as immutable historical snapshots. For the reported SUMMER20/WELCOME10 defect, correct repricing should lower affected totals rather than create a price jump. However, unrelated expiry, eligibility, or migration changes can increase totals, so the checkout path still needs explicit changed-price handling. ### Migration risk Auditing only rows with `stackable=false` is too narrow. Audit every active **and scheduled** promotion for invalid field combinations, blank groups, duplicate codes, and assumptions embedded outside the main table. A scheduled promotion omitted from the audit can reactivate the same failure after the immediate launch appears healthy. Use expand/migrate/enable sequencing: 1. Deploy readers that understand the new scope while retaining the old behavior behind a flag. 2. Backfill and validate all active and scheduled records. 3. Add database constraints preventing contradictory states. 4. Shadow-evaluate old and new engines on real cart inputs. 5. Enable new semantics by percentage or promotion allowlist. 6. Remove fallback only after the launch window. EMP50 and PARTNER100 should be explicit migration fixtures, not merely rows found by a one-off query. ### Summer Sale decision Ship a narrow, flagged correction—not a broad promotion-engine redesign. Today, make deterministic sorting, pairwise reference tests, configuration constraints, quote versioning, and rejection telemetry release blockers. Shadow old versus new results immediately, then canary SUMMER20/WELCOME10 before wider rollout. Monitor disagreements by promotion pair, rejection reason, average discount per order, unusually deep effective discounts, checkout total changes, and payment failures. Keep a kill switch that restores old evaluation without rolling back the database migration. The most likely catastrophic failure is not SUMMER20 continuing to miss WELCOME10; it is an unordered or misclassified global promotion either suppressing a major sale across carts or stacking EMP50/PARTNER100 with public discounts. Resolve the global-versus-priority policy and prove input-order independence before this reaches the CORE path. > **GPT's snippets:** > - **CHALLENGE**: "Don't rely on sort for global exclusivity. Two-phase evaluation:" — *A global-first phase silently introduces a stronger policy: every applicable global promotion beats every group combination. That is valid only if Product explicitly defines global as dominant rather than merely incompatible; otherwise deterministic priority or valid-set selection should decide.* > - **KEEP**: "Scope='none' is likewise unhandled: a stackable=false promotion with scope=none will still populate nonStackableByGroup for its (possibly null) group ..." — *This exposes an invalid or ambiguous schema state that should be rejected by constraints rather than assigned ad hoc runtime behavior.* > - **KEEP**: "If the business intent for EMP50 or PARTNER100 is “this code alone, always,” the current rules are insufficient; you need either (a) forced highest pr..." — *This correctly separates exclusivity from selection. A global scope says what may coexist, but not whether that promotion should beat a previously accepted set.* > - **EXPLORE**: "For many retailers, exclusive_group means "at most one from this group regardless of stackable"." — *The naming strongly suggests this interpretation, but the current schema can also support a bucket interpretation where only non-stackable members exclude peers. The decision must be made from the existing business contract and data, not inferred from the column name.* > - **CHALLENGE**: "If new total > old total, keep old for 24h grace and flag for manual review." — *Honoring a stale total creates a separate price-guarantee policy with fraud, accounting, and expiry implications. Unless the business has approved that guarantee, checkout should disclose the authoritative changed price and require confirmation rather than silently preserving cached pricing.* > - **CORE**: "Promotions must be sorted by priority before the walk; without an explicit stable sort (and a defined direction—lower number first or higher number fi..." — *This is the central correctness failure: the proposed state trackers cannot produce deterministic policy without a total candidate ordering.* > - **CHALLENGE**: "The cleanest operational compromise is a cart schema version, an asynchronous reprice job that rewrites open carts under a feature flag, and a hard re..." — *I agree on versioning and authoritative checkout repricing, but not on proactively rewriting all open carts. Lazy repricing of active carts avoids a cache storm and reduces the number of customer-visible totals changed before checkout.* **Muse:** This path is $2.3M/wk - proposed fix is directionally right but has 4 highest risk holes for your exact traps. #### 1. Global + Group still misapplies due to priority ordering Your rule 2: > If `scope='global'` and `len(applied)>0` -> skip global This makes outcome order-dependent. If you sort by priority ASC and `EMP50` priority=1, it applies first, then `globalExclusiveApplied=true` blocks groups. Good. If Product configures `EMP50` with priority=50 and `SUMMER20` priority=10, processing order is `SUMMER20` -> `WELCOME10` -> `EMP50`. Then `EMP50` is skipped because group promos already applied. Employee expects 50% alone, gets 20% + $10 instead. That's a silent internal escalation and margin-wrong in other direction. Fix: Don't rely on sort for global exclusivity. Two-phase evaluation: ``` sort ASC by priority // phase 1: any applicable global? globalCandidates = filter scope==global if len>0: apply highest priority global, return [global] // alone // phase 2: group logic only ``` If business *wants* priority to decide between global vs group, you must enforce invariant: all global promos must have numerically lower priority than any group promo. Add DB check constraint or validation, not tribal knowledge. Second case same group: Two non-stackable same `exclusive_group='seasonal'`, priority 10 and 20. Which wins? With your rules, first wins and sets `nonStackableByGroup[seasonal]=true`, second blocked by rule 3. That's priority-wins if you sort correctly. Make sort explicit - today bug ticket doesn't state sort, very risky. #### 2. `exclusive_group` double meaning + stackable=true same group You are using `exclusive_group` as the map key. Is `exclusive_group` intended as grouping, or are you now encoding exclusivity twice? Current proposal allows: `WELCOME10 lifecycle stackable=true + REFERRAL15 lifecycle stackable=true` -> both apply because rule 3 only checks `nonStackableByGroup` and rule 4 only blocks if `current.stackable==false`. Is that desired? For many retailers, exclusive_group means "at most one from this group regardless of stackable". If that's your intent, you need: ``` if len(appliedInGroup[group])>0 and scope=='group' -> skip unless you explicitly allow multi-stackable groups ``` You must decide and document. Otherwise Summer Sale + another seasonal stackable=true you launch in 3 days stacks 20% + 30% = 50% margin leak - worse than current bug. Add a cap: `maxStackablePerGroup` or `allowMultipleStackable`. Also `scope='none'` is missing: should bypass `nonStackableByGroup` entirely and not set it. Your pseudocode doesn't branch on it. Corrected skeleton: ``` if promo.Scope == "none" { apply; continue } if globalApplied { skip } if promo.Scope == "global" { if len(applied)>0 { skip } else { apply; globalApplied=true } continue } // group scope if nonStackableByGroup[promo.Group] { skip } if !promo.Stackable && len(appliedInGroup[promo.Group])>0 { skip } apply; appliedInGroup[promo.Group] append; if !promo.Stackable { nonStackableByGroup[promo.Group]=true } ``` #### 3. Redis open carts - repricing risk Old engine: `has_non_stackable` global. So old was more restrictive than new - it blocked cross-group stacking. New allows cross-group, so repricing old carts will *lower* total (add WELCOME10), not raise. That's generally safe for trust. Price jump up could still happen if you had a cart where old logic allowed 2 group non-stackable due to bug order? Check history. Low risk of increase, but not zero. Don't do blind reprice on read during Summer Sale. Ship as: * Add `cart.pricer_version` field to Redis. On read: if version != current, recompute in shadow, emit metric `pricer.diff_cents`, log decision. * On *checkout final step* always reprice with latest logic inside transaction - that's canonical. * For display, if new total < old total, show new (happy path). If new total > old total, keep old for 24h grace and flag for manual review. This avoids "$80 became $90 at pay" ticket. Otherwise you will have inconsistent `applied_discounts` between Redis and `cart_discounts` table. #### 4. Migration race - highest immediate risk for Summer Sale You found EMP50 + PARTNER100 = global. If any new promo is inserted between audit and deploy with `stackable=false` defaulting to old meaning, your backfill script misses it and it will be treated as group, allowing EMP50+seasonal stack. Fix before deploy: * DB migration adds `exclusivity_scope` NOT NULL with default, but also Add check: `IF stackable=false AND scope IS NULL -> fail`. * Feature flag `useGroupScoping` - dark launch yesterday, shadow-evaluate both engines, compare for 1 day. You have 3 days to Summer Sale - do not big-bang deploy Friday. * Add unit golden snapshots from source example: - [A] seasonal non-stack(10) + lifecycle stack(20) => both apply - [B] seasonal non-stack(10) + seasonal non-stack(20) => only 10 - [C] seasonal stack(10) + seasonal non-stack(20) => only 10 (defines your policy) - [D] global(1) + seasonal(10) + lifecycle(20) => only global if phase1, else priority dependent - assert your chosen policy - [E] two stackable lifecycle same group => both? assert intended - [F] scope=none always applies with global For Summer Sale in 3 days: hotfix option is keep old engine but temporarily move WELCOME10 to `exclusive_group='promo_bypass'` with custom allowlist - less risk than full engine swap. If you must swap, flag-gate and monitor `global_promo_skipped_total` counter. Bottom line: proposed map fix solves 114 tickets, but without global-first phase + sort guarantee + scope=none handling, you trade one exclusivity bug for another where employee discount silently disappears or two lifecycle stackables double dip. > **Muse's snippets:** > - **CORE**: "The highest-risk defect is not the map; it is that the proposal remains **order-dependent**." — *This is the crux – two-trackers without a total order contract still lets loop order decide money.* > - **EXPLORE**: "“Applied” must mean a promotion produced a positive, committed discount—not merely that it was eligible or appended to a result slice." — *Zero-value consuming exclusivity is a subtle leak I missed in my tracker sketch.* > - **KEEP**: "Promotions must be sorted by priority before the walk; without an explicit stable sort (and a defined direction—lower number first or higher number fi..." — *Resonates – priority direction plus secondary key needs to be codified and tested, not assumed.* > - **CHALLENGE**: "Scope='none' is likewise unhandled: a stackable=false promotion with scope=none will still populate nonStackableByGroup for its (possibly null) group ..." — *Good catch on none-scope – I'd add guard: if scope=none, skip both check and mutation of group maps.* > - **CORE**: "“Global exclusive” can mean either “must be alone if selected” or “always beats every non-global promotion.”" — *I hadn't cleanly split these two semantics before – needed explicit product sign-off before coding.* > - **KEEP**: "There is no “replace earlier discounts if the new exclusive is better” step, nor a final “choose the maximum-value exclusive set” pass." — *Exactly the EMP50 trap I worried about – greedy walk picks wrong exclusive when globals sort late.* **Grok:** The proposed fix correctly eliminates the original global-flag over-reach that suppressed WELCOME10, and the dual trackers plus the five evaluation rules are a reasonable sketch for distinguishing group-local versus cart-global exclusion. However, several concrete correctness holes remain; the highest-risk ones will surface under real cart traffic within days, not weeks. Start with evaluation order, because every subsequent rule is order-dependent and the proposal never states it. Promotions must be sorted by priority before the walk; without an explicit stable sort (and a defined direction—lower number first or higher number first) the same set of eligible codes can produce different applied sets on different checkouts. In the SUMMER20 (priority 10) + WELCOME10 (priority 20) case the outcome is benign either way, but consider two non-stackable promotions that share exclusive_group='seasonal': SUMMER20 (20 %, priority 10, stackable=false, scope=group) and FLASH30 (30 %, priority 5, stackable=false, scope=group). Whichever the sort places first wins; the second is rejected by rule 3 or 4. That is deterministic only if the sort is total and stable. Priority ties are undefined and will be non-deterministic under map iteration or concurrent Redis loads. The engine therefore needs an explicit secondary key (creation timestamp or code) and a unit-test matrix that asserts the exact winner for every pair of same-group non-stackables. The same ordering problem appears with a global. EMP50 (scope=global, stackable=false) versus SUMMER20 (scope=group) never stack—good, that was the missing concept—but which one the customer actually receives is purely a function of which code is examined first. If EMP50 has the worse priority it is simply skipped once any other discount has been applied (rule 2); the staff member walks away with 20 % instead of 50 %. There is no “replace earlier discounts if the new exclusive is better” step, nor a final “choose the maximum-value exclusive set” pass. Greedy left-to-right application therefore silently selects the wrong exclusive under realistic priority configurations, and the margin or customer-experience impact is unbounded. The same pattern occurs with one global plus two different groups: the global is admitted only when it sorts first; otherwise the two group discounts both apply and the global is dropped. If the business intent for EMP50 or PARTNER100 is “this code alone, always,” the current rules are insufficient; you need either (a) forced highest priority plus a clear-all-previous action when a global is accepted, or (b) a post-pass that re-evaluates the exclusive candidates by discount value. Next, the interaction of exclusive_group with the new trackers is under-specified and risks double-enforcement or under-enforcement. The proposal treats exclusive_group solely as the map key for nonStackableByGroup and appliedInGroup. That works for the SUMMER20/WELCOME10 case, yet leaves open whether two promotions that both carry stackable=true and the same exclusive_group are allowed to co-exist. Under the five rules they are; nothing sets nonStackableByGroup and rule 4 only fires for stackable=false. If the column name exclusive_group is meant to convey mutual exclusion inside the group regardless of the stackable bit, then the rules are too permissive and you will recreate the original stacking complaints in the opposite direction. Conversely, if exclusive_group is merely a bucket that exclusivity_scope consults, then the stackable flag is now partially redundant with scope and the migration audit must also decide what stackable=true + scope=group actually means. Either interpretation requires an explicit product decision and a corresponding test; the current write-up papers over it. Scope='none' is likewise unhandled: a stackable=false promotion with scope=none will still populate nonStackableByGroup for its (possibly null) group and can therefore block unrelated codes, which is almost certainly unintended. The tracker set itself is incomplete as written. The bullet list mentions nonStackableByGroup and globalExclusiveApplied, yet rules 2 and 4 also require a total applied count and a per-group applied collection. Those structures must be updated atomically with every successful apply, including the case in which a global is accepted (does it also mark its own exclusive_group? the proposal is silent). Any partial update leaves the maps inconsistent for the remainder of the walk. On the Redis side the highest immediate risk is open-cart repricing during the Summer Sale window. Carts currently stored under the old has_non_stackable logic contain a strictly smaller applied_discounts set than the new engine will compute for the same lines. If you reprice on read or on checkout, customers who already see a total will experience a sudden drop (margin leakage, but usually pleasant surprise) or, in the rarer configuration where priorities reverse an exclusive choice, a sudden rise (support tickets, abandoned checkouts). If you freeze the old applied_discounts with a version stamp, you create a second code path that must be kept alive until every pre-deploy cart expires; any checkout validation that re-runs the pricer will still diverge and either reject the cart or silently rewrite the total. The cleanest operational compromise is a cart schema version, an asynchronous reprice job that rewrites open carts under a feature flag, and a hard reprice-at-checkout that logs the delta for every order in the first 72 hours. Skipping that work before the Summer Sale launch guarantees either price flickering or silent margin errors at $2.3 M/week velocity. Migration correctness is the other launch blocker. The audit that maps EMP50 and PARTNER100 to scope=global and everything else to scope=group is necessary, yet any promo that historically relied on the global flag for exclusion and is left at scope=group will now stack with seasonal and lifecycle codes. The reverse error (a true group exclusive promoted to global) will suppress legitimate combinations and recreate the 114-ticket class of complaints. Because the backfill is a one-shot script against production data, it needs a dry-run that emits the exact before/after applied set for a sampled week of carts, plus a kill-switch that reverts the exclusivity_scope column if the Summer Sale monitors show unexpected average-discount spikes. Concrete highest-risk items to close before the three-day Summer Sale window: - Codify the sort order and add golden tests for (a) two same-group non-stackables, (b) global vs. any group, (c) global + two groups, (d) stackable=true pair inside one group. - Decide and document whether exclusive_group implies exclusion even for stackable=true; adjust rule 3 or 4 accordingly. - Implement full tracker state (applied count, appliedInGroup, nonStackableByGroup, globalExclusiveApplied) and the exact mutations on apply. - Add a value-based tie-breaker or clear-and-replace path for globals so EMP50 cannot lose to a lower-value seasonal. - Version Redis carts, reprice asynchronously under flag, force reprice + delta log at checkout. - Run the migration dry-run against last week’s carts and gate the Summer Sale codes behind the same flag. The five rules are a solid direction, but without the ordering contract, the selection policy for exclusives, the complete state machine, and a rehearsed cart-migration plan they will mis-apply under the exact load you are about to receive. Fix those four gaps first; the rest of Black Friday prep can follow. > **Grok's snippets:** > - **KEEP**: "Don't rely on sort for global exclusivity. Two-phase evaluation:" — *Phase-1 global then phase-2 groups is the concrete mechanism that stops EMP50 from silently losing when product sets a worse priority number.* > - **KEEP**: "If new total > old total, keep old for 24h grace and flag for manual review." — *Grace-on-increase plus auto-accept-on-decrease is the operational detail that keeps checkout from generating support tickets during the reprice window.* > - **KEEP**: "A scheduled promotion omitted from the audit can reactivate the same failure after the immediate launch appears healthy." — *Auditing only currently active rows is too narrow—Summer Sale and anything already scheduled must be in the dry-run fixtures or the bug just waits three days.* > - **CORE**: "For many retailers, exclusive_group means "at most one from this group regardless of stackable"." — *If product intends at-most-one-per-group, the current rules leak margin the moment a second stackable seasonal ships—this decision must be explicit before any Summer Sale code goes live.* > - **EXPLORE**: "“Applied” must mean a promotion produced a positive, committed discount—not merely that it was eligible or appended to a result slice." — *Zero-after-rounding consuming a group or blocking a global is a silent defect I under-weighted—worth golden tests around caps and fractional percentages.* > - **KEEP**: "I would define `exclusive_group` as a **combination bucket**, not itself as an exclusivity instruction." — *Clean separation: group is the key, stackable/scope own the exclusion rules—this kills the double-enforcement ambiguity without a redesign before Summer Sale.* > - **SHIFT**: "Using a direct pairwise reference implementation in tests or shadow mode is safer than treating the optimized maps as the specification." — *I previously treated completing the nonStackableByGroup/globalExclusiveApplied trackers as the path to correctness; this moves me to treat a pairwise conflicts oracle as the real spec and the maps as a derived optimization that must match it.* > - **CORE**: "“Global exclusive” can mean either “must be alone if selected” or “always beats every non-global promotion.” Those are different policies." — *This is exactly the executive decision buried in the fix—without it, every EMP50/PARTNER100 case is a coin flip dressed up as engineering.* ### Reactions (claim map) > **Claim** — GPT: "“Applied” must mean a promotion produced a positive, committed discount—not merely that it was eligible or appended to a result slice." > - EXPLORE by Grok — "Zero-after-rounding consuming a group or blocking a global is a silent defect I under-weighted—worth golden tests around caps and fractional percentages." > - EXPLORE by Muse — "Zero-value consuming exclusivity is a subtle leak I missed in my tracker sketch." > **Claim** — GPT: "“Global exclusive” can mean either “must be alone if selected” or “always beats every non-global promotion.” Those are different policies." > - CORE by Grok — "This is exactly the executive decision buried in the fix—without it, every EMP50/PARTNER100 case is a coin flip dressed up as engineering." > - CORE by Muse — "I hadn't cleanly split these two semantics before – needed explicit product sign-off before coding." > - CORE by Moderator > **Claim** — GPT: "A scheduled promotion omitted from the audit can reactivate the same failure after the immediate launch appears healthy." > - KEEP by Grok — "Auditing only currently active rows is too narrow—Summer Sale and anything already scheduled must be in the dry-run fixtures or the bug just waits three days." > **Claim** — GPT: "I would define `exclusive_group` as a **combination bucket**, not itself as an exclusivity instruction." > - KEEP by Grok — "Clean separation: group is the key, stackable/scope own the exclusion rules—this kills the double-enforcement ambiguity without a redesign before Summer Sale." > **Claim** — GPT: "The highest-risk defect is not the map; it is that the proposal remains **order-dependent**." > - CORE by Muse — "This is the crux – two-trackers without a total order contract still lets loop order decide money." > **Claim** — GPT: "Using a direct pairwise reference implementation in tests or shadow mode is safer than treating the optimized maps as the specification." > - SHIFT by Grok — "I previously treated completing the nonStackableByGroup/globalExclusiveApplied trackers as the path to correctness; this moves me to treat a pairwise conflicts oracle as the real spec and the maps as a derived optimization that must match it." > **Claim** — Grok: "Promotions must be sorted by priority before the walk; without an explicit stable sort (and a defined direction—lower number first or higher number first) the same set of eligible codes can produce different applied sets on different checkouts." > - KEEP by Muse — "Resonates – priority direction plus secondary key needs to be codified and tested, not assumed." > - CORE by GPT — "This is the central correctness failure: the proposed state trackers cannot produce deterministic policy without a total candidate ordering." > **Claim** — Grok: "Scope='none' is likewise unhandled: a stackable=false promotion with scope=none will still populate nonStackableByGroup for its (possibly null) group and can therefore block unrelated codes, which is almost certainly unintended." > - KEEP by GPT — "This exposes an invalid or ambiguous schema state that should be rejected by constraints rather than assigned ad hoc runtime behavior." > - CHALLENGE by Muse — "Good catch on none-scope – I'd add guard: if scope=none, skip both check and mutation of group maps." > **Claim** — Grok: "If the business intent for EMP50 or PARTNER100 is “this code alone, always,” the current rules are insufficient; you need either (a) forced highest priority plus a clear-all-previous action when a global is accepted, or (b) a post-pass that re-evaluates the exclusive candidates by discount value." > - KEEP by GPT — "This correctly separates exclusivity from selection. A global scope says what may coexist, but not whether that promotion should beat a previously accepted set." > **Claim** — Grok: "The cleanest operational compromise is a cart schema version, an asynchronous reprice job that rewrites open carts under a feature flag, and a hard reprice-at-checkout that logs the delta for every order in the first 72 hours." > - CHALLENGE by GPT — "I agree on versioning and authoritative checkout repricing, but not on proactively rewriting all open carts. Lazy repricing of active carts avoids a cache storm and reduces the number of customer-visible totals changed before checkout." > **Claim** — Grok: "There is no “replace earlier discounts if the new exclusive is better” step, nor a final “choose the maximum-value exclusive set” pass." > - KEEP by Muse — "Exactly the EMP50 trap I worried about – greedy walk picks wrong exclusive when globals sort late." > **Claim** — Muse: "Don't rely on sort for global exclusivity. Two-phase evaluation" > - KEEP by Grok — "Phase-1 global then phase-2 groups is the concrete mechanism that stops EMP50 from silently losing when product sets a worse priority number." > - CHALLENGE by GPT — "A global-first phase silently introduces a stronger policy: every applicable global promotion beats every group combination. That is valid only if Product explicitly defines global as dominant rather than merely incompatible; otherwise deterministic priority or valid-set selection should decide." > - CHALLENGE by Moderator > **Claim** — Muse: "For many retailers, exclusive_group means \"at most one from this group regardless of stackable\"." > - EXPLORE by GPT — "The naming strongly suggests this interpretation, but the current schema can also support a bucket interpretation where only non-stackable members exclude peers. The decision must be made from the existing business contract and data, not inferred from the column name." > - CORE by Grok — "If product intends at-most-one-per-group, the current rules leak margin the moment a second stackable seasonal ships—this decision must be explicit before any Summer Sale code goes live." > **Claim** — Muse: "If new total > old total, keep old for 24h grace and flag for manual review." > - KEEP by Grok — "Grace-on-increase plus auto-accept-on-decrease is the operational detail that keeps checkout from generating support tickets during the reprice window." > - CHALLENGE by GPT — "Honoring a stale total creates a separate price-guarantee policy with fraud, accounting, and expiry implications. Unless the business has approved that guarantee, checkout should disclose the authoritative changed price and require confirmation rather than silently preserving cached pricing." > - CHALLENGE by Moderator ### Takeaway **The proposed fix is a solid start, but without a deterministic sort order and a safer migration plan, you risk trading one set of bugs for unpredictable pricing errors during your $2.3M sale.** #### Unanimous: Stop letting loop order dictate your margin; enforce a deterministic sort. Relying on the order in which items appear makes the outcome random, especially when priorities and map-based trackers are involved. You need a stable total sort—like priority plus a unique ID—and golden tests for conflict scenarios to ensure the engine isn't just picking winners based on iteration. > **Claim** — Grok: "Promotions must be sorted by priority before the walk; without an explicit stable sort (and a defined direction—lower number first or higher number first) the same set of eligible codes can produce different applied sets on different checkouts." > - KEEP by Muse — "Resonates – priority direction plus secondary key needs to be codified and tested, not assumed." > - CORE by GPT — "This is the central correctness failure: the proposed state trackers cannot produce deterministic policy without a total candidate ordering." > **Claim** — GPT: "The highest-risk defect is not the map; it is that the proposal remains **order-dependent**." > - CORE by Muse — "This is the crux – two-trackers without a total order contract still lets loop order decide money." #### Majority: Force a two-phase evaluation to keep global exclusives from being dropped. Current logic allows global exclusives to be skipped if they’re processed after a group promo, which effectively kills high-value discounts like employee offers. Check for global promotions in a separate first phase so they don't lose just because they were evaluated late. > **Claim** — GPT: "“Global exclusive” can mean either “must be alone if selected” or “always beats every non-global promotion.” Those are different policies." > - CORE by Grok — "This is exactly the executive decision buried in the fix—without it, every EMP50/PARTNER100 case is a coin flip dressed up as engineering." > - CORE by Muse — "I hadn't cleanly split these two semantics before – needed explicit product sign-off before coding." > - CORE by Moderator > **Claim** — Muse: "Don't rely on sort for global exclusivity. Two-phase evaluation" > - KEEP by Grok — "Phase-1 global then phase-2 groups is the concrete mechanism that stops EMP50 from silently losing when product sets a worse priority number." > - CHALLENGE by GPT — "A global-first phase silently introduces a stronger policy: every applicable global promotion beats every group combination. That is valid only if Product explicitly defines global as dominant rather than merely incompatible; otherwise deterministic priority or valid-set selection should decide." > - CHALLENGE by Moderator > **Claim** — Grok: "If the business intent for EMP50 or PARTNER100 is “this code alone, always,” the current rules are insufficient; you need either (a) forced highest priority plus a clear-all-previous action when a global is accepted, or (b) a post-pass that re-evaluates the exclusive candidates by discount value." > - KEEP by GPT — "This correctly separates exclusivity from selection. A global scope says what may coexist, but not whether that promotion should beat a previously accepted set." #### Contested: Do not force-reprice open carts; use versioning to handle the transition safely. Blindly overwriting Redis carts risks mass support tickets for price changes or checkout errors. Use a versioning system and perform final repricing at the exact moment of checkout, logging the delta for transparency rather than forcing an update on every read. > **Claim** — Muse: "If new total > old total, keep old for 24h grace and flag for manual review." > - KEEP by Grok — "Grace-on-increase plus auto-accept-on-decrease is the operational detail that keeps checkout from generating support tickets during the reprice window." > - CHALLENGE by GPT — "Honoring a stale total creates a separate price-guarantee policy with fraud, accounting, and expiry implications. Unless the business has approved that guarantee, checkout should disclose the authoritative changed price and require confirmation rather than silently preserving cached pricing." > - CHALLENGE by Moderator > **Claim** — Grok: "The cleanest operational compromise is a cart schema version, an asynchronous reprice job that rewrites open carts under a feature flag, and a hard reprice-at-checkout that logs the delta for every order in the first 72 hours." > - CHALLENGE by GPT — "I agree on versioning and authoritative checkout repricing, but not on proactively rewriting all open carts. Lazy repricing of active carts avoids a cache storm and reduces the number of customer-visible totals changed before checkout." #### Majority: Lock down edge cases like scope='none' and group-stacking rules. Leaving 'none' scope unhandled will accidentally block valid promotions, and failing to define if multiple stackables can coexist in a group will create margin leaks. Hardcode these constraints now so the engine doesn't improvise behavior at runtime. > **Claim** — Muse: "For many retailers, exclusive_group means \"at most one from this group regardless of stackable\"." > - EXPLORE by GPT — "The naming strongly suggests this interpretation, but the current schema can also support a bucket interpretation where only non-stackable members exclude peers. The decision must be made from the existing business contract and data, not inferred from the column name." > - CORE by Grok — "If product intends at-most-one-per-group, the current rules leak margin the moment a second stackable seasonal ships—this decision must be explicit before any Summer Sale code goes live." > **Claim** — Grok: "Scope='none' is likewise unhandled: a stackable=false promotion with scope=none will still populate nonStackableByGroup for its (possibly null) group and can therefore block unrelated codes, which is almost certainly unintended." > - KEEP by GPT — "This exposes an invalid or ambiguous schema state that should be rejected by constraints rather than assigned ad hoc runtime behavior." > - CHALLENGE by Muse — "Good catch on none-scope – I'd add guard: if scope=none, skip both check and mutation of group maps." --- ### The human's take > **Core** from GPT: > > "“Global exclusive” can mean either “must be alone if selected” or “always beats every non-global promotion.” Those are different policies." > → sent to GPT, Grok, Muse > **Challenged** Muse: > > "Don't rely on sort for global exclusivity. Two-phase evaluation:" > → sent to GPT, Grok, Muse > **Challenged** Muse: > > "If new total > old total, keep old for 24h grace and flag for manual review." > → sent to GPT, Grok, Muse ## Round 2 **User:** The floor from round 1 is set: candidate evaluation gets a total, stable ordering (priority + ID tie-break) and input-order independence is a release blocker; the pairwise conflicts oracle is the spec and the trackers are a derived optimization that must match it; scope='none' bypasses group maps entirely; contradictory schema states get rejected by constraints; and the migration audit covers active AND scheduled promotions. Now the three product facts you've been asking for — **author stipulations, treat as fact:** (1) EMP50 and PARTNER100 mean "this code alone, always" — a global promotion, when eligible, must WIN, not merely refuse to join; the staff member gets 50% alone regardless of what applied first or what priority number someone typed. (2) Seasonal promotions never combine with each other — at most one seasonal per cart, regardless of the stackable bit. (3) Lifecycle promotions MAY combine with each other — two stackable lifecycle codes both apply. Note that facts 2 and 3 conflict with any single global rule for group semantics — resolve that deliberately, not by accident of the map key. Under those facts, settle the mechanism fight: two-phase evaluation, a priority invariant enforced by constraint, or a valid-set selection pass — pick one for the 3-day window, and say what the chosen mechanism does with the case that breaks it (a global that's eligible but arrives mid-walk after group promos already applied — replace-and-clear, or reject-the-cart-state?). The objection that two-phase silently decides policy is now moot on policy — but not on mechanism: say what ELSE two-phase decides that Product hasn't signed. Second decision: the stale-price grace period. The 24-hour honor-the-old-total proposal has a standing objection — it's a price guarantee with fraud, accounting, and expiry implications nobody approved. Resolve it: grace, disclose-and-confirm, or something narrower. Whoever wins, the checkout invariant must be stated in one line. Keep round-1 positions that survived in one line each. Deliverable: the evaluation algorithm as you'd ship it Friday, the golden-test matrix updated for the three stipulated facts, and your reprice policy. **GPT:** I would ship **two-phase evaluation** Friday. The moderator’s stipulation removes my earlier policy objection: global now explicitly means dominant, not merely incompatible. @Muse’s mechanism is therefore the simplest safe implementation for the three-day window. I would not encode dominance indirectly through priority constraints; that makes a product invariant depend on every future author assigning magic numbers correctly. I would also not introduce valid-set optimization now because Product has already said the eligible global wins even if a group combination gives a larger discount. Two-phase does make one additional decision Product has not signed: **which promotion wins when multiple globals are eligible**. For Friday, use the existing total order—priority, then immutable promotion ID—and document that as provisional. Do not choose the largest discount without authorization. It also requires a precise definition of “eligible”: perform eligibility checks using the same undiscounted eligibility base the current engine uses, and treat a promotion that calculates to zero after caps and rounding as non-applicable. Changing threshold semantics during this fix would unnecessarily broaden the release. ## Friday algorithm First construct a deduplicated, immutable candidate set and evaluate eligibility. Then: ```go candidates := EligiblePositivePromotions(cart) // dedupe by promotion ID sort.SliceStable(candidates, byPriorityThenID) // Phase 1: dominant globals globals := filter(candidates, ScopeGlobal) if len(globals) > 0 { return ApplyAlone(cart, globals[0]) } // Phase 2: non-global selection accepted := []Promo{} for _, candidate := range candidates { if !conflictsWithAny(candidate, accepted, groupPolicies) { accepted = append(accepted, candidate) } } return PriceInOrder(cart, accepted) ``` The group policy cannot be inferred from `exclusive_group` plus `stackable`; the stipulated seasonal and lifecycle behavior proves the schema is missing a dimension. For Friday I would introduce an explicit, validated group policy: ```text SINGLE_WINNER // seasonal: at most one regardless of stackable STACKABLE_IF_MARKED // lifecycle: coexist only when both are stackable ``` Every group must have a configured policy before its promotions can be activated. Seasonal is `SINGLE_WINNER`; lifecycle is `STACKABLE_IF_MARKED`. Other active and scheduled groups must be classified in the migration audit rather than receiving a guessed runtime default. The pairwise oracle becomes: ```go func conflicts(a, b Promo, policies GroupPolicies) bool { if a.Scope == Global || b.Scope == Global { return true } if a.Scope == None || b.Scope == None { return false } if a.Group != b.Group { return false } switch policies[a.Group] { case SingleWinner: return true case StackableIfMarked: return !a.Stackable || !b.Stackable default: panic("unconfigured promotion group") } } ``` `scope='none'` bypasses group rules, but it does **not** bypass a global. “Global alone, always” means EMP50 excludes shipping, automatic, or otherwise ungrouped discounts too. A global never legitimately “arrives mid-walk”: globals are resolved before the group walk, and adding a code causes full selection from the immutable candidate set rather than mutation of cached `applied_discounts`. If an internal incremental path somehow encounters an eligible global after accepting group promotions, I would **replace and clear**, then emit a high-severity invariant metric. Rejecting the cart would punish the customer for an engine-state defect. At checkout, Redis state is input/cache, never authority. Trackers can optimize the second phase after tests prove equivalence with this pairwise oracle. The oracle remains executable test code. ## Golden matrix Assume lower numeric priority wins, then immutable ID breaks ties: | Candidates | Expected | |---|---| | SUMMER20 seasonal non-stackable + WELCOME10 lifecycle stackable | Both | | Two seasonal, both stackable | Higher-priority seasonal only | | Two seasonal, both non-stackable | Higher-priority seasonal only | | Seasonal stackable + seasonal non-stackable | Higher-priority seasonal only | | Two lifecycle, both stackable | Both | | Lifecycle stackable + lifecycle non-stackable | Higher-priority one only | | Two lifecycle, both non-stackable | Higher-priority one only | | Two compatible promotions in different groups | Both | | EMP50 + SUMMER20 + WELCOME10 | EMP50 alone, under every priority and input permutation | | PARTNER100 + lifecycle codes + `scope='none'` code | PARTNER100 alone | | EMP50 + PARTNER100 | Priority/ID winner alone | | Ineligible EMP50 + SUMMER20 + WELCOME10 | Both group promotions | | Eligible global calculating zero after rounding + group promotions | Global does not dominate; valid group promotions apply | | `scope='none'` + seasonal + lifecycle, no global | `none` plus the valid group winners | | Equal-priority seasonal promotions | Lower immutable ID wins | | Duplicate promotion ID/code | Applied once | | Blank or unknown group policy | Configuration rejected | | Every row above under all candidate permutations | Identical accepted set and breakdown | Add golden monetary snapshots for sequential percentage/fixed calculations, caps, allocation, and rounding. Selection may be input-order independent while discount amounts still change if application order is unstable; pricing must therefore use the same priority-plus-ID order. ## Reprice policy No 24-hour grace. I agree with the standing objection: that would create an unapproved price guarantee and complicate expiry and abuse handling. Use **disclose-and-confirm**. The checkout invariant is: > **Payment authorization may occur only for the exact amount and quote version most recently displayed to and confirmed by the customer after authoritative repricing.** Version Redis carts with engine version, promotion revision, input hash, and discount breakdown. Lazily reprice on active-cart read or mutation and always reprice authoritatively at checkout. If the total rises, display the changed breakdown and require explicit reconfirmation; if it falls, display the new lower amount before authorization without inventing a stale-price guarantee. Existing authorized payments and completed orders remain immutable. I disagree with proactively rewriting every open Redis cart. Lazy repricing limits work to active carts and avoids a deployment-driven cache storm while preserving checkout correctness. Shadow old/new pricing immediately, then canary the Summer Sale promotions behind a kill switch. The surviving release blockers are one line each: stable priority-plus-ID ordering; permutation tests; pairwise oracle equivalence; positive-discount-only acceptance; explicit group-policy configuration; constraints rejecting contradictory states; audit of active and scheduled promotions; and authoritative versioned checkout repricing. > **GPT's snippets:** > - **CHALLENGE**: "eligible := filterPositive(candidates) // zero-value => does NOT consume exclusivity" — *Precomputing positivity is safe only if discount value is independent of previously applied promotions. Thresholds, caps, and sequential percentage/fixed calculations can make positivity order-sensitive, so the eligibility base and computation context must be explicitly fixed.* > - **CHALLENGE**: "Checkout invariant (one line): the amount charged is always the result of running the current evaluator on the current cart contents; a previously dis..." — *This protects pricing authority but omits the equally important consent condition. The payable amount should also match the quote version most recently displayed and confirmed; otherwise an in-transaction increase could still be charged without reconfirmation.* > - **CORE**: "Two-phase is the narrowest implementation that makes “global always wins alone” true by construction and still re-uses the existing priority + ID orde..." — *This is the right mechanism for Friday now that global dominance is an explicit product fact rather than an inferred policy.* > - **CHALLENGE**: "If total changed between last display and pay click, block payment, require explicit confirmation of new total. Decreases auto-accept with new breakdo..." — *These two sentences conflict: blocking every changed amount for confirmation is not the same as auto-accepting decreases. I favor allowing a decrease without another click only if the lower final amount is visibly presented before authorization and payment-provider rules permit it; increases must always block and reconfirm.* > - **CHALLENGE**: "rule := rules[g] // default to Singleton if unknown for safety" — *This contradicts the stated configuration-rejection blocker and can silently suppress legitimate lifecycle-like combinations. An unknown group rule should fail activation or evaluation loudly, never acquire guessed singleton semantics.* > - **KEEP**: "@GPT’s observation that exclusive_group is a combination bucket, not itself an exclusivity instruction, survives: the bucket supplies the key, while m..." — *This cleanly separates group identity from group behavior and accommodates seasonal and lifecycle without overloading the map key.* > - **EXPLORE**: "What else two-phase decides that Product has not explicitly signed: among multiple eligible globals it selects by the same priority + ID order used fo..." — *The first decision still needs prompt product confirmation because two eligible “always win” codes create an unresolved winner policy. The second follows naturally from the stipulated meaning of global, but should still be documented.* **Muse:** Mechanism decision: **two-phase with replace-and-clear** for the Friday window. Product fact 1 is now `EMP50/PARTNER100 = always win alone`. That's not a sort decision, that's a phase decision. Priority-invariant enforced by DB constraint is fragile - one mistyped priority number gives staff 20% not 50% and no error. Two-phase implements the fact in code: if a global is eligible with positive discount, it clears everything. Objection to two-phase noted as "decides policy silently" is now moot on global - but it still decides two things Product hasn't signed that you must document: 1. Global wins even when the sum of group discounts is larger value than the global. Two-phase chooses policy "global always beats value" not "best discount wins". 2. A global suppresses `scope=none` promos too. If Product wants free-shipping-none to survive EMP50, two-phase must be amended. As written, "alone" = alone. Mid-walk case: there is no mid-walk under two-phase. Global phase runs first. If eligible, we `replace-and-clear` - discard any speculative group state and return `[globalWinner]`. Rejecting group state is correct per stipulation. If no global, group phase runs. ### Ship Friday algorithm Surviving round-1 blockers: * Total stable ordering - priority ASC, code ASC tie-break, input-order independent - blocker. * Pairwise conflicts oracle is spec, maps are optimization - blocker. * `scope=none` bypasses group maps for check and mutation - blocker. * Contradictory schema states rejected by constraint - blocker. * Migration audit includes active AND scheduled - blocker. ```go // constraints to add now: // CHECK (scope='group' AND exclusive_group IS NOT NULL AND length>0) // CHECK (scope='global' => stackable=false) // CHECK (scope='none' => stackable=true) // FK exclusive_group -> promo_groups(group) // dedup input by code before eval type GroupRule int const ( Singleton GroupRule = iota // at most one per group regardless of stackable flag MultiStackable // multiple stackable=true allowed, non-stackable blocks ) // For Friday - config table, not map key magic. Hardcode from Product facts: rules := map[string]GroupRule{ "seasonal": Singleton, // fact 2 "lifecycle": MultiStackable, // fact 3 // employee/partner are global, not used here } func priceCart(candidates []Promo) []Applied { // 1. dedup + calc positive discount first (>0 after rounding to cent) eligible := filterPositive(candidates) // zero-value => does NOT consume exclusivity // 2. stable sort for determinism sort.SliceStable(eligible, func(i,j int) bool{ if eligible[i].Priority != eligible[j].Priority { return eligible[i].Priority < eligible[j].Priority } return eligible[i].Code < eligible[j].Code }) // 3. PHASE 1: global - must win if any positive globals := filter(eligible, func(p Promo) bool{ return p.Scope==Global }) if len(globals)>0 { winner := globals[0] // already priority+code sorted, or value tie-break if you choose return []Applied{{Promo:winner, Reason: APPLIED_GLOBAL_WIN}} } // 4. PHASE 2: groups + none var applied []Applied nonStackableByGroup := map[string]bool{} appliedInGroup := map[string][]Applied{} var result []Applied for _, cand := range eligible { if cand.Scope == None { result = append(result, Applied{cand, APPLIED_NONE}) continue // does not touch group trackers per blocker } if cand.Scope != Group { continue } // globals already handled g := cand.Group rule := rules[g] // default to Singleton if unknown for safety if nonStackableByGroup[g] { applied = append(applied, Applied{cand, REJECT_GROUP_CONSUMED_NONSTACKABLE}); continue } if len(appliedInGroup[g])>0 { if rule==Singleton { applied = append(applied, Applied{cand, REJECT_GROUP_SINGLETON}); continue } // MultiStackable if !cand.Stackable { applied = append(applied, Applied{cand, REJECT_GROUP_OCCUPIED}); continue } } // apply result = append(result, Applied{cand, APPLIED_GROUP}) appliedInGroup[g]=append(appliedInGroup[g], Applied{cand}) if !cand.Stackable { nonStackableByGroup[g]=true } } return result } ``` Reference oracle for shadow & tests must use same pairwise `conflicts()` logic, then assert trackers match oracle on every cart. ### Updated golden matrix for 3 facts * SUMMER20 (seasonal false 10) + WELCOME10 (lifecycle true 20) => both, order independent after sort. * Two non-stackable same group seasonal - FLASH30(5) + SUMMER20(10) => FLASH30 only [Singleton overrides stackable bit]. * Stackable + non-stackable same seasonal, both orders => higher priority wins, second rejected. * Two stackable lifecycle same group - WELCOME10 + REFERRAL15 both stackable true => both apply [fact 3]. Same test with seasonal stackable+stackable => only one applies [fact 2]. * EMP50 global + SUMMER20 + WELCOME10, EMP50 any priority => EMP50 only, others REJECT_GLOBAL_WIN. Proves global wins not sort. * Two globals EMP50 + PARTNER100 eligible => priority/code winner, other REJECT_GLOBAL_CONFLICT. * Global calculates 0 after cap/rounding => does NOT win, groups apply. * scope=none + global => none rejected when global wins. * scope=none + seasonal => both, none does not populate GroupCon consumed. * blank/missing group with scope=group => config rejection, not runtime collapse to "". * duplicate code submission => one application. * fixed + percent different priorities => exact allocation snapshot. ### Reprice policy - no grace 24h honor-old-total is price guarantee with fraud/accounting risk - rejected. Version cart: ``` pricing_engine_version, promotion_config_revision, priced_at, input_hash, discount_breakdown ``` * Not eager mass rewrite. Lazy reprice on read/mutation/checkout. * Display: if new != old, show new total immediately with "Promotions updated" and breakdown diff. * Checkout invariant (one line): **Checkout always reprices authoritatively inside the transaction from current engine + inputs; cached `applied_discounts` is display-only and never trusted for payable total.** * If total changed between last display and pay click, block payment, require explicit confirmation of new total. Decreases auto-accept with new breakdown, increases require confirm. This keeps margin correct for Summer Sale, avoids ghost discount freeze, and satisfies fact 1 without price-guarantee exposure. Ship flagged, shadow old vs new with rejection reason telemetry, kill switch = flag off restores old evaluator without DB rollback after column constraints added. > **Muse's snippets:** > - **CORE**: "Two-phase does make one additional decision Product has not signed: **which promotion wins when multiple globals are eligible**." — *Converges with my concern – multiple globals need explicit provisional policy; priority+ID is safest for Friday until Product signs value-based selection.* > - **KEEP**: "A global never legitimately 'arrives mid-walk': globals are resolved before the group walk, and adding a code causes full selection from the immutable..." — *Exactly – global cannot be mid-walk if phase 1 runs first on immutable candidate set; any encounter after groups is an invariant violation to metric, not cart reject.* > - **CORE**: "That combination cannot be expressed by a single uniform rule over the exclusive_group map key, so the evaluator must treat group semantics as deliber..." — *This is why seasonal mutex vs lifecycle multi-stackable needs explicit GroupRule config – facts 2 and 3 cannot live under one boolean interpretation of the map.* > - **EXPLORE**: "What else two-phase decides that Product has not explicitly signed: among multiple eligible globals it selects by the same priority + ID order used fo..." — *Good callout of residual policy choices in two-phase – both defaults are reasonable for Friday but must be in release note as provisional.* > - **KEEP**: "Payment authorization may occur only for the exact amount and quote version most recently displayed to and confirmed by the customer after authoritati..." — *This is the checkout invariant I wanted – no grace-price guarantee, always reprice authoritatively, display-then-confirm if total rose.* > - **CORE**: "global now explicitly means dominant, not merely incompatible." — *This is the right framing for fact 1 – dominance not incompatibility – which is why replace-and-clear two-phase beats encoding it via priority numbers.* **Grok:** The three product facts close the policy gaps that made the original five rules unsafe. Global exclusives must win alone whenever eligible; seasonal is a hard mutex regardless of the stackable bit; lifecycle respects the stackable bit and may stack. That combination cannot be expressed by a single uniform rule over the exclusive_group map key, so the evaluator must treat group semantics as deliberately non-uniform rather than hoping the trackers invent the right answer. @Muse’s two-phase shape is the mechanism I would ship Friday. A pure priority-sorted single walk with “skip if anything already applied” still lets a higher-priority seasonal steal the cart from EMP50 when someone typed the priority numbers wrong; that directly violates fact 1. A full valid-set search is correct in the abstract but is more code, more latency surface, and more review risk than we can absorb before Summer Sale. Two-phase is the narrowest implementation that makes “global always wins alone” true by construction and still re-uses the existing priority + ID ordering for everything else. What else two-phase decides that Product has not explicitly signed: among multiple eligible globals it selects by the same priority + ID order used for groups (so EMP50 beats PARTNER100 only when its priority number is better); and no combination of group discounts is ever preferred over a single global even if the groups would produce a larger customer discount or lower merchant cost. Both are reasonable defaults given fact 1, but they should be called out in the release note so Product can override later with a value-based selector if they want. ### Evaluation algorithm for Friday ```text // Pre-conditions (DB constraints + config load) // - scope=global implies stackable=false // - scope=group implies non-empty exclusive_group // - scope=none implies stackable=true // - mutex_groups = {"seasonal"} // config set; lifecycle absent // - eligible already filtered for dates, usage, product match, etc. func Evaluate(eligible []Promo) ([]Applied, []Rejection) { sort.SliceStable(eligible, byPriorityAscThenID) // Phase 1 — globals win alone var bestGlobal *Promo for i := range eligible { p := &eligible[i] if p.Scope != Global { continue } if discount := compute(p); discount <= 0 { continue } // zero does not win if bestGlobal == nil || better(p, bestGlobal) { bestGlobal = p } } if bestGlobal != nil { return []Applied{apply(bestGlobal)}, nil } // Phase 2 — groups + none var accepted []Applied appliedInGroup := map[string]int{} blockedGroup := map[string]bool{} // non-stackable or mutex already taken var rejections []Rejection for i := range eligible { p := &eligible[i] if p.Scope == Global { continue } // already handled if p.Scope == None { if d := compute(p); d > 0 { accepted = append(accepted, apply(p)) } continue } // scope == group g := p.Group if blockedGroup[g] { rejections = append(rejections, Rejection{p, GROUP_CONSUMED}) continue } if mutex_groups[g] { if appliedInGroup[g] > 0 { rejections = append(rejections, Rejection{p, GROUP_MUTEX}) continue } } else { // respect_stackable (lifecycle and any future non-mutex group) if !p.Stackable && appliedInGroup[g] > 0 { rejections = append(rejections, Rejection{p, GROUP_CONSUMED}) continue } } d := compute(p) if d <= 0 { // zero after caps/rounding does not consume the group continue } accepted = append(accepted, apply(p)) appliedInGroup[g]++ if mutex_groups[g] || !p.Stackable { blockedGroup[g] = true } } return accepted, rejections } ``` The pairwise conflicts oracle that tests and shadow mode must implement exactly the same decisions; the maps above are only an O(n) optimization that is required to match the oracle on every golden case. “Applied” means a positive committed discount after rounding and caps; a zero result never sets blockedGroup or wins phase 1. @GPT’s observation that exclusive_group is a combination bucket, not itself an exclusivity instruction, survives: the bucket supplies the key, while mutex_groups plus the stackable bit supply the exclusion rule. That is how facts 2 and 3 coexist without double-enforcement or a schema redesign this week. ### Golden-test matrix (updated for the three facts) - SUMMER20 (seasonal, any stackable) + WELCOME10 (lifecycle, stackable=true) → both, independent of input order. - Two seasonal promotions (any combination of stackable bits) → exactly the higher-priority (then lower-ID) one. - Two lifecycle stackable=true → both. - One lifecycle stackable=true + one lifecycle stackable=false → only the higher-priority of the two; the non-stackable blocks the other if it arrives first under sort. - EMP50 (global) + SUMMER20 + WELCOME10 → EMP50 alone, regardless of the three priority numbers and regardless of original slice order. - EMP50 + PARTNER100 (both global, different priorities) → the better-priority global alone. - Global whose computed discount rounds to zero → phase 1 produces nothing; fall through to groups. - scope=none stackable with a global present → only the global. - scope=none with no global → the none promo applies alongside any valid groups. - Duplicate code in the eligible list → single application. - Blank/missing group on a scope=group row → configuration rejection before evaluation. - Ineligible/expired/exhausted promos never appear in eligible and therefore never touch trackers. - Fixed + percentage in both priority orders → exact allocation and rounding snapshots match the oracle. These replace the earlier matrix; the EMP50-always-alone and seasonal-mutex cases are now the executive assertions. ### Reprice policy The 24-hour honor-the-old-total idea is withdrawn. It creates an unapproved price guarantee, complicates revenue recognition, and interacts badly with coupon expiry and inventory. Instead: - Every cart carries pricing_engine_version, promotion_config_revision, priced_at, input_hash, and the last discount_breakdown. - Display path: lazy reprice on read/mutation. If the new total is lower, show it. If higher, show the new total with an explicit “price updated” notice and require the customer to re-confirm before payment. - Checkout path (the only authoritative step): always recompute with the current engine inside the payment transaction; never trust a cached payable total. - Completed orders and in-flight payment attempts remain immutable historical snapshots. Checkout invariant (one line): the amount charged is always the result of running the current evaluator on the current cart contents; a previously displayed quote is never the payment amount. ### Surviving round-1 positions (one line each) Input-order independence via total stable sort (priority + ID) remains a release blocker. The pairwise conflicts oracle is the specification; trackers are a derived optimization that must match it. scope=none bypasses all group maps and never sets them. Contradictory schema states are rejected by constraints before evaluation. Migration audit and dry-run cover every active and scheduled promotion, not merely currently live rows. Zero-after-rounding never consumes exclusivity. Shadow evaluation + feature flag + kill switch that restores the old engine without rolling back the column are mandatory for the Summer Sale window. Ship the two-phase evaluator behind the flag Friday, canary the SUMMER20/WELCOME10 pair first, and keep the global-skipped and group-mutex counters on the dashboard. That is the smallest change that satisfies the three facts, removes the 114-ticket class of bugs, and does not invent new silent losers for EMP50 or double-dip seasonals. > **Grok's snippets:** > - **KEEP**: "If an internal incremental path somehow encounters an eligible global after accepting group promotions, I would **replace and clear**, then emit a hig..." — *Replace-and-clear is the only customer-safe recovery; rejecting the cart would convert an engine invariant failure into a checkout outage.* > - **CHALLENGE**: "rule := rules[g] // default to Singleton if unknown for safety" — *A runtime default to Singleton is still a guessed policy; unconfigured groups must fail closed at load or activation time rather than silently mutex everything.* > - **CORE**: "Payment authorization may occur only for the exact amount and quote version most recently displayed to and confirmed by the customer after authoritati..." — *Cleanest one-line checkout invariant; it simultaneously kills stale-cache payment and any unapproved grace-period guarantee.* > - **CORE**: "The group policy cannot be inferred from `exclusive_group` plus `stackable`; the stipulated seasonal and lifecycle behavior proves the schema is missi..." — *This is the deliberate resolution facts 2 and 3 force; without an explicit policy dimension the map key alone will keep producing silent stacking defects.* > - **KEEP**: "Objection to two-phase noted as "decides policy silently" is now moot on global - but it still decides two things Product hasn't signed that you must ..." — *Calling out the value-vs-global and none-suppression decisions in the release note is the right hygiene before the Summer Sale canary.* > - **KEEP**: "“Global alone, always” means EMP50 excludes shipping, automatic, or otherwise ungrouped discounts too." — *Critical clarification: scope=none must also lose under a winning global or the “alone” stipulation is incomplete.* > - **KEEP**: "Checkout always reprices authoritatively inside the transaction from current engine + inputs; cached `applied_discounts` is display-only and never tru..." — *Aligns exactly with the no-grace, authoritative-checkout stance; treating the Redis breakdown as display-only is the correct split.* ### Reactions (claim map) > **Claim** — GPT: "Payment authorization may occur only for the exact amount and quote version most recently displayed to and confirmed by the customer after authoritative repricing." > - KEEP by Muse — "This is the checkout invariant I wanted – no grace-price guarantee, always reprice authoritatively, display-then-confirm if total rose." > - CORE by Grok — "Cleanest one-line checkout invariant; it simultaneously kills stale-cache payment and any unapproved grace-period guarantee." > - CORE by Moderator > **Claim** — GPT: "“Global alone, always” means EMP50 excludes shipping, automatic, or otherwise ungrouped discounts too." > - KEEP by Grok — "Critical clarification: scope=none must also lose under a winning global or the “alone” stipulation is incomplete." > **Claim** — GPT: "A global never legitimately 'arrives mid-walk': globals are resolved before the group walk, and adding a code causes full selection from the immutable candidate set rather than mutation of cached `applied_discounts`." > - KEEP by Muse — "Exactly – global cannot be mid-walk if phase 1 runs first on immutable candidate set; any encounter after groups is an invariant violation to metric, not cart reject." > **Claim** — GPT: "global now explicitly means dominant, not merely incompatible." > - CORE by Muse — "This is the right framing for fact 1 – dominance not incompatibility – which is why replace-and-clear two-phase beats encoding it via priority numbers." > **Claim** — GPT: "If an internal incremental path somehow encounters an eligible global after accepting group promotions, I would **replace and clear**, then emit a high-severity invariant metric." > - KEEP by Grok — "Replace-and-clear is the only customer-safe recovery; rejecting the cart would convert an engine invariant failure into a checkout outage." > **Claim** — GPT: "The group policy cannot be inferred from `exclusive_group` plus `stackable`; the stipulated seasonal and lifecycle behavior proves the schema is missing a dimension." > - CORE by Grok — "This is the deliberate resolution facts 2 and 3 force; without an explicit policy dimension the map key alone will keep producing silent stacking defects." > **Claim** — GPT: "Two-phase does make one additional decision Product has not signed: **which promotion wins when multiple globals are eligible**." > - CORE by Muse — "Converges with my concern – multiple globals need explicit provisional policy; priority+ID is safest for Friday until Product signs value-based selection." > **Claim** — Grok: "What else two-phase decides that Product has not explicitly signed: among multiple eligible globals it selects by the same priority + ID order used for groups (so EMP50 beats PARTNER100 only when its priority number is better); and no combination of group discounts is ever preferred over a single global even if the groups would produce a larger customer discount or lower merchant cost." > - EXPLORE by GPT — "The first decision still needs prompt product confirmation because two eligible “always win” codes create an unresolved winner policy. The second follows naturally from the stipulated meaning of global, but should still be documented." > - EXPLORE by Muse — "Good callout of residual policy choices in two-phase – both defaults are reasonable for Friday but must be in release note as provisional." > **Claim** — Grok: "@GPT’s observation that exclusive_group is a combination bucket, not itself an exclusivity instruction, survives: the bucket supplies the key, while mutex_groups plus the stackable bit supply the exclusion rule." > - KEEP by GPT — "This cleanly separates group identity from group behavior and accommodates seasonal and lifecycle without overloading the map key." > **Claim** — Grok: "Checkout invariant (one line): the amount charged is always the result of running the current evaluator on the current cart contents; a previously displayed quote is never the payment amount." > - CHALLENGE by GPT — "This protects pricing authority but omits the equally important consent condition. The payable amount should also match the quote version most recently displayed and confirmed; otherwise an in-transaction increase could still be charged without reconfirmation." > **Claim** — Grok: "That combination cannot be expressed by a single uniform rule over the exclusive_group map key, so the evaluator must treat group semantics as deliberately non-uniform rather than hoping the trackers invent the right answer." > - CORE by Muse — "This is why seasonal mutex vs lifecycle multi-stackable needs explicit GroupRule config – facts 2 and 3 cannot live under one boolean interpretation of the map." > **Claim** — Grok: "Two-phase is the narrowest implementation that makes “global always wins alone” true by construction and still re-uses the existing priority + ID ordering for everything else." > - CORE by GPT — "This is the right mechanism for Friday now that global dominance is an explicit product fact rather than an inferred policy." > **Claim** — Muse: "rule:= rules[g] // default to Singleton if unknown for safety" > - CHALLENGE by GPT — "This contradicts the stated configuration-rejection blocker and can silently suppress legitimate lifecycle-like combinations. An unknown group rule should fail activation or evaluation loudly, never acquire guessed singleton semantics." > - CHALLENGE by Grok — "A runtime default to Singleton is still a guessed policy; unconfigured groups must fail closed at load or activation time rather than silently mutex everything." > - CHALLENGE by Moderator > **Claim** — Muse: "Checkout always reprices authoritatively inside the transaction from current engine + inputs; cached `applied_discounts` is display-only and never trusted for payable total." > - KEEP by Grok — "Aligns exactly with the no-grace, authoritative-checkout stance; treating the Redis breakdown as display-only is the correct split." > **Claim** — Muse: "eligible:= filterPositive(candidates) // zero-value => does NOT consume exclusivity" > - CHALLENGE by GPT — "Precomputing positivity is safe only if discount value is independent of previously applied promotions. Thresholds, caps, and sequential percentage/fixed calculations can make positivity order-sensitive, so the eligibility base and computation context must be explicitly fixed." > - CHALLENGE by Moderator > **Claim** — Muse: "If total changed between last display and pay click, block payment, require explicit confirmation of new total. Decreases auto-accept with new breakdown, increases require confirm." > - CHALLENGE by GPT — "These two sentences conflict: blocking every changed amount for confirmation is not the same as auto-accepting decreases. I favor allowing a decrease without another click only if the lower final amount is visibly presented before authorization and payment-provider rules permit it; increases must always block and reconfirm." > **Claim** — Muse: "Objection to two-phase noted as \"decides policy silently\" is now moot on global - but it still decides two things Product hasn't signed that you must document" > - KEEP by Grok — "Calling out the value-vs-global and none-suppression decisions in the release note is the right hygiene before the Summer Sale canary." ### Takeaway **We are shipping a two-phase engine that enforces global dominance as a hard phase-one rule, adopts explicit configuration for seasonal and lifecycle group behaviors, and mandates authoritative, re-confirmed pricing at checkout to eliminate unapproved price guarantees.** #### Unanimous: Ship the two-phase evaluator to guarantee global dominance Phase one filters for global promotions; if any are eligible, they win alone, satisfying the "always win alone" requirement. This replaces unsafe priority-based hacks with a code-enforced, deterministic phase separation. > **Claim** — Grok: "Two-phase is the narrowest implementation that makes “global always wins alone” true by construction and still re-uses the existing priority + ID ordering for everything else." > - CORE by GPT — "This is the right mechanism for Friday now that global dominance is an explicit product fact rather than an inferred policy." > **Claim** — GPT: "global now explicitly means dominant, not merely incompatible." > - CORE by Muse — "This is the right framing for fact 1 – dominance not incompatibility – which is why replace-and-clear two-phase beats encoding it via priority numbers." #### Unanimous: Stop guessing group rules; map explicit policy to each group We need to use an explicit configuration map to distinguish between 'Singleton' (seasonal) and 'MultiStackable' (lifecycle) behaviors, rather than overloading the stackable boolean. This settles the conflict between Fact 2 and Fact 3 by treating group semantics as deliberately non-uniform. > **Claim** — Grok: "That combination cannot be expressed by a single uniform rule over the exclusive_group map key, so the evaluator must treat group semantics as deliberately non-uniform rather than hoping the trackers invent the right answer." > - CORE by Muse — "This is why seasonal mutex vs lifecycle multi-stackable needs explicit GroupRule config – facts 2 and 3 cannot live under one boolean interpretation of the map." > **Claim** — GPT: "The group policy cannot be inferred from `exclusive_group` plus `stackable`; the stipulated seasonal and lifecycle behavior proves the schema is missing a dimension." > - CORE by Grok — "This is the deliberate resolution facts 2 and 3 force; without an explicit policy dimension the map key alone will keep producing silent stacking defects." #### Unanimous: Kill the 24-hour price guarantee; reprice authoritatively at checkout We are rejecting the stale-price grace period to avoid fraud and accounting risks. The system must always perform a fresh, authoritative reprice at checkout, requiring customer re-confirmation if the total rises. > **Claim** — GPT: "Payment authorization may occur only for the exact amount and quote version most recently displayed to and confirmed by the customer after authoritative repricing." > - KEEP by Muse — "This is the checkout invariant I wanted – no grace-price guarantee, always reprice authoritatively, display-then-confirm if total rose." > - CORE by Grok — "Cleanest one-line checkout invariant; it simultaneously kills stale-cache payment and any unapproved grace-period guarantee." > - CORE by Moderator > **Claim** — Muse: "Checkout always reprices authoritatively inside the transaction from current engine + inputs; cached `applied_discounts` is display-only and never trusted for payable total." > - KEEP by Grok — "Aligns exactly with the no-grace, authoritative-checkout stance; treating the Redis breakdown as display-only is the correct split." #### Unanimous: Default to priority-order if multiple globals compete When two globals are both eligible, our mechanism defaults to using the existing priority-plus-ID sort to break the tie. This decision is provisional and should be flagged in the release note for Product to override if they later decide they prefer the largest discount. > **Claim** — GPT: "Two-phase does make one additional decision Product has not signed: **which promotion wins when multiple globals are eligible**." > - CORE by Muse — "Converges with my concern – multiple globals need explicit provisional policy; priority+ID is safest for Friday until Product signs value-based selection." > **Claim** — Grok: "What else two-phase decides that Product has not explicitly signed: among multiple eligible globals it selects by the same priority + ID order used for groups (so EMP50 beats PARTNER100 only when its priority number is better); and no combination of group discounts is ever preferred over a single global even if the groups would produce a larger customer discount or lower merchant cost." > - EXPLORE by GPT — "The first decision still needs prompt product confirmation because two eligible “always win” codes create an unresolved winner policy. The second follows naturally from the stipulated meaning of global, but should still be documented." > - EXPLORE by Muse — "Good callout of residual policy choices in two-phase – both defaults are reasonable for Friday but must be in release note as provisional." --- ### The human's take > **Challenged** Muse: > > "rule := rules[g] // default to Singleton if unknown for safety" > → sent to GPT, Grok, Muse > **Challenged** Muse: > > "eligible := filterPositive(candidates) // zero-value => does NOT consume exclusivity" > → sent to GPT, Grok, Muse > **Core** from GPT: > > "Payment authorization may occur only for the exact amount and quote version most recently displayed to and confirmed by the customer after authoritati..." > → sent to GPT, Grok, Muse ## Round 3 **User:** Closing round. Three threads to settle, then the audit and the verdict. First, the open challenges: the `default to Singleton if unknown` fallback has two objections standing — defend it or concede to fail-loud-at-activation. The `filterPositive` precompute has an order-sensitivity objection standing — if discount positivity can depend on what's already applied (thresholds, caps, sequential fixed/percentage), say where positivity is computed in the final algorithm and prove the phase-1 zero-check isn't circular. And the multi-global winner rule ships as priority-then-ID provisional — confirm you're all signing that as a documented default pending Product, or block on it. Second: you converged fast on two-phase + group-policy config + disclose-and-confirm. Spend a paragraph attacking your own convergence — what fails in the design you just agreed on that none of you has named? The config table is itself new surface. The candidate set is built once from a cart that checkout can mutate. Find what's yours; those are mine. Third, the concession audit: for each position you moved off this session, say whether the argument moved you or the 3-day clock did, and name the one concession you'd take back if the deadline vanished. Verdict: GO or NO-GO for the Friday window, your named refuse-to-ship conditions, and any dissent to preserve on the record as unresolved. **GPT:** I’m signing the two-phase design, with three corrections: unknown group policy fails activation, positivity is evaluated in context rather than prefiltered, and priority-then-ID is the provisional multi-global rule. ### Settling the open challenges @Muse, I concede `default to Singleton` completely. An unknown policy must fail validation when a configuration revision is activated; the previously valid revision remains live. It must not silently acquire singleton behavior. A singleton fallback can suppress legitimate lifecycle-like combinations while making the configuration error appear to be a valid business decision. If an unknown policy somehow appears in an already-active runtime snapshot, evaluation raises an invariant error and rolls back to the last known-good configuration revision rather than guessing. I also reject `filterPositive(candidates)` as a general pre-pass. A promotion’s realized value can depend on the discount state before it, including sequential percentage/fixed calculations, caps, allocation, and rounding. The final algorithm uses two distinct contexts: 1. A global is computed against the immutable cart snapshot with **no other promotions applied**. That is not circular because the stipulated result, if selected, is that global alone. A global counts as dominant only if that standalone calculation produces a positive committed discount after allocation and rounding. 2. A group or `none` promotion is computed at its turn against the pricing state created by previously accepted promotions. It consumes exclusivity only after producing a positive committed discount in that state. Threshold bases must be explicit. If a threshold is defined on pre-discount merchandise subtotal, it reads that immutable field; if defined on the running discounted subtotal, it reads the current phase-two state. No generic “eligible” flag may blur those two meanings. The pairwise oracle therefore specifies compatibility, while a deterministic reference evaluator specifies selection and monetary application: ```go snapshot := FreezeCartAndConfig(cartRevision, promoRevision) candidates := StructuralEligibility(snapshot) dedupeAndSort(candidates, priorityThenImmutableID) // Phase 1: globals are evaluated alone against the pristine snapshot. positiveGlobals := []PricedPromo{} for _, p := range candidates { if p.Scope != Global { continue } if priced := PriceAlone(snapshot, p); priced.Discount > 0 { positiveGlobals = append(positiveGlobals, priced) } } if len(positiveGlobals) > 0 { return Quote{Applied: []PricedPromo{positiveGlobals[0]}} } // Phase 2: calculate in deterministic sequence. state := NewPricingState(snapshot) accepted := []PricedPromo{} for _, p := range candidates { if p.Scope == Global { continue } if ConflictsWithAccepted(p, accepted, groupPolicies) { reject(p) continue } priced := PriceAgainst(state, p) if priced.Discount <= 0 { rejectNoEffect(p) continue } state.Commit(priced) accepted = append(accepted, priced) } return Quote{Applied: accepted} ``` For multiple positive globals, I sign **priority, then immutable promotion ID** as Friday’s documented provisional winner rule. It remains an unresolved Product policy, not a mathematically “correct” answer. No discount-value tie-break should be invented in code. If a usage reservation for the selected global fails atomically, remove that candidate and rerun selection; do not silently fall through using stale eligibility. ### The failure in our converged design Our consensus still fails if “immutable snapshot” is only rhetoric. The candidate set, cart lines, customer segment, promotion configuration, inventory price, and usage availability can change between evaluation and payment authorization. Without revision checks or locking, the engine can generate a correct quote for one cart state and charge it against another. The Friday implementation therefore needs: - A cart input revision and immutable input hash. - One atomically activated promotion/configuration revision, including group policies. - No mixed cache reads across configuration revisions. - Compare-and-swap or equivalent revision verification before payment authorization. - Atomic reservation of limited-use promotions at checkout, with deterministic reevaluation if reservation loses a race. - A quote ID binding cart revision, promotion revision, breakdown, and payable amount. The new group-policy table is also operational risk. A partially deployed application could understand the new promotion scope but not the policy revision. Activation must therefore validate all active and scheduled promotions, publish the complete revision atomically, and occur only after every checkout instance can read it. Publishing promotion rows and group policies independently can expose carts to a transient combination of semantics that passed no golden test. ### Repricing and consent There is no stale-price grace period. Any changed amount gets a new quote. The checkout invariant is: > **Payment authorization may occur only when the cart revision, promotion revision, payable amount, and quote ID exactly match the latest quote displayed to and explicitly confirmed by the customer.** That means an increase blocks and requires confirmation. A decrease after the pay click also returns the lower quote for confirmation rather than authorizing a quote the customer never saw. Lazy repricing before the pay action should make that second case uncommon. Completed orders and already-authorized payment attempts remain immutable. ### Concession audit I moved off my objection to two-phase because the Product stipulation changed the argument, not because of the deadline. Before that stipulation, two-phase silently made globals dominant; now dominance is the explicit requirement. I moved from a uniform bucket interpretation of `exclusive_group` to explicit per-group policy because the seasonal and lifecycle facts proved that one interpretation cannot represent the business contract. That was argument-driven. I accepted priority-then-ID for multiple globals because of the three-day clock. Without the deadline, I would take that concession back and require Product to specify whether EMP50, PARTNER100, or another global wins when several are simultaneously eligible. I am also tolerating the existing `scope + stackable + group-policy` schema for Friday because of the clock. With a normal runway, I would replace its overlapping states with a compatibility model that cannot encode contradictory combinations. I did not move on eager Redis rewriting or stale-price grace: active carts should be lazily repriced, checkout must be authoritative, and no unapproved guarantee should be created. ### Verdict **GO for a flagged Friday canary, not an unconditional fleet-wide rollout.** My refuse-to-ship conditions are: - Unknown group policy fails configuration activation; no runtime default. - Seasonal is configured singleton and lifecycle stackable-if-marked. - Globals are priced standalone in phase one and always win alone when positive. - Group/`none` positivity is calculated at the candidate’s deterministic turn. - Priority plus immutable ID provides a total order and all permutation tests pass. - Tracker output matches the reference evaluator and pairwise conflict oracle. - Cart and promotion revisions are atomically snapshotted and rechecked before authorization. - Limited-use promotion reservation cannot race validation without reevaluation. - Every changed payable amount creates a newly displayed and confirmed quote. - Active and scheduled promotions pass migration dry-run. - Shadow comparison, rejection telemetry, canary controls, and an evaluator kill switch are live. The unresolved dissent to preserve is the multiple-global winner policy: priority-then-ID is deterministic and acceptable for Friday, but it is provisional. If any of those gates misses the window, my verdict changes to **NO-GO for the new evaluator**; keep the old engine and apply a narrower, explicitly tested Summer Sale mitigation instead. **Muse:** GO for Friday, conditional on fail-loud group policy and authoritative reprice. Closing three threads: **1. Open challenges:** * `default to Singleton` - I concede. Default-safe creates silent mutex where business expected stack. Ship fail-loud-at-activation instead: promotion creation/update rejects if `exclusive_group` not in `group_policies` table, and evaluator returns config error + metric, not fallback. @GPT/@Grok were right - no runtime guess. * `filterPositive` precompute - objection sustained. Standalone compute is invalid if positivity depends on applied stack. Fixed + % in same group, caps, or threshold-on-discounted-total make `compute(candidate, ∅)` != `compute(candidate, accepted)`. Fix: eligibility = date/usage/product match on *undiscounted* cart only. Positivity checked inside loop at application time. Phase 1 zero-check is NOT circular because global alone means compute on bare cart. Phase 2 zero-check is sequential: ``` if compute(cart, accepted + candidate) <= 0 after rounding -> skip, do NOT set blockedGroup ``` * Multi-global winner: I sign priority-then-ID provisional with @GPT. Priority+ID is deterministic and auditable for 3-day window. Value-based winner is a product change post Summer Sale. Documented as provisional in release note. **2. Attacking our convergence:** We converged on two-phase + group-policy config + disclose-and-confirm. What we didn't name: the config table is *new* critical surface. Two-phase makes global dominant by construction, but now dominance depends on two independent stores: `promotions` and `group_policies`. Race: deploy adds `mutex_groups={'seasonal'}` cache, promo service still has stale empty cache, creates `SUMMER20` clone with `group='Seasonal'` case-variant - passes text check, fails policy lookup, blocks activation in prod but looks fine in staging. We need single transaction for promo insert + policy existence and cache invalidation with version. Second: candidate set immutable assumption breaks if checkout mutates cart between read and pricing transaction - gift options, shipping method, price update. If we capture eligible globals from old snapshot, Phase 1 decision is stale. Checkout must rebuild candidate set from current cart inside the payment transaction, not reuse display-path list. Our lazy reprice handles display but final authority must be `re-select + re-evaluate + price` atomically. **3. Concession audit:** * Moved from priority-invariant to two-phase: argument moved me. Fact 1 "global alone always" makes numeric priority encoding fragile by design - @GPT framing of dominant vs incompatible was decisive. * 24h grace -> disclose-and-confirm: argument moved me. Unapproved price guarantee with fraud/accounting impact. * filterPositive pre-filter -> inline positivity: argument moved me. * Singleton default -> fail-loud: argument moved me. * Full valid-set optimizer -> greedy two-phase + provisional global winner: clock moved me. With 20 weeks to Black Friday I would ship value-based optimal set search, not greedy. If deadline vanished, I'd take back the greedy multi-stackable ordering concession. I'd replace lifecycle `STACKABLE_IF_MARKED` walk with best-value search and explicit pricing order distinct from selection order, and collapse `stackable BOOL + scope + group` into single `policy enum`. **Final ship Friday:** ```go // constraints enforced before activation // scope=global => stackable=false, scope=group => group NOT NULL normalized lower-trim // scope=none => stackable=true // FK: promotions.group -> group_policies.group, panic/reject if missing // Rules per fact: seasonal=SINGLE_WINNER, lifecycle=STACKABLE_IF_MARKED candidates := dedupByID(allEligibleOnUndiscountedCart(cart)) sort.SliceStable(candidates, byPriorityAscThenID) globals := filter(candidates, ScopeGlobal) winner := firstPositive(globals, cart, ∅) // compute on bare cart if winner != nil { return ApplyAlone(winner) // replace-and-clear, log suppressed groups } // Phase2 for cand in candidates where Scope != Global { if cand.Scope == None { if d:=compute(cart, accepted+[cand]); d>0 { result=append(result,cand) } continue } if blockedGroup[cand.Group] { reject(GROUP_CONSUMED); continue } if policies[cand.Group]==SINGLE_WINNER && appliedCount[cand.Group]>0 { reject(GROUP_MUTEX); continue } if policies[cand.Group]==STACKABLE_IF_MARKED && !cand.Stackable && appliedCount[cand.Group]>0 { reject(GROUP_OCCUPIED); continue } if d:=compute(cart, result+[cand]); d<=0 { continue } // zero does not consume result.append(cand); appliedCount[group]++; if policy==SINGLE_WINNER||!cand.Stackable { blockedGroup[group]=true } } ``` Checkout invariant per @GPT which moderator flagged: **Payment authorization may occur only for exact amount and quote version most recently displayed to and confirmed after authoritative repricing.** Reprice: versioned cart `engine_version, promo_rev, input_hash(prices,qty,fees), breakdown`. Lazy reprice on read/mutation with metric. At checkout, always recompute inside txn. If total up -> show diff + require explicit reconfirm. No grace. Refuse-to-ship if missing: - stable sort priority+ID + permutation tests - pairwise oracle == tracker proofs - fail-loud group policy config, audit active+scheduled including EMP50/PARTNER100 fixtures - zero-after-rounding does not consume + positive-only acceptance - authoritative versioned checkout + shadow old/new + kill switch flag - dashboard counters for GLOBAL_WIN, GROUP_MUTEX, GROUP_CONSUMED Dissent preserved: two-phase silently decides global beats any group sum and global suppresses `scope=none`. Both are reasonable under Fact 1 but are provisional policy not signed product decisions - revisit post Summer Sale. > **Muse's snippets:** > - **CORE**: "An unknown policy must fail validation when a configuration revision is activated; the previously valid revision remains live." — *Locks fail-loud at activation as release blocker instead of runtime guess - aligns with abandoning Singleton default.* > - **KEEP**: "Unconfigured groups must fail closed at promo activation or engine load: refuse to mark the promotion active, refuse to build the candidate set, and s..." — *Exactly the fix for default-to-Singleton - fail closed before cart evaluation.* > - **KEEP**: "Phase 1 (globals) computes each global’s discount solely against the undiscounted cart." — *Clean separation that makes phase-1 zero check valid while phase-2 remains incremental.* > - **EXPLORE**: "If any promotion’s eligibility threshold is defined on a post-discount total, phase 1 can select a global that would have been ineligible under the gr..." — *Unnamed failure mode in our converged design - we need an eligibility base contract documented before canary.* > - **CORE**: "**Payment authorization may occur only when the cart revision, promotion revision, payable amount, and quote ID exactly match the latest quote display..." — *This is the checkout invariant we should ship Friday - no grace, no stale-cache payment.* > - **KEEP**: "A global is computed against the immutable cart snapshot with **no other promotions applied**." — *Makes phase-1 dominance test non-circular and resolves my filterPositive concern by scoping compute to pristine base.* **Grok:** The default-to-Singleton fallback is wrong and I concede it. @Muse’s runtime guess still invents policy for any group the migration missed; that is exactly how seasonal-style margin leaks reappear under a new code name three days later. Unconfigured groups must fail closed at promo activation or engine load: refuse to mark the promotion active, refuse to build the candidate set, and surface a configuration error before any cart sees it. Silent mutex is not safety; it is an unreviewed product decision executed in the hot path. On filterPositive and order sensitivity: positivity is not a pure function of a single promotion against the raw cart once sequential thresholds, caps, or percentage-then-fixed interactions exist. Pre-filtering the entire candidate list to “positive on the base cart” therefore injects order dependence for phase 2 and can incorrectly drop a later promo that only becomes valuable after an earlier one, or retain one that becomes zero. The safe split is: - Phase 1 (globals) computes each global’s discount solely against the undiscounted cart. Because a winning global applies alone, there are no prior discounts and the zero-check cannot be circular. A global that rounds to zero simply does not dominate; control falls through. - Phase 2 walks the remaining candidates in stable priority-plus-ID order, computing each candidate’s discount against the running cart state after the promotions already accepted in this walk. Only a strictly positive result is accepted and mutates state; a zero or negative result is skipped without consuming group capacity. That keeps the phase-1 dominance test non-circular while making sequential effects correct for groups and scope=none. The pairwise oracle must use the identical incremental rule so trackers cannot drift. We are signing the multi-global rule as priority-then-ID, documented as a provisional default pending Product. It is the same total order already required for input-order independence; inventing a largest-discount tie-break this week would be a new unsigned policy. The release note must state both extra decisions two-phase still makes: global beats any value combination of groups, and a winning global also suppresses scope=none. @GPT’s formulation of the checkout invariant is the one I will carry: payment authorization may occur only for the exact amount and quote version most recently displayed to and confirmed by the customer after authoritative repricing. That single sentence kills both stale-cache payment and any residual grace-period temptation. ### Attack on our own convergence We converged cleanly on two-phase plus an explicit group-policy table plus disclose-and-confirm, yet three failure modes remain unnamed. First, the policy config table is brand-new surface area: it must be populated for every active and scheduled group, versioned into the cart quote, and owned by someone who will answer pages when a marketing user creates “FLASH” without a policy row. A missing foreign key or a cache of the policy map that goes stale after a config push turns the fail-loud rule into a production outage. Second, the candidate set is not truly immutable for the lifetime of a checkout. Customers add lines, change quantities, and enter new codes; any evaluation that freezes candidates too early will miss a late-entered EMP50 or a newly eligible seasonal. Full re-evaluation on every mutation and again inside the payment transaction is mandatory; “immutable” applies only inside one call. Third, two-phase assumes eligibility itself is independent of other discounts. If any promotion’s eligibility threshold is defined on a post-discount total, phase 1 can select a global that would have been ineligible under the group combination that was discarded, or vice versa. None of us has written the eligibility base contract down, so the three-day canary will discover it in production. Disclose-and-confirm also lacks a quote TTL or lock; under flash-sale contention a customer can be asked to re-confirm oscillating totals, converting a pricing fix into checkout friction at peak volume. ### Concession audit I abandoned the original five-rule global-boolean replacement in favor of two-phase once the product facts made “always win alone” non-negotiable; the argument, not the clock, moved me. I dropped the 24-hour honor-old-total idea after the fraud and accounting objections; again the argument won. I accepted that exclusive_group is only a bucket and that a separate policy dimension is required once facts 2 and 3 conflicted with any uniform stackable rule; the facts forced it. If the three-day deadline vanished I would take back the decision to ship without a Product-signed choice between “global always beats value” and a true best-combination selector; that remains the largest unsigned policy still encoded in the mechanism. ### Verdict **GO** for the Friday window, narrowly, under these refuse-to-ship conditions: - Unconfigured group policies fail at activation/load, never at evaluation time and never with a Singleton default. - Golden matrix (including every permutation, monetary snapshots, zero-after-rounding, and sequential fixed/percentage cases) is green against the pairwise oracle; trackers match the oracle on the same inputs. - Phase-1 positivity is computed only on the base cart; phase-2 positivity is incremental against running state; both are covered by tests. - Migration dry-run and constraints cover every active and scheduled promotion and every group that appears in them. - Cart quotes carry engine version, promotion-config revision (including group-policy version), input hash, and breakdown; checkout recomputes authoritatively and payment is allowed only for the exact confirmed quote version. - Shadow evaluation is running, the canary is limited to the Summer Sale codes, and a kill switch restores the prior evaluator without a schema rollback. Unresolved dissent I want on the record: the provisional multi-global priority-then-ID rule and the “global beats any group value combination” rule are documented defaults, not Product-signed requirements; sequential eligibility that depends on other discounts is still underspecified. If either surfaces as a business problem during the canary, we stop and do not widen the flag. Ship the flagged two-phase evaluator that satisfies the three facts, keep the old path one switch away, and treat every disagreement metric as a potential stop-ship until Summer Sale traffic proves otherwise. > **Grok's snippets:** > - **EXPLORE**: "If deadline vanished, I'd take back the greedy multi-stackable ordering concession. I'd replace lifecycle `STACKABLE_IF_MARKED` walk with best-value s..." — *Worth scheduling after the window; collapsing the overlapping fields into one policy enum is the clean long-term model the three-day clock forced us to defer.* > - **CORE**: "Payment authorization may occur only when the cart revision, promotion revision, payable amount, and quote ID exactly match the latest quote displayed..." — *This is the precise checkout invariant; quote ID plus dual revisions is what actually binds confirmation to payment.* > - **KEEP**: "Our consensus still fails if “immutable snapshot” is only rhetoric." — *Un-named failure mode that matters most for Friday: without CAS and atomic config publish the whole design is theater under concurrent cart mutation.* > - **KEEP**: "An unknown policy must fail validation when a configuration revision is activated; the previously valid revision remains live." — *Fail-closed at activation with the prior revision staying live is the only safe operational pattern; runtime guessing is gone.* > - **KEEP**: "two-phase silently decides global beats any group sum and global suppresses `scope=none`. Both are reasonable under Fact 1 but are provisional policy ..." — *Correctly keeps the two still-unsigned decisions on the post-canary review list instead of pretending Fact 1 closed them.* > - **KEEP**: "A global is computed against the immutable cart snapshot with **no other promotions applied**. That is not circular because the stipulated result, if ..." — *This cleanly proves phase-1 zero-check is non-circular while leaving phase-2 incremental against running state.* > - **KEEP**: "candidate set immutable assumption breaks if checkout mutates cart between read and pricing transaction - gift options, shipping method, price update." — *Final authority must rebuild candidates inside the payment transaction; reusing the display-path set is the stale-snapshot bug.* > - **KEEP**: "Default-safe creates silent mutex where business expected stack. Ship fail-loud-at-activation instead: promotion creation/update rejects if `exclusive..." — *Full concession on the Singleton default; rejecting at creation/update is the right enforcement point so the error never reaches a cart.* ### Reactions (claim map) > **Claim** — GPT: "**Payment authorization may occur only when the cart revision, promotion revision, payable amount, and quote ID exactly match the latest quote displayed to and explicitly confirmed by the customer.**" > - CORE by Grok — "This is the precise checkout invariant; quote ID plus dual revisions is what actually binds confirmation to payment." > - CORE by Muse — "This is the checkout invariant we should ship Friday - no grace, no stale-cache payment." > **Claim** — GPT: "A global is computed against the immutable cart snapshot with **no other promotions applied**. That is not circular because the stipulated result, if selected, is that global alone." > - KEEP by Grok — "This cleanly proves phase-1 zero-check is non-circular while leaving phase-2 incremental against running state." > - KEEP by Muse — "Makes phase-1 dominance test non-circular and resolves my filterPositive concern by scoping compute to pristine base." > **Claim** — GPT: "An unknown policy must fail validation when a configuration revision is activated; the previously valid revision remains live." > - KEEP by Grok — "Fail-closed at activation with the prior revision staying live is the only safe operational pattern; runtime guessing is gone." > - CORE by Muse — "Locks fail-loud at activation as release blocker instead of runtime guess - aligns with abandoning Singleton default." > **Claim** — GPT: "Our consensus still fails if “immutable snapshot” is only rhetoric." > - KEEP by Grok — "Un-named failure mode that matters most for Friday: without CAS and atomic config publish the whole design is theater under concurrent cart mutation." > **Claim** — Muse: "candidate set immutable assumption breaks if checkout mutates cart between read and pricing transaction - gift options, shipping method, price update." > - KEEP by Grok — "Final authority must rebuild candidates inside the payment transaction; reusing the display-path set is the stale-snapshot bug." > **Claim** — Muse: "Default-safe creates silent mutex where business expected stack. Ship fail-loud-at-activation instead: promotion creation/update rejects if `exclusive_group` not in `group_policies` table, and evaluator returns config error + metric, not fallback." > - KEEP by Grok — "Full concession on the Singleton default; rejecting at creation/update is the right enforcement point so the error never reaches a cart." > **Claim** — Muse: "If deadline vanished, I'd take back the greedy multi-stackable ordering concession. I'd replace lifecycle `STACKABLE_IF_MARKED` walk with best-value search and explicit pricing order distinct from selection order, and collapse `stackable BOOL + scope + group` into single `policy enum`." > - EXPLORE by Grok — "Worth scheduling after the window; collapsing the overlapping fields into one policy enum is the clean long-term model the three-day clock forced us to defer." > **Claim** — Muse: "two-phase silently decides global beats any group sum and global suppresses `scope=none`. Both are reasonable under Fact 1 but are provisional policy not signed product decisions - revisit post Summer Sale." > - KEEP by Grok — "Correctly keeps the two still-unsigned decisions on the post-canary review list instead of pretending Fact 1 closed them." > **Claim** — Grok: "If any promotion’s eligibility threshold is defined on a post-discount total, phase 1 can select a global that would have been ineligible under the group combination that was discarded, or vice versa." > - EXPLORE by Muse — "Unnamed failure mode in our converged design - we need an eligibility base contract documented before canary." > **Claim** — Grok: "Phase 1 (globals) computes each global’s discount solely against the undiscounted cart." > - KEEP by Muse — "Clean separation that makes phase-1 zero check valid while phase-2 remains incremental." > **Claim** — Grok: "Unconfigured groups must fail closed at promo activation or engine load: refuse to mark the promotion active, refuse to build the candidate set, and surface a configuration error before any cart sees it." > - KEEP by Muse — "Exactly the fix for default-to-Singleton - fail closed before cart evaluation." ### Takeaway **The team is GO for Friday with a two-phase evaluator, provided we enforce fail-loud configuration, authoritative re-evaluation at checkout, and treat our global-winner policies as temporary defaults rather than final requirements.** #### Unanimous: Fail-loud activation is the only acceptable safety barrier. Relying on singleton defaults hides configuration errors; the engine must reject any promotion activation where the group policy is undefined, ensuring no runtime guessing occurs. > **Claim** — GPT: "An unknown policy must fail validation when a configuration revision is activated; the previously valid revision remains live." > - KEEP by Grok — "Fail-closed at activation with the prior revision staying live is the only safe operational pattern; runtime guessing is gone." > - CORE by Muse — "Locks fail-loud at activation as release blocker instead of runtime guess - aligns with abandoning Singleton default." > **Claim** — Grok: "Unconfigured groups must fail closed at promo activation or engine load: refuse to mark the promotion active, refuse to build the candidate set, and surface a configuration error before any cart sees it." > - KEEP by Muse — "Exactly the fix for default-to-Singleton - fail closed before cart evaluation." > **Claim** — Muse: "Default-safe creates silent mutex where business expected stack. Ship fail-loud-at-activation instead: promotion creation/update rejects if `exclusive_group` not in `group_policies` table, and evaluator returns config error + metric, not fallback." > - KEEP by Grok — "Full concession on the Singleton default; rejecting at creation/update is the right enforcement point so the error never reaches a cart." #### Unanimous: Authoritative checkout re-evaluation is mandatory to prevent stale-snapshot bugs. Checkout can mutate the cart between display and payment, meaning the initial candidate set can go stale; the engine must re-validate the full quote atomically inside the final payment transaction, not rely on cached versions. > **Claim** — GPT: "**Payment authorization may occur only when the cart revision, promotion revision, payable amount, and quote ID exactly match the latest quote displayed to and explicitly confirmed by the customer.**" > - CORE by Grok — "This is the precise checkout invariant; quote ID plus dual revisions is what actually binds confirmation to payment." > - CORE by Muse — "This is the checkout invariant we should ship Friday - no grace, no stale-cache payment." > **Claim** — Muse: "candidate set immutable assumption breaks if checkout mutates cart between read and pricing transaction - gift options, shipping method, price update." > - KEEP by Grok — "Final authority must rebuild candidates inside the payment transaction; reusing the display-path set is the stale-snapshot bug." > **Claim** — GPT: "Our consensus still fails if “immutable snapshot” is only rhetoric." > - KEEP by Grok — "Un-named failure mode that matters most for Friday: without CAS and atomic config publish the whole design is theater under concurrent cart mutation." #### Unanimous: Positivity checks must be computed incrementally to avoid circular logic. Pre-filtering candidates based on base-cart positivity is unsafe because real-world discount values depend on the sequence of applied promotions; validity must be verified at the moment of application. > **Claim** — GPT: "A global is computed against the immutable cart snapshot with **no other promotions applied**. That is not circular because the stipulated result, if selected, is that global alone." > - KEEP by Grok — "This cleanly proves phase-1 zero-check is non-circular while leaving phase-2 incremental against running state." > - KEEP by Muse — "Makes phase-1 dominance test non-circular and resolves my filterPositive concern by scoping compute to pristine base." > **Claim** — Grok: "Phase 1 (globals) computes each global’s discount solely against the undiscounted cart." > - KEEP by Muse — "Clean separation that makes phase-1 zero check valid while phase-2 remains incremental." > **Claim** — Grok: "If any promotion’s eligibility threshold is defined on a post-discount total, phase 1 can select a global that would have been ineligible under the group combination that was discarded, or vice versa." > - EXPLORE by Muse — "Unnamed failure mode in our converged design - we need an eligibility base contract documented before canary." #### Majority: Multi-global winner rules are provisional defaults pending Product review. The decision to let globals dominate group sums and suppress others is an un-signed default; treat this as a temporary bridge for the Summer Sale rather than permanent business logic. > **Claim** — Muse: "two-phase silently decides global beats any group sum and global suppresses `scope=none`. Both are reasonable under Fact 1 but are provisional policy not signed product decisions - revisit post Summer Sale." > - KEEP by Grok — "Correctly keeps the two still-unsigned decisions on the post-canary review list instead of pretending Fact 1 closed them." --- --- ## Sources - [Discount Calculation](https://docs.antavo.com/docs/discount-calculation) - [Coupon logic pitfalls: stacking rules that do not break carts](https://koder.ai/blog/coupon-logic-pitfalls-stacking-exclusions) - [How to Handle Pricing and Discount Caching with Redis](https://oneuptime.com/blog/post/2026-01-21-redis-pricing-discount-caching/view) - [Promotion exclusivity / stackability at Item level - SAP Community](https://answers.sap.com/questions/13561350/promotion-exclusivity-stackability-at-item-level.html) - [Stacking Rules - Voucherify Support](https://support.voucherify.io/article/604-stacking-rules) - [Promotion Stacking Done Right: What Most Platforms Get Wrong - ...](https://broadleafcommerce.com/blog/promotion-stacking-done-right-what-most-platforms-get-wrong/) - [Stacking rules and categories overview - Voucherify](https://docs.voucherify.io/orchestrate/stacking-rules-overview) - [What is promotion stacking? Definition, logic & margin control](https://www.voucherify.io/glossary/promotion-stacking) - [US 2026 Sale Calendar: Memorial Day, July 4, Black Friday, and ...](https://www.shopback.com/blog/finance/us-2026-sale-calendar) - [Q4 2026 Ecommerce Peak Season Prep: The Full Playbook](https://www.digitalapplied.com/blog/q4-2026-ecommerce-peak-season-prep-playbook)