--- name: sync-catalog description: > Sync a catalog plugin's generated code and domain layer after OpenAPI spec changes. Regenerates stubs, detects new fields, updates datastore entries, entity mappings, and service implementation. Interactive — asks the user which new fields to persist and make filterable. user-invocable: true --- # Sync Catalog Plugin Propagate OpenAPI spec changes through all downstream files for a catalog plugin. ## Phase 1: Identify Plugin Ask the user which plugin to sync. Validate by checking that these exist: - `api/openapi/src/plugins/.yaml` — the spec - `catalog/internal/plugins//plugin.go` — the plugin entry point - `catalog/internal/catalog/catalog/` — the domain package ## Phase 2: Regenerate Auto-Generated Code Run these three make targets in order: ```bash make api/openapi/catalog.yaml make -C catalog gen/openapi-server make -C catalog gen/openapi ``` Report what was regenerated. Check `go build ./catalog/...` — if it fails, proceed to Phase 4 (service sync) first since interface mismatches are the most common cause. **Note:** `catalog/internal/server/openapi/type_asserts_overrides.go` is a hand-maintained file that overrides auto-generated assert functions for polymorphic types (e.g., `AssertCatalogArtifactRequired`, `AssertFilterOptionRequired`). If `gen/openapi-server` regenerates `type_asserts.go` and the build fails with undefined assert function errors, check whether this overrides file needs updating for the new types. ## Phase 3: Sync Datastore Entries + Entity Mappings ### 3a: Detect new fields Read the plugin's OpenAPI spec (`api/openapi/src/plugins/.yaml`) and extract all properties from each entity schema under `components.schemas`. Read the current `DatastoreEntries` in `catalog/internal/plugins//plugin.go` and extract the registered property names (the string arguments to `.AddString()`, `.AddStruct()`, `.AddBoolean()`). Compute the diff: fields in the spec but not in DatastoreEntries. Exclude these fields from the diff (they are handled automatically by the schema layer, not as properties): - `id`, `name`, `externalId`, `external_id` - `createTimeSinceEpoch`, `lastUpdateTimeSinceEpoch` - `customProperties`, `custom_properties` - `nextPageToken`, `pageSize`, `size`, `items` (list-level fields) ### 3b: Ask user which fields to persist Present the diff to the user. For each new field show: - Field name - OpenAPI type - Suggested `Add*()` call based on type: - `string` → `AddString` - `boolean` → `AddBoolean` - `integer` / `number` → `AddString` (stored as string property by convention) - `array` / `object` → `AddStruct` Ask: "Which of these new fields should be persisted in the datastore?" (multi-select) ### 3c: Apply changes For each confirmed field, update three files: **1. DatastoreEntries** in `catalog/internal/plugins//plugin.go`: - Find the entity's `DatastoreEntry` block - Add the new `.Add*("")` call before the closing comma **2. Entity mappings** in `catalog/internal/catalog/catalog/service/entity_mappings_.go`: - Add entry to the properties map variable: ```go "": {Location: filter.PropertyTable, ValueType: filter., Column: ""}, ``` Where ValueType is: - `StringValueType` for string/integer/number - `BoolValueType` for boolean - `ArrayValueType` for array **3. Entity mappings test** in `catalog/internal/catalog/catalog/service/entity_mappings__test.go`: - Add entry to the expected properties map with the same definition ### 3d: Ask about filterability Ask the user: "Which of these fields should be directly filterable as query parameters?" (multi-select from the persisted fields) For filterable fields: - Add a field to the `ListOptions` struct in `catalog/internal/catalog/catalog/models/.go` - Add a WHERE clause in `applyListFilters` in `catalog/internal/catalog/catalog/service/.go`: ```go if listOptions. != nil { query = query.Where(" = ?", *listOptions.) } ``` ## Phase 4: Sync Service Implementation Read the regenerated service interface from `catalog/internal/server/openapi/api_.go`. Read the current service implementation from `catalog/internal/server/openapi/api__catalog_service_service.go`. Compare method signatures: - **Changed signatures**: Update the implementation method to match the new interface signature. Keep the existing body logic, just fix the parameters. - **New methods**: Add a stub implementation returning an empty/not-found response (same pattern as init-catalog Phase 6). - **Removed methods**: Delete the implementation method. If the service implementation file doesn't exist yet (e.g., it was deleted during a regen), recreate it following the init-catalog Phase 6 pattern. ## Phase 5: Sync DB Provider + Property-to-API Mapping The db_provider (`catalog/internal/catalog/catalog/db_.go`) is the bridge between the repository layer and the OpenAPI models. When the spec gains new fields, the property-to-API mapping function must be updated to include them. ### 5a: Check if db_provider has real query methods Read `catalog/internal/catalog/catalog/db_.go`. If it still has the generated TODO stubs (no `List*` or `Get*` methods), wire it up: 1. **Export the type**: rename `dbXxxCatalogImpl` to `DBXxxCatalog` so the service impl can reference it. 2. **Add List method**: query the repository with `ListOptions`, convert results via mapping function, return the API list type with pagination. 3. **Add Get method**: parse the ID, call `GetByID`, convert via mapping function, return the API type or 404. 4. **Add a `ListParams` struct** for the method parameters (Name, SourceIDs, FilterQuery, OrderBy, SortOrder, NextPageToken, PageSize). Note: Plugins do NOT implement a `FindSources` method — sources are managed by the model catalog via the shared `/sources` endpoint with `assetType` filtering. Reference: `catalog/internal/catalog/modelcatalog/db_catalog.go` (`ListModels`, `GetModel`, `mapDBModelToAPIModel`). ### 5b: Sync the property-to-API mapping function Read the generated client model struct from `catalog/pkg/openapi/model_.go` to get all available fields and their Go types. Read the current `mapDBToAPI` function (or create it if missing). For each field in the API model that comes from a stored Property, ensure there's a `case` in the Properties switch: - **String fields** (`*string`): `res. = prop.StringValue` - **Boolean fields** (`*bool`): `res. = prop.BoolValue` - **Array fields** (`[]string`): JSON unmarshal from `prop.StringValue` - **Integer fields** (`*int32`): parse from `prop.IntValue` or `prop.StringValue` When new fields were added to DatastoreEntries in Phase 3, add corresponding cases here. ### 5c: Update RegisterRoutes if needed If the db_provider type or constructor changed (e.g., from unexported to exported, or new constructor parameters), update `RegisterRoutes` in `catalog/internal/plugins//plugin.go` to match. Ensure the service constructor receives the provider: ```go provider := .NewDBCatalog(p.services, p.loader.Sources) svc := openapi.NewCatalogServiceAPIService(provider) ``` ## Phase 6: Verify + Report ```bash go build ./catalog/... go test ./catalog/internal/catalog/catalog/... -count=1 ``` If build fails, read errors and fix. Common issues: - Unused imports after method removal - Missing imports after adding filter logic (`fmt`, `utils`) - Interface signature mismatches - Pointer vs value mismatches on list fields (`*int32` vs `int32`, `*string` vs `string`) Print summary: ``` Sync complete for plugin "": Regenerated: - api/openapi/catalog.yaml (merged spec) - catalog/internal/server/openapi/api_*.go (server stubs) - catalog/pkg/openapi/model_*.go (client types) Datastore entries updated: - Entity mappings updated: - DB provider: - Service implementation: - Build: PASS/FAIL Tests: PASS/FAIL Remaining manual work: - Add complex filter logic for fields that need more than equality matching - Update loader if new fields affect data ingestion ```