Skip to content

feat(versioning): unified version family for contracts and products (Phase 1)#446

Merged
larsgeorge-db merged 1 commit into
mainfrom
feat-version-family
May 28, 2026
Merged

feat(versioning): unified version family for contracts and products (Phase 1)#446
larsgeorge-db merged 1 commit into
mainfrom
feat-version-family

Conversation

@larsgeorge-db
Copy link
Copy Markdown
Collaborator

Summary

Introduces version_family_id as a canonical, immutable grouping key on both data_contracts and data_products so all versions of an entity are retrievable via a single indexed equality lookup. Replaces the brittle parent-walk + base_name heuristics with a stable family identity that survives renames and is propagated through every clone path.

This is Phase 1 of the PRD: backend foundation + shared version navigator in detail views. Subsequent phases (list-view collapse, unified EntityVersionPicker, reference-storage *_family_id columns) ship as follow-up PRs.

See PRD: docs/prds/prd-version-family-and-unified-selector.md (GH #442).

Backend

  • Alembic j1_add_version_family_id — adds column, backfills via recursive parent-walk CTE, marks NOT NULL on both tables.
  • before_insert listeners on DataContractDb/DataProductDb guarantee the NOT NULL invariant on every insert path (repo, demo loader, direct ORM construction in tests).
  • Repositories: get_family_versions + list_family_representatives with personal-draft visibility filtering and a window-function-based latest-visible collapse.
  • All clone paths (create_new_version, ContractCloner, clone_product_for_new_version) propagate version_family_id from the source.
  • ContractCloner no longer mutates name with _v<version> — name stays stable across the family; version is its own column.
  • GET /api/data-contracts/{id}/versions rewritten and new GET /api/data-products/{id}/versions added — both visibility-aware, returning a lightweight EntityVersionRow shape.

Frontend

  • versionFamilyId on DataContract, DataContractSummary, DataProduct.
  • New shared components/common/version-selector.tsx — generic over entityKind: 'contract' | 'product'.
  • New components/common/version-navigator.tsx — prev/next chevrons + selector, hides itself for single-version families.
  • Wired into both data-contract-details.tsx and data-product-details.tsx. Old contract-specific selector deleted.

Deferred to follow-up PRs

  1. List-view collapse?include_history toggle, family/version badge, expandable history.
  2. EntityVersionPicker — unified picker with entity-pinned vs family-follow-latest modes.
  3. Reference storage*_family_id nullable columns + check constraints on join tables (output port → contract first).

Test plan

  • 40 unit tests passing (test_version_family.py + repo + clone regression)
  • Migration backfill verified on local DB (existing families correctly grouped)
  • Live API smoke-test of /api/data-contracts/{id}/versions returns proper EntityVersionRow payload
  • Manual: clone a contract, confirm name no longer gets _v… suffix and VersionNavigator appears in details view
  • Manual: clone a product, same checks
  • Manual: consumer/draft visibility on /versions endpoint
  • Playwright regression on contract/product details navigation

Introduce version_family_id as a canonical, immutable grouping key on
both data_contracts and data_products so all versions of an entity are
retrievable via a single indexed equality lookup. Replaces the brittle
parent-walk + base_name heuristics with a stable family identity that
survives renames and is propagated through every clone path.

Backend
- Alembic j1_add_version_family_id: add column, backfill via recursive
  parent-walk CTE, then mark NOT NULL on both tables.
- before_insert event listeners on DataContractDb/DataProductDb seed
  id and version_family_id so the NOT NULL invariant holds on any
  insert path (repo, demo loader, direct ORM construction).
- Repositories get get_family_versions and list_family_representatives
  with personal-draft visibility filtering and a window-function-based
  latest-visible collapse.
- All clone paths (create_new_version, ContractCloner,
  clone_product_for_new_version) propagate version_family_id from the
  source. ContractCloner no longer mutates name with a _v<version>
  suffix — name stays stable across the family, version is its own
  column.
- GET /api/data-contracts/{id}/versions rewritten and new
  GET /api/data-products/{id}/versions added — both visibility-aware
  and returning a lightweight EntityVersionRow shape.

Frontend
- versionFamilyId on DataContract, DataContractSummary, DataProduct.
- New shared components/common/version-selector.tsx generic over
  entityKind: 'contract' | 'product'.
- New components/common/version-navigator.tsx wraps prev/next chevrons
  + selector and hides itself for single-version families.
- Wired into data-contract-details and data-product-details, replacing
  the contract-specific implementation.

Deferred to follow-up PRs: list-view collapse with include_history
toggle, unified EntityVersionPicker (entity vs family-follow-latest),
and *_family_id reference-storage columns on join tables.

See docs/prds/prd-version-family-and-unified-selector.md (PRD #442).
@larsgeorge-db larsgeorge-db force-pushed the feat-version-family branch from 363467b to 0d2da07 Compare May 28, 2026 15:02
@larsgeorge-db larsgeorge-db merged commit 0ff3d77 into main May 28, 2026
6 of 7 checks passed
larsgeorge-db added a commit that referenced this pull request May 28, 2026
…se 2)

Make the data-contracts and data-products list views show one row per
version_family_id by default, with a "Show all versions" toggle that
expands every visible version. Each collapsed row surfaces a small
"N versions" badge so users can see at a glance which entities have
history, without paying the row-count cost.

Builds on PRD #442 / PR #446 (Phase 1).

Backend
- DataContractSummary + DataProduct API models gain a `versionCount`
  (alias `version_count`) field. Populated only on the collapsed view
  so the expanded view never lies about per-row counts.
- DataContractsManager._query_contracts: drop the legacy base_name
  Python-side dedupe in favor of canonical version_family_id grouping,
  and return (rows, family_counts) so the builder can attach counts
  without a second query. Same shape feeds both collapsed and
  include_history paths.
- DataContractsManager._build_contract_summaries now accepts an
  optional family_counts map and emits parent/family/draft/changeSummary
  fields that were previously left null on the summary even though they
  exist on the row.
- list_contracts_from_db and the /data-contracts route propagate the
  include_history flag end-to-end (route was already accepting it).
- DataProductsManager.list_products: collapse by version_family_id
  after the visibility cascade, attach version_count to the surviving
  row. include_history=True bypasses the collapse and suppresses the
  count.
- /data-products route surfaces include_history.

Frontend
- DataContractListItem and DataProduct gain `versionCount`.
- New components/common/version-count-badge.tsx — compact pill that
  hides itself for single-version families and otherwise renders a
  click-through history hint next to the version cell.
- data-contracts.tsx and data-products.tsx: family badge next to the
  version column + "Show all versions" switch in the toolbar; both
  list-fetch paths plumb include_history through the query string.

Tests
- 5 new unit tests cover the collapse default, the family-count emission,
  include_history bypass, and product-side parity.

Note: existing dev uvicorn instances have a stale --reload-dir pointing
at the pre-Phase-1 src/api layout (see /tmp/backend.log statreload trace).
Restart the backend to pick up the new include_history wiring locally.
larsgeorge-db added a commit that referenced this pull request May 28, 2026
…se 2)

