Skip to content

fix(catalog): create explicit plan versions - #2285

Merged
charlietlamb merged 3 commits into
devfrom
charlie/catalog-explicit-versions
Jul 17, 2026
Merged

fix(catalog): create explicit plan versions#2285
charlietlamb merged 3 commits into
devfrom
charlie/catalog-explicit-versions

Conversation

@charlietlamb

@charlietlamb charlietlamb commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • preserve explicit versions in new-plan previews and license preflight
  • create subsequent explicit versions through the existing versioning path
  • cover fresh same-batch versioned plans with pinned license dependencies

Verification

  • targeted integration regression passes against an isolated server
  • Mobbin 34-version catalog reaches preview analysis without the previous missing-version error
  • server typecheck and catalog dependency unit test pass
  • no catalog update was confirmed or written

Summary by cubic

Fix explicit plan versioning end-to-end. We now enforce sequential versions and process intra-plan versions in order, preventing missing versions and keeping license pins correct.

  • Bug Fixes

    • Preview and license preflight: honor plan.version (default to 1 if missing) and validate sequential targets against existing products.
    • Update path: when an explicit version is provided and the plan exists, create the next version via the versioning path with force_version; require latest + 1 and fail with a clear error if not.
    • Ordering: sortCatalogPlansByDependencies now ensures vN depends on vN-1, so versions for the same plan_id are applied in ascending order within a batch.
    • Tests: added an integration test for reversed-order, same-batch explicit versions with pinned license dependencies, and a unit test that fresh plans must start at v1.
  • Dependencies

    • Refreshed bun.lock to pick up transitive updates.

Written for commit b294ed3. Summary will update on new commits.

Review in cubic

Greptile Summary

This PR fixes catalog plan versioning to honour explicit version fields supplied in the catalog update payload — previously the preview, preflight, and write paths all silently ignored or reset the caller-supplied version to 1.

  • Bug fixespreviewCatalogPlanUpdate now passes version ?? 1 instead of always 1 to new-plan previews, and catalogPlanPreflight uses plan.version ?? 1 so virtual products for license sync carry the correct version.
  • Bug fixes / ImprovementsupdateCatalog.upsertPlans grows a new code path: when a plan with an explicit version is absent in the DB but a lower version already exists, it validates the gap (version === latest.version + 1) and calls updateProduct with force_version: true instead of falling through to a plain create.
  • Improvements — A new integration test exercises a four-plan same-batch scenario (childId v1, parentId v1, childId v2, parentId v2) to confirm fresh explicit versions are created and same-batch pinned license references resolve correctly.

Confidence Score: 3/5

The write path in updateCatalog can silently produce an incomplete catalog if callers submit versions for the same plan_id out of ascending order — v2 params end up creating a v1 product, and the intended v2 is never written.

The core logic in upsertPlans depends on intra-plan plans being processed in ascending version order, but sortCatalogPlansByDependencies only topologically orders by license references — it provides no guarantee about version ordering within the same plan_id. A caller submitting [childId v2, childId v1] would silently get only v1 with correct data and no v2, with no error returned.

server/src/internal/catalog/actions/updateCatalog/updateCatalog.ts — the new explicit-version branch and the interaction with sortCatalogPlansByDependencies need a second look.

Important Files Changed

Filename Overview
server/src/internal/catalog/actions/updateCatalog/updateCatalog.ts Adds explicit version creation path in upsertPlans; the sort dependency guarantee is missing for intra-plan version ordering, which can silently produce wrong state if plans are not submitted in ascending version order.
server/src/internal/catalog/actions/catalogPlanPreflight.ts Preserves explicit version (plan.version ?? 1) for new plans in the virtual product built for license preflight; correct and minimal change.
server/src/internal/catalog/actions/previewUpdateCatalog/previewCatalogPlanUpdate.ts Stops discarding the incoming version field and uses it in the preview product (version ?? 1); straightforward and correct.
server/tests/integration/licenses/catalog-update/license-catalog-response.test.ts Adds an integration test for fresh explicit versions with same-batch pinned license dependencies; covers the happy path but relies on plans being submitted in ascending version order, leaving the reverse-order edge case untested.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[upsertPlans: process planParams] --> B{current = getFull with version?}
    B -- found --> C[existing update path]
    B -- not found --> D{version defined?}
    D -- no --> G[createProduct - fresh plan v1]
    D -- yes --> E[getFull latest version, allowNotFound]
    E --> F{latest found?}
    F -- no --> G
    F -- yes --> H{version === latest.version + 1?}
    H -- no --> I[throw ProductNotFoundError]
    H -- yes --> J[updateProduct force_version: true]
    J --> K[continue to next plan]
    G --> K
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[upsertPlans: process planParams] --> B{current = getFull with version?}
    B -- found --> C[existing update path]
    B -- not found --> D{version defined?}
    D -- no --> G[createProduct - fresh plan v1]
    D -- yes --> E[getFull latest version, allowNotFound]
    E --> F{latest found?}
    F -- no --> G
    F -- yes --> H{version === latest.version + 1?}
    H -- no --> I[throw ProductNotFoundError]
    H -- yes --> J[updateProduct force_version: true]
    J --> K[continue to next plan]
    G --> K
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
server/src/internal/catalog/actions/updateCatalog/updateCatalog.ts:162-190
**Intra-plan version ordering not enforced**

`sortCatalogPlansByDependencies` sorts by license dependencies but makes no guarantee about ascending version order within the same `plan_id`. If a caller submits `version: 2` before `version: 1` for a brand-new plan, `getFull(childId, allowNotFound: true)` returns `null` (no plan exists yet), so the v2 params fall through to `createProduct`, which always creates at version 1. Then when v1 is processed it finds the just-created product and updates it with the correct v1 items — but v2 is never written. The data ends up silently correct for v1 while v2 is permanently missing, with no error surfaced to the caller.

### Issue 2 of 2
server/src/internal/catalog/actions/updateCatalog/updateCatalog.ts:173-175
**`ProductNotFoundError` for a version-gap condition**

`ProductNotFoundError` is used when `version !== latest.version + 1`, but in this context the plan itself exists — the problem is that the requested version is not the next sequential one. The error class name implies the product wasn't found, which would be misleading in client-facing messages or logs (e.g., submitting `version: 4` when the latest is `version: 2`). A dedicated validation error with a message like "version must be exactly latest_version + 1" would be clearer.

Reviews (1): Last reviewed commit: "chore: refresh bun lockfile" | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

@capy-ai

capy-ai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Capy auto-review is paused for this organization because the usage-cycle auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

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

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
checkout Ignored Ignored Jul 17, 2026 11:29am
landing-page Ignored Ignored Jul 17, 2026 11:29am

Request Review

Comment thread server/src/internal/catalog/actions/updateCatalog/updateCatalog.ts
Comment thread server/src/internal/catalog/actions/updateCatalog/updateCatalog.ts Outdated

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

All reported issues were addressed

Confidence score: 5/5

  • Safe to merge after the addressed issues were fixed.

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

Re-trigger cubic

Comment thread server/src/internal/catalog/actions/updateCatalog/updateCatalog.ts
Comment thread server/src/internal/catalog/actions/updateCatalog/updateCatalog.ts
@charlietlamb
charlietlamb merged commit 30d7153 into dev Jul 17, 2026
13 checks passed
@charlietlamb
charlietlamb deleted the charlie/catalog-explicit-versions branch July 17, 2026 16:21
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