Skip to content

feat(products): live store readiness after apply + price equalization - #126

Merged
joshdholtz merged 7 commits into
joshholtz/dx-973-store-state-response-schemafrom
joshholtz/dx-974-store-readiness
Aug 20, 2026
Merged

feat(products): live store readiness after apply + price equalization#126
joshdholtz merged 7 commits into
joshholtz/dx-973-store-state-response-schemafrom
joshholtz/dx-974-store-readiness

Conversation

@joshdholtz

@joshdholtz joshdholtz commented Aug 19, 2026

Copy link
Copy Markdown
Member

Closes the Product Catalog team's #1 concern: "RevenueCat accepted the plan" was being reported as if "Apple has a sellable product." Now products store apply/sync verify the live result.

Stacked on #125 (which captures raw_store_status).

Post-apply readiness

After apply reaches applied, it reads each affected product's live store state and classifies it — no longer a blanket "Applied to the stores":

  • APPROVED + every available territory priced → READY
  • MISSING_METADATA / READY_TO_SUBMIT / DEVELOPER_ACTION_NEEDED (or any available-but-unpriced territory) → INCOMPLETE (lists the unpriced territories)
  • WAITING_FOR_REVIEW / IN_REVIEW / PENDING_BINARY_APPROVALIN PROGRESS
  • not_found / apply failed → FAILED
  • read error (network) → UNKNOWN (best-effort — a failed read never turns a successful apply into a reported failure)

Overall = worst product; a non-READY overall renders as a warning, not a success. --json gets a readiness object (overall + per-product {product_id, verdict, raw_store_status, unpriced_territories}) alongside the plan. sync gets this automatically (it calls the same apply path).

Equalization

--equalize-base-territory US injects common.pricing.equalize_missing_subscription_prices into each desired state (preserving any explicit territory_prices), so missing App Store subscription prices get filled during apply.

(Blocker-gating on warnings[severity=blocker] already existed.)

Agreements/tax/banking are intentionally out of scope — account-level, not derivable from per-product state.

Tests: classifier table (every verdict), worst-overall, equalization injection, and an apply→readiness integration test asserting a MISSING_METADATA result reports INCOMPLETE (not success).

🤖 Generated with Claude Code


Note

Medium Risk
Changes apply/sync output and success signaling for agents (JSON envelope, warning vs success). Classification of store statuses could mis-report readiness, but apply itself is unchanged and live reads are best-effort.

Overview
products store apply/sync no longer treat a successful plan apply as a sellable product. After apply (including no-op plans), the CLI reads each product's live store state and reports READY / IN_PROGRESS / INCOMPLETE / FAILED / UNKNOWN, with overall = worst product. Non-READY is a warning, not success. --json adds a readiness object (verdict, raw_store_status, unpriced territories, store warnings, next actions).

Adds --equalize-base-territory on plan/sync so missing App Store subscription prices are filled from a base territory during apply, without overwriting existing territory_prices. Next-action hints are store-specific (Play is not told to use the Apple flag).

Reviewed by Cursor Bugbot for commit 32e7844. Bugbot is set up for automated code reviews on this repo. Configure here.

@linear-code

linear-code Bot commented Aug 19, 2026

Copy link
Copy Markdown
DX-974 products store: post-apply readiness verification + subscription price equalization

From Product Catalog team feedback. The CLI already uses the Plans API (plan→apply with polling) — the gaps are verifying the live result and modeling equalization.

#4 Post-apply readiness (the headline gap)

Today apply polls the plan to applied and stops — it never reads live store state, so "RevenueCat accepted" is reported as if "Apple has a sellable product." Add: after apply, GetStoreState each affected product (from plan_items[*].product_id) and classify from store_status.raw_store_status (needs DX-973) cross-checked with availability/pricing:

  • APPROVED + every available territory priced → READY
  • MISSING_METADATA / READY_TO_SUBMIT / DEVELOPER_ACTION_NEEDED, or available-but-unpriced territories → INCOMPLETE (list the unpriced territories)
  • WAITING_FOR_REVIEW / IN_REVIEW / PENDING_BINARY_APPROVALIN PROGRESS (not yet sellable)
  • not_found / apply_status: failedFAILED

Overall verdict = worst product; never print generic success when any product isn't READY. Same summary in --json. Surface raw_store_status verbatim.

Agreements/tax/banking (account-level, one-time for new devs) are explicitly out of scope — not derivable from per-product state.

#3 Equalization + availability-vs-pricing

  • --equalize-base-territory <TERR> → inject common.pricing.equalize_missing_subscription_prices.base_territory into the plan desired state (spec-supported). Plan diff/warnings show the effect.
  • Report available-but-unpriced territories as INCOMPLETE (availability and pricing are separate: a product can be available but unsellable).

Also

  • Gate apply on plan warnings[severity=blocker] (refuse unless explicitly overridden).

Tests

Plan-not-direct, blocker prevents apply, apply waits/resumes, MISSING_METADATA → INCOMPLETE, APPROVED+priced → READY, WAITING_FOR_REVIEW → IN-PROGRESS, unpriced-territory → INCOMPLETE, applied-but-incomplete-live → not READY, equalize present in desired state, JSON + interactive.

Blocked by

DX-973 (needs raw_store_status modeled/captured first).

Review in Linear

@joshdholtz

Copy link
Copy Markdown
Member Author

bugbot run

@cursor cursor 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit c007482. Configure here.

@joshdholtz
joshdholtz marked this pull request as ready for review August 19, 2026 13:31
@joshdholtz
joshdholtz requested review from a team and popcorn and a lite review from Copilot August 19, 2026 13:31

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@joshdholtz

Copy link
Copy Markdown
Member Author

bugbot run

@joshdholtz
joshdholtz force-pushed the joshholtz/dx-974-store-readiness branch from c007482 to e3ec2fb Compare August 19, 2026 13:42
Comment thread internal/cli/products_store_readiness.go
popcorn
popcorn previously approved these changes Aug 19, 2026
@joshdholtz

Copy link
Copy Markdown
Member Author

bugbot run

@cursor cursor 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.

✅ Bugbot reviewed your changes and found no new issues!

1 issue from previous review remains unresolved.

Fix All in Cursor

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit ed7cb62. Configure here.

@joshdholtz

Copy link
Copy Markdown
Member Author

bugbot run

Comment thread internal/cli/products_store_readiness.go Outdated
@joshdholtz

Copy link
Copy Markdown
Member Author

bugbot run

@cursor cursor 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 2cabff9. Configure here.

@joshdholtz
joshdholtz force-pushed the joshholtz/dx-974-store-readiness branch from 43375b7 to 2cabff9 Compare August 20, 2026 17:07
@joshdholtz
joshdholtz force-pushed the joshholtz/dx-974-store-readiness branch from ce4d427 to 7247adf Compare August 20, 2026 18:10
@joshdholtz

Copy link
Copy Markdown
Member Author

bugbot run

Comment thread internal/cli/products_store.go
@joshdholtz

Copy link
Copy Markdown
Member Author

bugbot run

@cursor cursor 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit c2adaec. Configure here.

joshdholtz and others added 3 commits August 20, 2026 14:04
…ization

apply/sync now read each affected product's live store state and report
READY / IN_PROGRESS / INCOMPLETE / FAILED from raw_store_status + availability/
pricing, instead of a blanket success — so "RevenueCat accepted" is no longer
mistaken for "Apple sellable." Adds --equalize-base-territory to fill missing
subscription territory prices via common.pricing.equalize_missing_subscription_prices.

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

Post-apply readiness now carries the live store warnings (the store's own
remedy text) and derived next actions (unpriced -> --equalize-base-territory;
MISSING_METADATA -> add metadata + attach a real screenshot) per product, in
both JSON and interactive output, so a non-READY result names the fix instead of
just the condition. Adds WAITING_FOR_UPLOAD to in-progress, documents the
verdicts and --equalize-base-territory in help.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
--equalize-base-territory is App Store subscription equalization; a Play product
with unpriced territories was being told to use it. Route Play to its own
base-plan other-regions guidance and keep a neutral fallback, so readiness
next-actions aren't Apple-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
joshdholtz and others added 4 commits August 20, 2026 14:04
Both stores collapse into RevenueCat's normalized status enum; the classifier
only handled ok/not_found and defaulted the rest to INCOMPLETE. Handle
action_in_progress (in progress), needs_action/inactive_in_store/draft
(incomplete), and could_not_check/unspecified/unknown (unknown). Play has no
verbatim raw state, so this normalized layer is its primary readiness signal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A create apply can return plan items whose RevenueCat product ID is still
unset. verifyStoreStateReadiness dropped those items, so a create-only apply
left an empty product list and worstReadiness reported READY — printing success
though no live store state was ever read. Record unreadable items as UNKNOWN
and make an empty list UNKNOWN too, so readiness is never READY without a check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The unpriced-territory next action told users to re-run with
--equalize-base-territory, but that flag lives on products store plan/sync, not
apply — following it on the apply path hits an unknown-flag error. Name the
right commands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An apply whose plan already matched the desired state reported success and
returned without reading live store state, so an in-sync but unsellable
catalog (e.g. MISSING_METADATA) looked done. Run the same readiness check the
normal apply path uses and surface its verdict.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@joshdholtz
joshdholtz force-pushed the joshholtz/dx-974-store-readiness branch from c2adaec to 32e7844 Compare August 20, 2026 19:04

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 32e7844. Configure here.

}
if pr.RawStoreStatus != nil && strings.EqualFold(*pr.RawStoreStatus, "MISSING_METADATA") {
actions = append(actions, "add the product's required metadata (localizations/review info) and re-apply; attach a real review screenshot with: rc products store screenshot <product-id> --file <path>")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Metadata hint points at re-apply

Medium Severity

The MISSING_METADATA next action tells users to add localizations or review info and re-apply. apply only replays the existing plan ID and never picks up new desired state, so that path cannot supply the missing metadata. Screenshot upload is a separate command and is fine; the rest of the hint sends agents back through the same incomplete plan.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 32e7844. Configure here.

@joshdholtz
joshdholtz merged commit 8510968 into main Aug 20, 2026
10 checks passed
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.

4 participants