Make the data-contracts and data-products list views show one row per
version_family_id by default, with a "Show all versions" toggle that
expands every visible version. Each collapsed row surfaces a small
"N versions" badge so users can see at a glance which entities have
history, without paying the row-count cost.

Builds on PRD #442 / PR #446 (Phase 1).

Backend
- DataContractSummary + DataProduct API models gain a `versionCount`
  (alias `version_count`) field. Populated only on the collapsed view
  so the expanded view never lies about per-row counts.
- DataContractsManager._query_contracts: drop the legacy base_name
  Python-side dedupe in favor of canonical version_family_id grouping,
  and return (rows, family_counts) so the builder can attach counts
  without a second query. Same shape feeds both collapsed and
  include_history paths.
- DataContractsManager._build_contract_summaries now accepts an
  optional family_counts map and emits parent/family/draft/changeSummary
  fields that were previously left null on the summary even though they
  exist on the row.
- list_contracts_from_db and the /data-contracts route propagate the
  include_history flag end-to-end (route was already accepting it).
- DataProductsManager.list_products: collapse by version_family_id
  after the visibility cascade, attach version_count to the surviving
  row. include_history=True bypasses the collapse and suppresses the
  count.
- /data-products route surfaces include_history.

Frontend
- DataContractListItem and DataProduct gain `versionCount`.
- New components/common/version-count-badge.tsx — compact pill that
  hides itself for single-version families and otherwise renders a
  click-through history hint next to the version cell.
- data-contracts.tsx and data-products.tsx: family badge next to the
  version column + "Show all versions" switch in the toolbar; both
  list-fetch paths plumb include_history through the query string.

Tests
- 5 new unit tests cover the collapse default, the family-count emission,
  include_history bypass, and product-side parity.

Note: existing dev uvicorn instances have a stale --reload-dir pointing
at the pre-Phase-1 src/api layout (see /tmp/backend.log statreload trace).
Restart the backend to pick up the new include_history wiring locally.
larsgeorge-db added a commit that referenced this pull request May 28, 2026
…se 2) (#447)

Make the data-contracts and data-products list views show one row per
version_family_id by default, with a "Show all versions" toggle that
expands every visible version. Each collapsed row surfaces a small
"N versions" badge so users can see at a glance which entities have
history, without paying the row-count cost.

Builds on PRD #442 / PR #446 (Phase 1).

Backend
- DataContractSummary + DataProduct API models gain a `versionCount`
  (alias `version_count`) field. Populated only on the collapsed view
  so the expanded view never lies about per-row counts.
- DataContractsManager._query_contracts: drop the legacy base_name
  Python-side dedupe in favor of canonical version_family_id grouping,
  and return (rows, family_counts) so the builder can attach counts
  without a second query. Same shape feeds both collapsed and
  include_history paths.
- DataContractsManager._build_contract_summaries now accepts an
  optional family_counts map and emits parent/family/draft/changeSummary
  fields that were previously left null on the summary even though they
  exist on the row.
- list_contracts_from_db and the /data-contracts route propagate the
  include_history flag end-to-end (route was already accepting it).
- DataProductsManager.list_products: collapse by version_family_id
  after the visibility cascade, attach version_count to the surviving
  row. include_history=True bypasses the collapse and suppresses the
  count.
- /data-products route surfaces include_history.

Frontend
- DataContractListItem and DataProduct gain `versionCount`.
- New components/common/version-count-badge.tsx — compact pill that
  hides itself for single-version families and otherwise renders a
  click-through history hint next to the version cell.
- data-contracts.tsx and data-products.tsx: family badge next to the
  version column + "Show all versions" switch in the toolbar; both
  list-fetch paths plumb include_history through the query string.

Tests
- 5 new unit tests cover the collapse default, the family-count emission,
  include_history bypass, and product-side parity.

Note: existing dev uvicorn instances have a stale --reload-dir pointing
at the pre-Phase-1 src/api layout (see /tmp/backend.log statreload trace).
Restart the backend to pick up the new include_history wiring locally.
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