From fbf69fbc1f3603fe26084f3c23e352956bcd2c0c Mon Sep 17 00:00:00 2001 From: Michal Lehotsky Date: Sun, 14 Jun 2026 16:34:47 -0700 Subject: [PATCH 1/5] fix(db): normalize legacy 'general fund' initiative_type to 'general_fund' Two rows migrated from DynamoDB in 2022 have initiative_type = 'general fund' (space) instead of the canonical 'general_fund' (underscore) used by all new code. This causes them to be invisible on the General Funds filter tab and rejected by ValidInitiativeTypes on any update attempt. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Michal Lehotsky --- .../002_normalize_initiative_types.up.sql | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 backend/db/migrations/002_normalize_initiative_types.up.sql diff --git a/backend/db/migrations/002_normalize_initiative_types.up.sql b/backend/db/migrations/002_normalize_initiative_types.up.sql new file mode 100644 index 00000000..f73f7542 --- /dev/null +++ b/backend/db/migrations/002_normalize_initiative_types.up.sql @@ -0,0 +1,22 @@ +-- Copyright The Linux Foundation and each contributor to LFX. +-- SPDX-License-Identifier: MIT +-- ============================================ +-- Migration: Normalize legacy initiative_type values +-- Created: 2026-06-14 +-- +-- Legacy DynamoDB rows imported with initiative_type = 'general fund' +-- (space-separated display string). The canonical value used by all new +-- code is 'general_fund' (underscore). Normalize the 2 legacy rows so +-- that the General Funds filter tab and type validation work correctly. +-- ============================================ + +BEGIN; + +SET LOCAL search_path TO crowdfunding, public; + +UPDATE initiatives +SET initiative_type = 'general_fund', + updated_on = NOW() +WHERE initiative_type = 'general fund'; + +COMMIT; From 78aada25ca26f41dcc67030e6bd88069dfb19740 Mon Sep 17 00:00:00 2001 From: Michal Lehotsky Date: Sun, 14 Jun 2026 16:40:29 -0700 Subject: [PATCH 2/5] =?UTF-8?q?fix(migration):=20normalise=20'general=20fu?= =?UTF-8?q?nd'=20=E2=86=92=20'general=5Ffund'=20at=20source?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Python migration script restored DynamoDB's entityType quirk as 'general fund' (space) instead of the canonical 'general_fund' (underscore) used by ValidInitiativeTypes and all frontend code. Fix it at the source so production migration never produces the wrong value. The SQL migration (002) is kept to fix environments that ran the old script (e.g. DEV already has 2 affected rows). Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Michal Lehotsky --- .../migrations/002_normalize_initiative_types.up.sql | 12 ++++++++---- backend/db/scripts/migrate_dynamo_to_postgres.py | 10 ++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/backend/db/migrations/002_normalize_initiative_types.up.sql b/backend/db/migrations/002_normalize_initiative_types.up.sql index f73f7542..3d1d641f 100644 --- a/backend/db/migrations/002_normalize_initiative_types.up.sql +++ b/backend/db/migrations/002_normalize_initiative_types.up.sql @@ -4,10 +4,14 @@ -- Migration: Normalize legacy initiative_type values -- Created: 2026-06-14 -- --- Legacy DynamoDB rows imported with initiative_type = 'general fund' --- (space-separated display string). The canonical value used by all new --- code is 'general_fund' (underscore). Normalize the 2 legacy rows so --- that the General Funds filter tab and type validation work correctly. +-- migrate_dynamo_to_postgres.py previously restored DynamoDB's entityType +-- quirk as 'general fund' (space) instead of the canonical 'general_fund' +-- (underscore) used by all new backend code. The script has been fixed, but +-- any environment that ran the old script (e.g. DEV) still has the wrong value. +-- This migration normalises those rows so the General Funds filter tab and +-- type validation work correctly. +-- +-- Safe to re-run: WHERE clause is a no-op if already normalised. -- ============================================ BEGIN; diff --git a/backend/db/scripts/migrate_dynamo_to_postgres.py b/backend/db/scripts/migrate_dynamo_to_postgres.py index b6b62eea..3c65234f 100644 --- a/backend/db/scripts/migrate_dynamo_to_postgres.py +++ b/backend/db/scripts/migrate_dynamo_to_postgres.py @@ -42,8 +42,9 @@ _as_uuid(projectId / entityId) so FK lookups from donations/subscriptions remain stable across re-runs. - DynamoDB entity type quirk: SaveEntity rewrites 'general fund' → 'initiative' - before every PutItem. Migration reverses this: - if entityType == 'initiative' → restore to 'general fund' + before every PutItem. Migration reverses this and normalises to the canonical + underscore form used by the new backend: + if entityType == 'initiative' → 'general_fund' - Budget.AmountInCents is serialised with json tag "amount" — access as budget.get("amount"), NOT budget.get("amountInCents"). - Excluded from schema (no DynamoDB write path / computed at read-time): @@ -723,10 +724,11 @@ def migrate_initiatives(cur, entities: list, projects: list, known_users: set, u known_initiative_ids.add(pg_id) # entityType quirk: SaveEntity rewrites 'general fund' → 'initiative' - # before every PutItem. Reverse it here. + # before every PutItem. Reverse it here and normalise to the canonical + # underscore form used by ValidInitiativeTypes in the new backend. entity_type: str = e.get("entityType") or "" if entity_type == "initiative": - entity_type = "general fund" + entity_type = "general_fund" # ── Core initiative row ────────────────────────────────────────── initiative_rows.append( From d4ff09fba4e098e80f61d4e77327b836ef5e8c6a Mon Sep 17 00:00:00 2001 From: Michal Lehotsky Date: Sun, 14 Jun 2026 16:51:20 -0700 Subject: [PATCH 3/5] =?UTF-8?q?fix(migration):=20remove=20002=20SQL=20migr?= =?UTF-8?q?ation=20=E2=80=94=20Python=20script=20fix=20is=20sufficient?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SQL migration was added to fix DEV rows written by the old script, but: - On prod the fixed Python script runs before the app deploys, so 002 is a no-op - SQL migrations run forever on every new environment — wrong mechanism for a one-time dev cleanup - DEV can be fixed with a manual UPDATE or by re-running the fixed script Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Michal Lehotsky --- .../002_normalize_initiative_types.up.sql | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 backend/db/migrations/002_normalize_initiative_types.up.sql diff --git a/backend/db/migrations/002_normalize_initiative_types.up.sql b/backend/db/migrations/002_normalize_initiative_types.up.sql deleted file mode 100644 index 3d1d641f..00000000 --- a/backend/db/migrations/002_normalize_initiative_types.up.sql +++ /dev/null @@ -1,26 +0,0 @@ --- Copyright The Linux Foundation and each contributor to LFX. --- SPDX-License-Identifier: MIT --- ============================================ --- Migration: Normalize legacy initiative_type values --- Created: 2026-06-14 --- --- migrate_dynamo_to_postgres.py previously restored DynamoDB's entityType --- quirk as 'general fund' (space) instead of the canonical 'general_fund' --- (underscore) used by all new backend code. The script has been fixed, but --- any environment that ran the old script (e.g. DEV) still has the wrong value. --- This migration normalises those rows so the General Funds filter tab and --- type validation work correctly. --- --- Safe to re-run: WHERE clause is a no-op if already normalised. --- ============================================ - -BEGIN; - -SET LOCAL search_path TO crowdfunding, public; - -UPDATE initiatives -SET initiative_type = 'general_fund', - updated_on = NOW() -WHERE initiative_type = 'general fund'; - -COMMIT; From 50276ad5d81982d2a599ffb7880673ed39085399 Mon Sep 17 00:00:00 2001 From: Michal Lehotsky Date: Sun, 14 Jun 2026 18:15:16 -0700 Subject: [PATCH 4/5] =?UTF-8?q?fix(migration):=20normalise=20ostif=20?= =?UTF-8?q?=E2=86=92=20security=5Faudit=20on=20migration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OSTIF initiatives are security audits run by the Open Source Technology Improvement Fund. Reclassify them to security_audit so they match ValidInitiativeTypes and appear correctly in the UI filter. The OSTIF-specific detail block still reads the raw DynamoDB entityType (before normalisation) to correctly populate initiative_ostif_detail. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Michal Lehotsky --- .../db/scripts/migrate_dynamo_to_postgres.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/backend/db/scripts/migrate_dynamo_to_postgres.py b/backend/db/scripts/migrate_dynamo_to_postgres.py index 3c65234f..6c52efa9 100644 --- a/backend/db/scripts/migrate_dynamo_to_postgres.py +++ b/backend/db/scripts/migrate_dynamo_to_postgres.py @@ -41,10 +41,9 @@ - initiatives.id (UUID PK) is generated deterministically via _as_uuid(projectId / entityId) so FK lookups from donations/subscriptions remain stable across re-runs. -- DynamoDB entity type quirk: SaveEntity rewrites 'general fund' → 'initiative' - before every PutItem. Migration reverses this and normalises to the canonical - underscore form used by the new backend: - if entityType == 'initiative' → 'general_fund' +- DynamoDB entity type quirks — normalised to ValidInitiativeTypes canonical forms: + 'initiative' → 'general_fund' (SaveEntity rewrites 'general fund' before PutItem) + 'ostif' → 'security_audit' - Budget.AmountInCents is serialised with json tag "amount" — access as budget.get("amount"), NOT budget.get("amountInCents"). - Excluded from schema (no DynamoDB write path / computed at read-time): @@ -723,12 +722,15 @@ def migrate_initiatives(cur, entities: list, projects: list, known_users: set, u ) known_initiative_ids.add(pg_id) - # entityType quirk: SaveEntity rewrites 'general fund' → 'initiative' - # before every PutItem. Reverse it here and normalise to the canonical - # underscore form used by ValidInitiativeTypes in the new backend. + # Normalise DynamoDB entityType values to the canonical forms used by + # ValidInitiativeTypes in the new backend: + # 'initiative' → 'general_fund' (SaveEntity quirk: rewrites 'general fund' before PutItem) + # 'ostif' → 'security_audit' (OSTIF is a security-audit programme) entity_type: str = e.get("entityType") or "" if entity_type == "initiative": entity_type = "general_fund" + elif entity_type == "ostif": + entity_type = "security_audit" # ── Core initiative row ────────────────────────────────────────── initiative_rows.append( @@ -845,7 +847,9 @@ def migrate_initiatives(cur, entities: list, projects: list, known_users: set, u # ── OSTIF-specific detail (ostif entity type only) ─────────────── # entity.Detail interface{} is a domain.Detail struct when non-nil. # TypeDeserializer returns it as a plain dict. - if entity_type == "ostif": + # Use the raw DynamoDB entityType here — entity_type is already + # normalised to 'security_audit' by this point. + if e.get("entityType") == "ostif": raw_detail = e.get("detail") if isinstance(raw_detail, dict) and raw_detail: ostif_detail_rows.append( From d2324bcf0b38eedd6cf3eb4dc23add165323b609 Mon Sep 17 00:00:00 2001 From: Michal Lehotsky Date: Sun, 14 Jun 2026 18:24:00 -0700 Subject: [PATCH 5/5] fix(types): surface other and community under General Fund tab Legacy initiative types 'other' and 'community' have no dedicated UI representation. Rather than renaming them in the DB, surface them under the General Fund tab: - Backend: when filtering by general_fund, query also includes 'other' and 'community' rows via ANY($1) - Frontend card: map 'other' and 'community' to the general_fund card config (label, icon, colours) Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Michal Lehotsky --- .../infrastructure/db/initiative_repository.go | 10 ++++++++-- .../initiative-card/initiative-card.config.ts | 13 +++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/backend/internal/infrastructure/db/initiative_repository.go b/backend/internal/infrastructure/db/initiative_repository.go index 42a38c9c..636ffedb 100644 --- a/backend/internal/infrastructure/db/initiative_repository.go +++ b/backend/internal/infrastructure/db/initiative_repository.go @@ -199,8 +199,14 @@ func (r *InitiativeRepository) List(ctx context.Context, filter models.Initiativ argN++ } if filter.InitiativeType != "" { - where += fmt.Sprintf(" AND i.initiative_type = $%d", argN) - args = append(args, filter.InitiativeType) + if filter.InitiativeType == "general_fund" { + // 'other' and 'community' are legacy types that surface under the General Fund tab. + where += fmt.Sprintf(" AND i.initiative_type = ANY($%d)", argN) + args = append(args, []string{"general_fund", "other", "community"}) + } else { + where += fmt.Sprintf(" AND i.initiative_type = $%d", argN) + args = append(args, filter.InitiativeType) + } argN++ } if filter.Status != "" { diff --git a/frontend/app/components/shared/components/initiative-card/initiative-card.config.ts b/frontend/app/components/shared/components/initiative-card/initiative-card.config.ts index 523f4046..e62a1c0f 100644 --- a/frontend/app/components/shared/components/initiative-card/initiative-card.config.ts +++ b/frontend/app/components/shared/components/initiative-card/initiative-card.config.ts @@ -34,6 +34,19 @@ export const initiativeTypeConfigMap: Record = { colorClass: 'text-positive-600', gradient: 'linear-gradient(180deg, rgba(240, 253, 244, 0.5) 0%, rgba(255, 255, 255, 0.5) 50%)', }, + // Legacy types that surface under the General Fund tab without DB renaming. + other: { + label: 'General Fund', + icon: 'hand-holding-dollar', + colorClass: 'text-positive-600', + gradient: 'linear-gradient(180deg, rgba(240, 253, 244, 0.5) 0%, rgba(255, 255, 255, 0.5) 50%)', + }, + community: { + label: 'General Fund', + icon: 'hand-holding-dollar', + colorClass: 'text-positive-600', + gradient: 'linear-gradient(180deg, rgba(240, 253, 244, 0.5) 0%, rgba(255, 255, 255, 0.5) 50%)', + }, }; export const defaultInitiativeTypeConfig: InitiativeTypeConfig = {