Skip to content

handbook: narrow versioning design doc scope - #15684

Draft
pieterbeulque wants to merge 5 commits into
mainfrom
pieter/versioning-tweaks
Draft

pieterbeulque wants to merge 5 commits into
mainfrom
pieter/versioning-tweaks

Conversation

@pieterbeulque

@pieterbeulque pieterbeulque commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Review changes on top of #15332, based on feat-versioning so the diff is the rewrite only.

Decisions

  1. Full freeze for declarative-config orgs. No change reaches existing customers until they are explicitly moved. Dashboard-only orgs keep today's behavior. "Current state" now lists what really propagates today: benefit property edits, credit units at next renewal, benefit add/remove to past one-time buyers.
  2. No migrations DSL. Moving customers = bulk subscription update (a loop over today's subscription update) + a new one-time order endpoint. Declarative migrations get their own design doc built on these.
  3. New ID per version. Purchases stay pinned to their row. Checkout links and discounts are re-pointed in the publish transaction. Checkout creation accepts external_id.
  4. Draft lives in the API, config is its only writer. apply publishes at once, apply --draft uploads for read-only dashboard review/publish/discard. No conflict/force/pull rules.
  5. Live membership is derived from catalogs/catalog_products. No stored is_current; a partial unique index is mentioned as a possible cache.
  6. No meter versioning in v0. Today's last_billed_event lock stays. Balance carry-over on move is the M3 direction; a balance shared across meter versions is ruled out.
  7. New orgs only in v0. Adoption stays an open question.
  8. Grants are re-pointed by benefit external_id through the strategy's existing update, so license keys, Discord roles, etc. aren't revoked and reissued on a move. Blocking stricter benefits was considered and rejected.
  9. API hides versions. Lists return only the live catalog, no params. Draft rows only through catalog draft endpoints.
  10. Webhooks: product.created for the new row, product.updated for the row leaving the live catalog.

The benefit → product cascade is kept and now explained: grants are rebuilt from product.benefits on reactivation, seat claims, etc., so editing links in place would break the freeze for some customers.

Also adds a catalog_benefits membership table, since benefits are versioned too.

For @psincraian

Petru's latest commit (fix: answered questions) is merged in:

  • "Discounts already applied to existing subscriptions or checkouts don't change" → References section.
  • The credits answer (settle old usage, revoke old credit grant, grant new prorated credits, extra usage at new price) → "Moving customers", as the one exception to grant re-pointing. Please confirm this matches what you meant.

🤖 Generated with Claude Code

View guided diff Turn on auto-fix

psincraian and others added 5 commits October 9, 2026 12:17
These were auto-written by `next dev` and accidentally swept into the
versioning commit; unrelated to this PR.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Full freeze for declarative-config orgs, migrations DSL and meter
versioning out of scope, derived live membership instead of is_current,
config-only drafts with read-only dashboard review, and new References
and API surface sections.

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

vercel Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
orbit Ready Ready Preview Oct 20, 2026 2:10pm UTC
polar-test Ready Ready Preview Oct 20, 2026 2:10pm UTC

Request Review

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found across 1 file

Confidence score: 3/5

  • In handbook/engineering/design-documents/versioning.mdx, plain apply can publish without member review, leaving a path for an agent to move customers. Clarify that agents are limited to apply --draft or document a separate safeguard.
  • The uniqueness rule in handbook/engineering/design-documents/versioning.mdx makes meters catalog-versioned, which conflicts with the stated v0 scope. Limit the rule to products and benefits, and clarify meter identity in v0.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="handbook/engineering/design-documents/versioning.mdx">

<violation number="1" location="handbook/engineering/design-documents/versioning.mdx:86">
P3: This uniqueness rule makes meters catalog-versioned, contradicting the stated v0 scope. Limit the catalog-version uniqueness rule to products and benefits, and clarify meter identity in v0.</violation>

<violation number="2" location="handbook/engineering/design-documents/versioning.mdx:287">
P2: This mitigation only holds for `apply --draft`. Plain `apply` publishes with no member review, so an agent can publish and then move customers. Either state that agents are limited to `apply --draft`, or name a separate control that stops an agent from publishing and moving customers in one go.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

- **Risks:** an agent moves many customers to higher prices. Mitigations: plan before apply, idempotent work, and cancel of migrations.
- **Who can access it:** the existing read and write scopes. Publishing a draft and moving customers check permissions again. Moving subscriptions needs `subscriptions:write`. Moving one-time orders needs its own write permission.
- **Data we store:** draft and published rows, and catalogs. No new personal data.
- **Risks:** an agent moves many customers to higher prices. Mitigations: agents propose drafts that a member publishes, publishing never moves customers, and moves are explicit and idempotent.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: This mitigation only holds for apply --draft. Plain apply publishes with no member review, so an agent can publish and then move customers. Either state that agents are limited to apply --draft, or name a separate control that stops an agent from publishing and moving customers in one go.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At handbook/engineering/design-documents/versioning.mdx, line 287:

<comment>This mitigation only holds for `apply --draft`. Plain `apply` publishes with no member review, so an agent can publish and then move customers. Either state that agents are limited to `apply --draft`, or name a separate control that stops an agent from publishing and moving customers in one go.</comment>

<file context>
@@ -144,125 +168,123 @@ export default defineConfig({
-- **Risks:** an agent moves many customers to higher prices. Mitigations: plan before apply, idempotent work, and cancel of migrations.
+- **Who can access it:** the existing read and write scopes. Publishing a draft and moving customers check permissions again. Moving subscriptions needs `subscriptions:write`. Moving one-time orders needs its own write permission.
+- **Data we store:** draft and published rows, and catalogs. No new personal data.
+- **Risks:** an agent moves many customers to higher prices. Mitigations: agents propose drafts that a member publishes, publishing never moves customers, and moves are explicit and idempotent.
 
 ## Appendix: Milestones
</file context>
Suggested change
- **Risks:** an agent moves many customers to higher prices. Mitigations: agents propose drafts that a member publishes, publishing never moves customers, and moves are explicit and idempotent.
- **Risks:** an agent moves many customers to higher prices. Mitigations: agents propose drafts with `apply --draft` that a member publishes, publishing never moves customers, and moves are explicit and idempotent. Plain `apply` (publish at once) must be restricted to members or to a permission that agents do not hold.

4. **`is_current`.** It is `true` for rows in the live catalog. It is a fast way to filter for current entities and new sales. The list of rows in each catalog (catalog membership) is the source of truth.
1. **Catalog.** It contains the full list of products and benefits at a point in time. It has a per-organization incremental `version` and a state: `draft`, `live`, or `archived`. Every organization has at most one draft and one live catalog.
2. **Copy-on-write** for products and benefits. Every change to a product or benefit creates a new row in the draft catalog. Once the catalog is published, those rows are locked.
3. **`external_id` is the stable name.** It is required for every product, benefit, and meter. A row is unique by `(organization_id, external_id, version)`, where `version` is the catalog where the row was created.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: This uniqueness rule makes meters catalog-versioned, contradicting the stated v0 scope. Limit the catalog-version uniqueness rule to products and benefits, and clarify meter identity in v0.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At handbook/engineering/design-documents/versioning.mdx, line 86:

<comment>This uniqueness rule makes meters catalog-versioned, contradicting the stated v0 scope. Limit the catalog-version uniqueness rule to products and benefits, and clarify meter identity in v0.</comment>

<file context>
@@ -12,58 +12,83 @@ description: "How meters, products, and benefits get versions."
-4. **`is_current`.** It is `true` for rows in the live catalog. It is a fast way to filter for current entities and new sales. The list of rows in each catalog (catalog membership) is the source of truth.
+1. **Catalog.** It contains the full list of products and benefits at a point in time. It has a per-organization incremental `version` and a state: `draft`, `live`, or `archived`. Every organization has at most one draft and one live catalog.
+2. **Copy-on-write** for products and benefits. Every change to a product or benefit creates a new row in the draft catalog. Once the catalog is published, those rows are locked.
+3. **`external_id` is the stable name.** It is required for every product, benefit, and meter. A row is unique by `(organization_id, external_id, version)`, where `version` is the catalog where the row was created.
+4. **Catalog membership is the source of truth.** `catalog_products` and `catalog_benefits` list the rows in each catalog. "Current" means "in the live catalog", and it is always derived from membership. We don't store an `is_current` flag. If performance requires one later, it is a cache enforced by a partial unique index on `(organization_id, external_id) WHERE is_current`.
 
</file context>
Suggested change
3. **`external_id` is the stable name.** It is required for every product, benefit, and meter. A row is unique by `(organization_id, external_id, version)`, where `version` is the catalog where the row was created.
3. **`external_id` is the stable name.** It is required for every product, benefit, and meter. Product and benefit rows are unique by `(organization_id, external_id, version)`, where `version` is the catalog where the row was created. Meters are not catalog-versioned in v0.

cursor Bot pushed a commit that referenced this pull request Oct 21, 2026
Brings in the review from #15684 and the follow-ups: declarative config only,
no migrations DSL (move endpoints instead), meters not versioned, grants
re-pointed by benefit external_id, polar init for adoption, git-style catalog
hashes, apply uploads a draft by default with --publish, and dashboard edits
in the same draft. Registers the page in the handbook sidebar.

Co-authored-by: Petru Rares Sincraian <psincraian@outlook.com>
Base automatically changed from feat-versioning to main October 22, 2026 10:05
@mintlify

mintlify Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
Handbook 🟢 Ready View Preview Oct 22, 2026, 10:07 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

opensource-fork-sync Bot pushed a commit to cla7aye15I4nd/opensource-polarsource__polar that referenced this pull request Oct 22, 2026
* feat: add versioning desing doc

* feat: versionig v2

* Revert next-dev generated AGENTS.md/CLAUDE.md in apps/web

These were auto-written by `next dev` and accidentally swept into the
versioning commit; unrelated to this PR.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: answered questions

* docs: narrow versioning design doc scope

Brings in the review from polarsource#15684 and the follow-ups: declarative config only,
no migrations DSL (move endpoints instead), meters not versioned, grants
re-pointed by benefit external_id, polar init for adoption, git-style catalog
hashes, apply uploads a draft by default with --publish, and dashboard edits
in the same draft. Registers the page in the handbook sidebar.

Co-authored-by: Petru Rares Sincraian <psincraian@outlook.com>

* docs: clarify versioning scope, draft iteration, and catalog hashes

- versioning is only for orgs with declarative config; dashboard changes stay
  live right away for other orgs
- apply replaces the draft in the same draft catalog, so merchants can iterate;
  it only needs --force when the dashboard changed the draft since the last
  apply or pull
- the catalog hash includes the previous published hash, so going back to an
  older definition gets a new hash

Co-authored-by: Petru Rares Sincraian <psincraian@outlook.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>

This branch was successfully deployed

3 active deployments
staging - handbook — 0c258bc9 Deployed Oct 22, 2026 by mintlify[bot]
Preview – polar-test — 0c258bc9 Deployed Oct 20, 2026 by vercel[bot]
Preview – orbit — 0c258bc9 Deployed Oct 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants