Skip to content

Carry main into redesign/main: streak gap repairs, billed amount, free sub-habits and referral Pro days (#746) - #829

Merged
thomasluizon merged 7 commits into
redesign/mainfrom
thomasluizon/ticket-746-carry-oct20b
Oct 20, 2026
Merged

thomasluizon merged 7 commits into
redesign/mainfrom
thomasluizon/ticket-746-carry-oct20b

Conversation

@thomasluizon

@thomasluizon thomasluizon commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Carries the four requested main fixes into redesign/main, retaining the redesign API, Astra previews and revisions, strict tool schemas, v2 endpoints, and capability policies.

Refs thomasluizon/orbit-tickets#746
Refs thomasluizon/orbit-tickets#483
Refs thomasluizon/orbit-tickets#385
Refs thomasluizon/orbit-tickets#386
Refs thomasluizon/orbit-tickets#237
Refs thomasluizon/orbit-tickets#28

Carried commits

Applied oldest first with git cherry-pick -x --no-commit, then built and ran focused tests before each commit. Every resulting commit retains the full source commit trailer.

Main commit Carry commit Resolution
90c5320d (#818) f7e7afb1 Preserve yearly gap repairs through recalculation and history reads, including the bounded schedule slices and upstream tests. Retain the redesign's product-language explanation while updating its window to 732 days.
0460a545 (#819) fadb7fa7 Carry the optional recurring billing preview, query mapping, provider failure handling, and tests. Generate both OpenAPI contracts from the combined controllers and DTOs.
c9d22a6e (#824) 12ce3fae Remove sub-habit plan gates from REST commands, inline Astra creation, bulk creation and duplication. Preserve the redesign's creation and argument-check structure, atomic AI quota consumption, root ceiling and depth validation. Keep slip alerts gated by Pro. Adapt redesign-only tests, MCP description and feature explanation.
7b4e9af6 (#823) 8e7638b7 Carry atomic referral Pro days, subscriber coupon delivery, entitlement separation, validators, tests and both migrations. Adapt the redesign-only proactive and Notion eligibility queries to mapped subscription fields and independent bonus expiry. Preserve the combined migration assertions.

No commits were skipped. Before applying them, inspected origin/redesign/main at 84451e28, searched its commit messages for all four trailers, and ran a reverse git apply --check for each source patch; all four reverse checks exited 1. Content inspection also showed the changes missing:

  • src/Orbit.Application/Common/AppConstants.cs:73 had only the 365-day streak lookback.
  • src/Orbit.Application/Subscriptions/SubscriptionDtos.cs:50 defined the billing response without the appended billed amount.
  • src/Orbit.Application/Common/PayGateService.cs:30 still enforced CanCreateSubHabits through SubHabitsProOnly.
  • src/Orbit.Domain/Entities/Referral.cs:8 had no per-recipient reward choices or delivery timestamps, and the new referral migration files were absent.

These line references describe the pre-carry base, not the changed head.

Conflict resolutions by redesign commit

Product-language explanations, #797 (45548c59)

  • src/Orbit.Application/Chat/Content/FeatureExplanations/streaks.md: retain prose without internal identifiers, change the window to 732 days, and describe the retained predecessor occurrence. Update the provenance to StreakRepairWindowDays.

Notion API additions, #732 (5d9ff03e)

  • src/Orbit.Api/openapi.json
  • src/Orbit.Api/openapi_v2.json

The billing schema insertion conflicted with the redesign's AuthorizeRequest schema. Discard the conflicted generated output and regenerate both documents through dotnet build Orbit.slnx. Both retain every pre-carry path and schema and append optional, nullable billedAmountPerPeriod. The final build also regenerates the referral response schemas. Neither document was hand-merged.

Atomic AI quota checks, #806 (e40a2a69)

  • src/Orbit.Application/Common/PayGateService.cs: remove only CanCreateSubHabits; keep TryConsumeAiMessage, refunds, smoke-account handling and Notion access. Do not restore the retired duplicate CanSendAiMessage check from main's surrounding context.
  • src/Orbit.Domain/Interfaces/IPayGateService.cs: remove only the sub-habit gate contract and retain the redesign's atomic quota interface.
  • tests/Orbit.Application.Tests/Common/PayGateServiceTests.cs: remove the five tests for the deleted gate, as upstream does, while retaining atomic quota, refund and smoke-account coverage. The main-side duplicate-check tests target the method already removed by test: close Astra coverage gaps and remove the duplicate quota check #806; the retained atomic-path tests cover that behavior.

Strict schemas and combined model checks, #816 (84451e28), with conversation assertions from #800 (4e7ddcb5)

  • src/Orbit.Application/Chat/Tools/Implementations/CreateHabitTool.cs: remove the sub-habit gate while retaining the redesign's BuildChildHabits signature, optional-null normalization, preview checks and independent child scheduling. Apply main's slip-alert gate before any persistence.
  • tests/Orbit.Infrastructure.Tests/Persistence/FeatureFlagAccountsMigrationTests.cs: resolve both carries' conflicts by retaining the redesign's schema-version, AI-security, conversation, proactive preference and migration inventory checks; add the free-sub-habit seed assertion and all five referral column assertions. The combined model has 51 operations relative to the existing baseline. Keep every migration in timestamp order and assert no pending model changes.

Creation surface audit

  • REST v1 and v2 share the carried CreateHabitCommand, CreateSubHabitCommand, BulkCreateHabitsCommand and duplication handlers. The existing versioned route declarations remain intact.
  • Astra inline creation uses the carried gate removal on the redesign structure. Dedicated creation and the redesign-only bulk-under-existing-parent path reach CreateSubHabitCommand; their free and Pro cases now persist real children through the handlers. Test arguments include schema-derived optional nulls and pass through the same OptionalToolArguments normalization as the chat dispatcher.
  • MCP create_sub_habit retains its executor, audit and policy bridge. Remove the obsolete Pro requirement from its description and adapt its real tool-to-command test to the carried handler constructor.
  • The capability inventory already maps child creation to HabitsWrite and bulk creation to HabitsBulkWrite, with no plan requirement. It needs no policy change. Catalog, policy, strict-schema and MCP tests pass.
  • The carried migration sets AppFeatureFlags.sub_habits.PlanRequirement to null and removes AppConfigs.SubHabitsProOnly. The service/interface/config constant no longer exposes the obsolete gate. The redesign-only free/Pro explanation and drift assertions now match.
  • GetHabitByIdQuery, GetHabitFullDetailQuery, strict schema conversion, pending-operation revision/refresh behavior and the v2 API surface are retained.

Migrations

Keep the original migration and Designer files byte-for-byte:

  • 20261020023828_AddReferralProDayRewards
  • 20261020030817_MakeSubHabitsFree
  • 20261020041243_SeparateBonusProEntitlement

The combined snapshot retains the redesign migrations, including 20261020023110_VersionPendingToolArguments and 20261020033359_RestoreRequiredProactiveAstraPreference, while adding the referral fields and free-sub-habit seed. Original timestamps order the referral column addition before the sub-habit seed change, despite the requested cherry-pick order. These operations are independent; the bonus-entitlement data migration remains last. No migration was renamed or regenerated.

From src/Orbit.Api, env -u LANG dotnet ef migrations has-pending-model-changes --project ../Orbit.Infrastructure --startup-project . --no-build exited 0 and reported: "No changes have been made to the model since the last migration." The design-time factory uses no live database. EF tools report the existing 10.0.12 versus runtime 10.0.13 version warning.

Assumptions

  • Preserve both referral migrations contained in 7b4e9af6; rejected interpreting the order's singular migration reference as permission to omit SeparateBonusProEntitlement.
  • Apply child creation changes to the redesign's existing BuildChildHabits signature; rejected restoring main's older argument shape and losing the strict-schema work.
  • Extend the combined model assertions rather than replacing them with main's smaller migration inventory; rejected dropping redesign schema guarantees to match main's operation count.
  • Included the auto-merged eval-trace test adjustment in this resolution rather than deferring its stale expectation to another ticket.

Test evidence

Each coherent carry was built, tested and committed before broader verification. A separate follow-up commit updates the remaining redesign migration expectations and shared assertions, with no production changes. Focused passing runs: streak Application 134 and Infrastructure 48; billing Application 9 and Infrastructure 85; sub-habit Application 338 and Infrastructure 201; referral Domain 23, Application 208 and Infrastructure 134. All exited 0.

Regression observations from this run:

  1. The unchanged FeatureExplanationDriftTests passed while the old Pro-only sub-habit claim was present: env -u LANG dotnet test tests/Orbit.Application.Tests --no-build --filter 'FullyQualifiedName~HabitScheduleServiceTests|FullyQualifiedName~GetStreakHistoryQueryHandlerTests|FullyQualifiedName~RepairStreakGapCommandValidatorTests|FullyQualifiedName~FeatureExplanationDriftTests' exited 0 with 134 passing tests. After updating Paygate_ProUnlocksMatchEnforcedGates for the carried policy, env -u LANG dotnet test tests/Orbit.Application.Tests --no-build --filter FullyQualifiedName~FeatureExplanationDriftTests exited 1 with two intended failures: the prose still listed Sub-habits as a Pro unlock. After fixing the prose, the 338-test Application filter passed, including both cases.

  2. The unchanged combined-model test detected both inventory mismatches: after the sub-habit carry, env -u LANG dotnet test tests/Orbit.Infrastructure.Tests --no-build --filter FullyQualifiedName~FeatureFlagAccountsMigrationTests exited 1, expecting 45 operations and finding 46. After the referral carry, the same test in the filter below expected 46 and found 51. Retaining all prior assertions and adding the carried assertions made the migration tests pass. No strengthened red test was needed because the existing assertion detected the mismatch.

  3. Before the referral carry, env -u LANG dotnet test tests/Orbit.Infrastructure.Tests --no-build --filter 'FullyQualifiedName~ProactiveEngineServiceTests|FullyQualifiedName~NotionPollServiceTests.ScheduledTickIncludesPeopleWithTrialProAccess' passed 69 tests. After the carry and before adaptation, env -u LANG dotnet test tests/Orbit.Infrastructure.Tests --no-build --filter 'FullyQualifiedName~ProactiveEngineServiceTests.TrialIsEligible_FreeAndExpiredTrialAreNot|FullyQualifiedName~NotionPollServiceTests.ScheduledTickIncludesPeopleWithTrialProAccess|FullyQualifiedName~FeatureFlagAccountsMigrationTests' exited 1: both unchanged scheduling tests failed because EF could not translate the now-unmapped Plan and PlanExpiresAt members, alongside the model-count failure above.

  4. Added bonus-expiry boundary cases before correcting those two queries. env -u LANG dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~BonusProEligibility exited 1 with all six cases failing on that translation error. After the query adaptation, the 134-test Infrastructure filter passed, including active, exactly expired and previously expired bonus accounts on both services.

  5. The first broad env -u LANG dotnet test tests/Orbit.Infrastructure.Tests --no-build --no-restore exited 1 with three existing migration assertions failing: ExpandHabitScheduleAndStartDateMigrationTests.TargetModel_PreservesRedesignWithConversationAndAiSecurityAdditions expected 16 operations and found 22; DailyAiDollarBudgetMigrationTests.DollarExpansion_PreservesLegacyWritersWithConversationAndArgumentVersionAdditions expected 8 and found 14; ConversationMigrationTests.Migration_ExpandsSchemaAndDisablesCollection_AndDownDropsDiagnosticData expected only the pending-argument-version column among the remaining additions. All now retain their prior schema checks and explicitly verify the five referral columns and free-sub-habit seed. env -u LANG dotnet test tests/Orbit.Infrastructure.Tests --filter 'FullyQualifiedName~MigrationTests|FullyQualifiedName~RollingMigrationGateTests' then exited 0 with 138 passing tests. Existing assertions caught these mismatches, so no separate strengthened red test was needed.

Final verification on 26b385e2, with every process judged by its exit code:

Command LANG unset LC_ALL=en_US.UTF-8
dotnet build Orbit.slnx Exit 0, zero errors Exit 0, zero errors
dotnet test tests/Orbit.Domain.Tests --no-build --no-restore Exit 0, 836 passed Exit 0, 836 passed
dotnet test tests/Orbit.Application.Tests --no-build --no-restore Exit 0, 6,222 passed Exit 0, 6,222 passed
dotnet test tests/Orbit.Infrastructure.Tests --no-build --no-restore Exit 0, 4,521 passed Exit 0, 4,521 passed

The two environment prefixes were env -u LANG and LC_ALL=en_US.UTF-8. Both Infrastructure runs set ORBIT_MIGRATION_GUARD_BASE=origin/redesign/main. Each locale passed all 11,579 tests with zero failures or skips, including migration and strict-schema tests. The final full Application and Infrastructure commands provide the passing results for every regression named above. Both builds regenerate both OpenAPI documents without leaving changes. All five commits passed the normal hooks. Mutation testing remains with CI; live release verification remains with the run.

Build and suites passed with both LANG unset and LC_ALL=en_US.UTF-8, each exiting 0.

Check Result per locale
dotnet build Orbit.slnx 0 errors
Domain tests 837 passed
Application tests 6,259 passed
Infrastructure tests 4,534 passed

The unchanged pre-merge focused suite passed 20 tests. After merging, the stale eval-trace expectation failed with “expected 2, found 8.” After reconciliation, all 22 focused tests passed.

dotnet ef migrations has-pending-model-changes exited 0 and reported no changes. Commit hooks passed.

The merge of api redesign/main (84f433bd, dd8b07b6) is 57585c1a. The Pullfrog P1 on the billed-amount preview was fixed on api main first under thomasluizon/orbit-tickets#1581 (orbit-api#830, merged as 377eca51) and cherry-picked here with -x as a80cf565; on the carry, dotnet test tests/Orbit.Infrastructure.Tests --filter "FullyQualifiedName~StripeBilling" passed 70 of 70, exit 0.

External interface evidence

The carried Stripe preview fields and error handling are supported by #819's pinned SDK source inspection: Stripe.net 52.4.2 source commit f4f1c8ce704825d7d7f00b4c9d848a2d028757eb, covering preview options, invoice currency/lines, pagination, recurring parent details, subtotal and discount amounts. Reproduce from the package nuspec repository commit and the linked source declarations.

The carried Hangfire job members, retry attributes and Stripe idempotency key are supported by #823's runtime reflection evidence. That evidence lists the actual installed IBackgroundJobClient.Create, Job.Type, Job.Args, RequestOptions.IdempotencyKey, CouponService.CreateAsync and retry attribute shapes. Queue return IDs are not used as failure signals. The redesign adaptations add no provider response reads or invented provider fixtures.

Manual steps

  • The run's go-live production API release carries this change after promotion to main, before the paired billing and referral consumers, thomasluizon/orbit-tickets#386 and thomasluizon/orbit-tickets#62. In GitHub Actions > Release API, dispatch from main with environment=production and branch=main. Startup applies the included migrations. This worker does not perform the release.
  • As release verification, read authenticated GET /api/subscriptions/billing for a discounted Stripe subscription and verify billedAmountPerPeriod alongside the unchanged plan price. Verify the nullable fallback for unavailable previews before the billing UI ships.
  • As release verification, read authenticated GET /api/config and confirm features.sub_habits.enabled is true and planRequirement is null. Confirm ordinary free child creation persists through REST and Astra, free creation with slip alerts returns PAY_GATE without persistence, and Pro creation retains alerts.
  • As release verification, confirm a qualifying referral grants seven Pro days atomically to eligible accounts without a paid subscription, retains the subscriber coupon path and exact paid expiry, and retries delivery without a duplicate entitlement grant.
  • If the run rolls production back to a binary predating separate bonus entitlements, apply the included Down for SeparateBonusProEntitlement first using dotnet ef database update 20261020023828_AddReferralProDayRewards against the production connection. Verify every bonus row has raw Plan = Pro and PlanExpiresAt covering its bonus expiry before starting the older binary.

None for the owner. No new environment variables, secrets, dashboard settings, manual SQL or backfills are required for rollout. Live release checks remain the run's release verification steps.

None introduced by this merge.

* fix: preserve yearly streak gap repairs through recalculation

* fix: isolate yearly streak repair history window

(cherry picked from commit 90c5320)
* fix: expose discounted Stripe billed amount per period

* fix: isolate optional recurring Stripe billing preview

(cherry picked from commit 0460a54)
* feat: make sub-habits available to every plan (ORB-231)

* test: track free sub-habits in migration model expectations

* fix: preserve Pro slip alerts when creating free sub-habits

(cherry picked from commit c9d22a6)
* feat: grant atomic referral Pro days (ORB-22)

* fix: preserve referral transaction and billing context

* fix: preserve bonus Pro access across provider changes

(cherry picked from commit 7b4e9af)

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Important

The new billed-amount preview excludes the one-time referral coupons Orbit actually issues, so the discounted billing display remains incorrect for those subscribers.

Reviewed changes across all five carry commits into redesign/main, including the combined migrations, redesign-specific adaptations, tests, and paired billing consumer.

  • Streak repairs: Extends streak and repair history to 732 days and slices schedule reads to retain annual predecessors through recalculation.
  • Billing amounts: Appends an optional discounted-amount field, isolates Stripe previews from ordinary subscription reads, and preserves nullable provider-failure fallback.
  • Free sub-habits: Removes creation plan gates across REST, Astra, bulk, duplication and MCP while retaining slip-alert entitlement, root ceilings and depth checks.
  • Referral rewards: Adds atomic bonus Pro days, subscriber coupon delivery, independent bonus expiry, and mapped entitlement queries for background services.
  • Combined schema: Carries three migrations and retains redesign migration/model assertions and both versioned OpenAPI documents.

Focused verification passed all 278 tests: 79 Infrastructure, 176 Application and 23 Domain. The full suite was not rerun; the worktree is clean. The optional cross-model check was unavailable because codex is not installed.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Comment thread src/Orbit.Infrastructure/Services/StripeBillingService.cs Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes since the prior Pullfrog review at 26b385e2, covering the redesign/main merge and billing fix through a80cf565.

  • Fixed one-time coupon previews: Switched Stripe previews to next, including applicable referral coupons while preserving subscription-line filtering, the unchanged plan price, and nullable fallback.
  • Expanded billing regressions: Covered once and forever coupons, normal pricing after consumption, and exclusion of customer credit and debit balances.
  • Reconciled migration assertions: Retained eval-trace seed checks alongside referral columns, free sub-habits, and the combined migration inventory after the base merge.

All 86 focused billing and migration tests passed with zero failures or skips. The full suite was not rerun; the worktree is clean.

Pullfrog  | View workflow run | Using gpt-6.1-sol | 𝕏

@sonarqubecloud

Copy link
Copy Markdown

@thomasluizon
thomasluizon merged commit ed74959 into redesign/main Oct 20, 2026
26 checks passed
@thomasluizon
thomasluizon deleted the thomasluizon/ticket-746-carry-oct20b branch October 20, 2026 08:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant