Skip to content

feat: add creator-scoped config item properties#1930

Closed
adityathebe wants to merge 7 commits into
mainfrom
feat/config-property-provenance
Closed

feat: add creator-scoped config item properties#1930
adityathebe wants to merge 7 commits into
mainfrom
feat/config-property-provenance

Conversation

@adityathebe
Copy link
Copy Markdown
Member

@adityathebe adityathebe commented Apr 28, 2026

Config item properties can now carry creator provenance for scrapers and people.

Add a Postgres function that atomically replaces only the property slice owned by a specific creator, preserving user-created, other-scraper, and legacy unowned properties. Expose a Go helper for callers, register the RPC for catalog RBAC, and extend property models with created_by / creator_type.

This also supports empty incoming properties as creator now owns zero properties, allowing stale scraper-owned properties to be removed without clobbering manual properties.

Summary by CodeRabbit

  • New Features

    • Configuration item properties now track ownership, allowing updates attributed to specific creators while preserving others' properties.
    • Support for removing a single owner-owned property without affecting other owners' or legacy properties.
  • Tests

    • Added tests covering updates, single-property deletion, ownership preservation, and concurrent updates from multiple sources.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Walkthrough

Adds an “owned properties” representation for ConfigItem properties, a pair of SQL functions to merge or delete creator-scoped properties, Go model types and helpers to persist/scan them, RBAC entries for the new RPCs, and tests/fixtures updated to use the new OwnedProperties shape. Updates cloning and benchmarks to the new type.

Changes

Owned-properties feature (single cohesive DAG)

Layer / File(s) Summary
Data shape / types
models/owned_property.go, models/config.go
Introduces OwnedProperty/OwnedProperties and changes ConfigItem.Properties from *types.Properties*OwnedProperties. Adds JSON marshal/unmarshal and conversion helpers NewOwnedProperties / AsProperties.
Persistence / GORM integration
models/owned_property.go (Value, Scan, GormDataType, GormDBDataType, GormValue)
Implements DB driver/GORM hooks to store/load OwnedProperties as JSON/JSONB/text across dialects.
Core DB behavior
functions/config_item_properties.sql
Adds update_config_item_properties(...) that stamps incoming properties with creator_type/created_by, removes/merges same-owner entries while preserving others and legacy entries, conditionally updates config_items.properties, and returns (changed, properties). Adds delete_config_item_property(...) to remove a single owned property by name+owner.
Model wiring / RPC call
models/owned_property.go (UpdateConfigItemProperties)
Adds UpdateConfigItemProperties which serializes incoming OwnedProperties, calls the SQL function (SELECT changed, properties FROM update_config_item_properties(...)), handles missing-row errors, and unmarshals returned merged properties into OwnedProperties result.
Model cloning / internal helpers
models/clone.go
Switches ConfigItem.Clone to clone OwnedProperties via new cloneOwnedPropertiesPtr/cloneOwnedProperties helpers and removes prior type-based cloners; preserves deep-copy semantics and nil-tolerance.
Authorization mapping
rbac/objects.go
Registers RPC endpoints "rpc/update_config_item_properties" and "rpc/delete_config_item_property" to policy.ObjectCatalog in dbResourceObjMap.
Tests / behavior coverage
tests/config_item_properties_function_test.go
Adds Ginkgo/Gomega tests covering merging semantics, change detection, deletion of single owned property, empty-incoming removal behavior, missing-config error path, and concurrent updates from multiple scrapers.
Test fixtures & call sites
query/template_test.go, tests/fixtures/dummy/config.go, tests/fixtures/dummy/config_demo_cluster.go, types/resource_selector_test.go, bench/insertion_test.go
Updates fixtures, tests, and benchmark helper to construct Properties using models.NewOwnedProperties(...)/pointer wrappers and to accept models.OwnedProperties return types.

Sequence Diagram

sequenceDiagram
  participant Client
  participant App as Go Model
  participant DB as Postgres
  Client->>App: call UpdateConfigItemProperties(configID, creatorType, createdBy, props)
  App->>DB: SELECT changed, properties FROM update_config_item_properties(configID, creatorType, createdBy, props::jsonb)
  DB-->>App: returns (changed, merged_properties_json)
  App->>App: unmarshal merged_properties_json -> OwnedProperties
  App-->>Client: return {Changed, Properties}
Loading

Possibly related PRs

  • flanksource/duty#1936: updates buildBenchProperties to return models.OwnedProperties (related change to benchmark helper and types).
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add creator-scoped config item properties' accurately and concisely describes the main change: introducing creator attribution to config item properties.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/config-property-provenance
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/config-property-provenance

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 28, 2026

Benchstat (Other)

Base: 56f947af2dcf5eb826679bd512131381376d72c0
Head: 4f6025d978244c09fa59cda00c6ffefe74606fb1

✅ No significant performance changes detected

Full benchstat output
goos: linux
goarch: amd64
pkg: github.com/flanksource/duty/bench
cpu: AMD EPYC 7763 64-Core Processor                
                                                       │ bench-head.txt │
                                                       │     sec/op     │
InsertionForRowsWithAliases/external_users.aliases-4       589.2µ ±  4%
InsertionForRowsWithAliases/config_items.external_id-4     1.109m ± 12%
InsertionOfConfigsWithProperties-4                         3.757m ±  2%
UpdateOfConfigsWithProperties-4                            7.510m ±  2%
ResourceSelectorConfigs/name-4                             221.9µ ±  3%
ResourceSelectorConfigs/name_and_type-4                    238.7µ ±  2%
ResourceSelectorConfigs/tags-4                             29.75m ±  5%
ResourceSelectorQueryBuild/name-4                          43.09µ ±  2%
ResourceSelectorQueryBuild/name_and_type-4                 63.50µ ±  3%
ResourceSelectorQueryBuild/tags-4                          17.32µ ±  0%
geomean                                                    517.5µ

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 28, 2026

Benchstat (RLS)

Base: 56f947af2dcf5eb826679bd512131381376d72c0
Head: 4f6025d978244c09fa59cda00c6ffefe74606fb1

✅ No significant performance changes detected

Full benchstat output
goos: linux
goarch: amd64
pkg: github.com/flanksource/duty/bench
cpu: AMD EPYC 9V74 80-Core Processor                
                                               │ bench-head.txt │
                                               │     sec/op     │
RLS/Sample-15000/catalog_changes/Without_RLS-4      4.447m ± 2%
RLS/Sample-15000/catalog_changes/With_RLS-4         121.9m ± 0%
RLS/Sample-15000/config_changes/Without_RLS-4       4.395m ± 1%
RLS/Sample-15000/config_changes/With_RLS-4          121.3m ± 2%
RLS/Sample-15000/config_detail/Without_RLS-4        3.400m ± 2%
RLS/Sample-15000/config_detail/With_RLS-4           117.3m ± 0%
RLS/Sample-15000/config_names/Without_RLS-4         11.87m ± 1%
RLS/Sample-15000/config_names/With_RLS-4            116.7m ± 2%
RLS/Sample-15000/config_summary/Without_RLS-4       53.87m ± 0%
RLS/Sample-15000/config_summary/With_RLS-4          682.2m ± 1%
RLS/Sample-15000/configs/Without_RLS-4              6.438m ± 2%
RLS/Sample-15000/configs/With_RLS-4                 116.9m ± 0%
RLS/Sample-15000/analysis_types/Without_RLS-4       3.401m ± 2%
RLS/Sample-15000/analysis_types/With_RLS-4          3.387m ± 3%
RLS/Sample-15000/analyzer_types/Without_RLS-4       3.237m ± 1%
RLS/Sample-15000/analyzer_types/With_RLS-4          3.234m ± 4%
RLS/Sample-15000/change_types/Without_RLS-4         4.448m ± 1%
RLS/Sample-15000/change_types/With_RLS-4            4.465m ± 1%
RLS/Sample-15000/config_classes/Without_RLS-4       2.814m ± 3%
RLS/Sample-15000/config_classes/With_RLS-4          115.2m ± 0%
RLS/Sample-15000/config_types/Without_RLS-4         3.438m ± 1%
RLS/Sample-15000/config_types/With_RLS-4            116.1m ± 0%
geomean                                             17.12m

@adityathebe
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@adityathebe adityathebe force-pushed the feat/config-property-provenance branch from 1e7ea8c to cc86af5 Compare April 28, 2026 12:07
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
functions/config_item_properties.sql (1)

20-54: Compute merged JSON once to reduce duplication and drift risk.

Line 23-54 repeats the same merge expression in both SET and WHERE. Consider factoring it into one CTE value and reusing it for both update and distinct-check.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@functions/config_item_properties.sql` around lines 20 - 54, Extract the
repeated JSON merge expression into a single CTE (e.g., name it merged_incoming
or merged_props) computed from stamped and the filtered existing properties,
then reference that CTE in the UPDATE: use merged_props for the SET properties
value and for the IS DISTINCT FROM check; update the current CTE chain so that
stamped -> merged_props -> updated (which performs UPDATE config_items ci SET
properties = (SELECT merged FROM merged_props) WHERE ci.id = p_config_id AND
ci.properties IS DISTINCT FROM (SELECT merged FROM merged_props)) to remove
duplication and ensure a single source of truth for the merged JSON used by
updated.
tests/config_item_properties_function_test.go (1)

104-114: Prefer dummy fixture resources before creating new config items in tests.

Consider reusing tests/fixtures/dummy/all.go resources first, then tailoring properties as needed, to keep fixture setup consistent across suites.

Based on learnings: Use resources from the dummy dataset in tests/fixtures/dummy/all.go in tests before creating new test resources.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/config_item_properties_function_test.go` around lines 104 - 114,
seedConfigItemWithProperties currently creates new ConfigItem records directly;
instead, reuse the dummy fixtures from tests/fixtures/dummy/all.go and then only
mutate or extend their properties for this test. Modify
seedConfigItemWithProperties (and callers) to first obtain an existing dummy
resource (e.g., the exported dummy variables in tests/fixtures/dummy/all.go) to
derive Type, ExternalID or base Config, then merge/override its Properties with
the provided properties and persist that updated record via
DefaultContext.DB().Create/Save; reference the seedConfigItemWithProperties
function and DefaultContext.DB() when making the change so the test uses fixture
resources rather than creating brand-new standalone items.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@models/config_item_properties.go`:
- Around line 25-31: The SQL function call using tx.Raw(...).Scan(&result) can
yield zero rows when configID doesn't exist, which Scan doesn't treat as an
error; after executing the query (the db returned by tx.Raw(...).Scan(&result)),
check the GORM RowsAffected value and if it is 0 return a descriptive error
(e.g., NotFound/invalid configID) instead of the zero-value
UpdateConfigItemPropertiesResult; ensure you still handle and return any non-nil
err from Scan. Use the existing symbols tx.Raw(...).Scan(&result), the result
variable and the UpdateConfigItemPropertiesResult return type to locate where to
add the RowsAffected check and error return.

---

Nitpick comments:
In `@functions/config_item_properties.sql`:
- Around line 20-54: Extract the repeated JSON merge expression into a single
CTE (e.g., name it merged_incoming or merged_props) computed from stamped and
the filtered existing properties, then reference that CTE in the UPDATE: use
merged_props for the SET properties value and for the IS DISTINCT FROM check;
update the current CTE chain so that stamped -> merged_props -> updated (which
performs UPDATE config_items ci SET properties = (SELECT merged FROM
merged_props) WHERE ci.id = p_config_id AND ci.properties IS DISTINCT FROM
(SELECT merged FROM merged_props)) to remove duplication and ensure a single
source of truth for the merged JSON used by updated.

In `@tests/config_item_properties_function_test.go`:
- Around line 104-114: seedConfigItemWithProperties currently creates new
ConfigItem records directly; instead, reuse the dummy fixtures from
tests/fixtures/dummy/all.go and then only mutate or extend their properties for
this test. Modify seedConfigItemWithProperties (and callers) to first obtain an
existing dummy resource (e.g., the exported dummy variables in
tests/fixtures/dummy/all.go) to derive Type, ExternalID or base Config, then
merge/override its Properties with the provided properties and persist that
updated record via DefaultContext.DB().Create/Save; reference the
seedConfigItemWithProperties function and DefaultContext.DB() when making the
change so the test uses fixture resources rather than creating brand-new
standalone items.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d948e48d-4931-497b-bd35-ca10ec2a5bdc

📥 Commits

Reviewing files that changed from the base of the PR and between 5c5798c and 1e7ea8c.

📒 Files selected for processing (5)
  • functions/config_item_properties.sql
  • models/config_item_properties.go
  • rbac/objects.go
  • tests/config_item_properties_function_test.go
  • types/properties.go

Comment thread models/owned_property.go Outdated
@adityathebe adityathebe force-pushed the feat/config-property-provenance branch 2 times, most recently from 127ef94 to 9876999 Compare April 28, 2026 15:38
@adityathebe adityathebe marked this pull request as ready for review April 28, 2026 15:51
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@functions/config_item_properties.sql`:
- Around line 120-144: computed currently reads ci.properties without locking,
allowing race conditions where two concurrent transactions overwrite each
other's changes; modify the computed CTE to select the target config_items row
using a SELECT ... FOR UPDATE (or FOR NO KEY UPDATE) against config_items
filtered by p_config_id so the row is locked before deriving new_properties,
keeping references to computed, updated, ci.properties, and p_config_id to
locate and update the CTE logic; ensure the lock is taken in the same
transaction before the jsonb aggregation so updated only runs after the locked
snapshot is used to compute and compare new_properties.

In `@models/owned_property.go`:
- Around line 96-123: The Scan implementation for OwnedProperties only accepts
[]byte but GormDBDataType advertises TEXT/NVARCHAR which database drivers often
return as string, so update the OwnedProperties.Scan method to also accept
string (and nullable string variants if used) by adding a type switch case for
string that converts it to []byte (or directly json.Unmarshal from the string)
before calling json.Unmarshal; reference the OwnedProperties.Scan method and
GormDBDataType to locate where to change this behavior and ensure Scan returns
the same zero-value on nil as it currently does.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 40b554c4-fd84-48ae-a450-8e285b95e529

📥 Commits

Reviewing files that changed from the base of the PR and between 1e7ea8c and 92ea337.

📒 Files selected for processing (10)
  • functions/config_item_properties.sql
  • models/clone.go
  • models/config.go
  • models/owned_property.go
  • query/template_test.go
  • rbac/objects.go
  • tests/config_item_properties_function_test.go
  • tests/fixtures/dummy/config.go
  • tests/fixtures/dummy/config_demo_cluster.go
  • types/resource_selector_test.go
✅ Files skipped from review due to trivial changes (1)
  • rbac/objects.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/config_item_properties_function_test.go

Comment thread functions/config_item_properties.sql
Comment thread models/owned_property.go
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 28, 2026

Gavel results

Gavel exited with code .

View full results

@adityathebe adityathebe requested a review from moshloop April 28, 2026 16:37
@adityathebe adityathebe force-pushed the feat/config-property-provenance branch 2 times, most recently from f27e903 to 9f6a3f8 Compare May 4, 2026 16:40
Config item properties can be produced by scrapers and people, but previous writes had no ownership metadata or scoped replacement semantics.

Add property creator provenance fields and a Postgres function that atomically replaces only the properties owned by a specific creator. The function preserves other creators and legacy unowned properties, returns the merged JSON for cache updates, and supports empty input as removal of the creator-owned slice.

Expose the SQL function through a model helper and catalog RPC RBAC mapping so config-db can route scraper and manual property updates through the same path.
UpdateConfigItemProperties previously used Raw().Scan() without checking whether the SQL function returned any rows. When the config item ID did not exist, GORM left the result at its zero value and returned nil, making the missing item look like an unchanged update.

Capture the scan result, return scan errors as before, and return an explicit not-found error when RowsAffected is zero. Add coverage for the missing config item case.
Concurrent updates for different property creators could derive their replacement JSON from the same stale config_items.properties snapshot. The later writer could then overwrite the earlier creator's slice, breaking the preservation guarantee.

Lock the target config_items row before computing new_properties so concurrent calls serialize on the latest row state. Also allow OwnedProperties.Scan to accept string values returned by text-backed drivers such as sqlite or sqlserver.
Add a PostgREST-accessible SQL function for deleting a single config item property owned by a specific creator.\n\nThe delete RPC removes only the matching owned property by name while preserving other owners and legacy unowned properties, so UI deletes do not replace the user's entire property list.
@adityathebe adityathebe force-pushed the feat/config-property-provenance branch from 9f6a3f8 to 4f6025d Compare May 5, 2026 08:07
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
functions/config_item_properties.sql (1)

174-213: ⚡ Quick win

Duplicate subquery computation in delete_config_item_property.

The same jsonb_agg(prop ...) filter is computed twice — once in the SET clause and once in the WHERE IS DISTINCT FROM guard. Refactoring to the same locked → computed → updated CTE pattern used in update_config_item_properties computes the result once, adds FOR UPDATE for style consistency, and makes the no-op guard a simple column comparison.

♻️ Proposed refactor
-  RETURN QUERY
-  WITH updated AS (
-    UPDATE config_items ci
-    SET properties =
-      COALESCE(
-        (
-          SELECT jsonb_agg(prop ORDER BY ord)
-          FROM jsonb_array_elements(COALESCE(ci.properties, '[]'::jsonb))
-            WITH ORDINALITY AS existing(prop, ord)
-          WHERE (
-            prop->>'creator_type' = p_creator_type
-            AND prop->>'created_by' = p_created_by::text
-            AND prop->>'name' = p_property_name
-          ) IS NOT TRUE
-        ),
-        '[]'::jsonb
-      )
-    WHERE ci.id = p_config_id
-      AND ci.properties IS DISTINCT FROM
-      COALESCE(
-        (
-          SELECT jsonb_agg(prop ORDER BY ord)
-          FROM jsonb_array_elements(COALESCE(ci.properties, '[]'::jsonb))
-            WITH ORDINALITY AS existing(prop, ord)
-          WHERE (
-            prop->>'creator_type' = p_creator_type
-            AND prop->>'created_by' = p_created_by::text
-            AND prop->>'name' = p_property_name
-          ) IS NOT TRUE
-        ),
-        '[]'::jsonb
-      )
-    RETURNING true AS changed, ci.properties
-  )
-  SELECT updated.changed, updated.properties
-  FROM updated
-  UNION ALL
-  SELECT false AS changed, ci.properties
-  FROM config_items ci
-  WHERE ci.id = p_config_id
-    AND NOT EXISTS (SELECT 1 FROM updated);
+  RETURN QUERY
+  WITH locked AS (
+    SELECT
+      ci.id,
+      COALESCE(ci.properties, '[]'::jsonb) AS current_properties
+    FROM config_items ci
+    WHERE ci.id = p_config_id
+    FOR UPDATE
+  ), computed AS (
+    SELECT
+      locked.id,
+      locked.current_properties,
+      COALESCE(
+        (
+          SELECT jsonb_agg(prop ORDER BY ord)
+          FROM jsonb_array_elements(locked.current_properties)
+            WITH ORDINALITY AS existing(prop, ord)
+          WHERE (
+            prop->>'creator_type' = p_creator_type
+            AND prop->>'created_by' = p_created_by::text
+            AND prop->>'name' = p_property_name
+          ) IS NOT TRUE
+        ),
+        '[]'::jsonb
+      ) AS new_properties
+    FROM locked
+  ), updated AS (
+    UPDATE config_items ci
+    SET properties = computed.new_properties
+    FROM computed
+    WHERE ci.id = computed.id
+      AND computed.current_properties IS DISTINCT FROM computed.new_properties
+    RETURNING true AS changed, ci.properties
+  )
+  SELECT updated.changed, updated.properties
+  FROM updated
+  UNION ALL
+  SELECT false AS changed, computed.current_properties AS properties
+  FROM computed
+  WHERE NOT EXISTS (SELECT 1 FROM updated);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@functions/config_item_properties.sql` around lines 174 - 213, The
delete_config_item_property logic duplicates the same jsonb_agg subquery in both
the SET and the WHERE IS DISTINCT FROM check; refactor by adding a
locking/compute CTE (e.g., compute_new AS (SELECT ci.id, ci.properties AS
old_properties, (SELECT jsonb_agg(prop ORDER BY ord) FROM
jsonb_array_elements(COALESCE(ci.properties, '[]'::jsonb)) WITH ORDINALITY AS
existing(prop, ord) WHERE NOT (prop->>'creator_type' = p_creator_type AND
prop->>'created_by' = p_created_by::text AND prop->>'name' = p_property_name))
AS new_properties FROM config_items ci WHERE ci.id = p_config_id FOR UPDATE))
then use an UPDATE ... FROM compute_new to set properties =
COALESCE(compute_new.new_properties, '[]'::jsonb) only when
compute_new.old_properties IS DISTINCT FROM COALESCE(compute_new.new_properties,
'[]'::jsonb), RETURNING changed and properties in the updated CTE, and keep the
UNION ALL fallback selecting from config_items when NOT EXISTS updated;
reference compute_new, updated, p_config_id, p_creator_type, p_created_by, and
p_property_name to implement this single-computation pattern.
tests/config_item_properties_function_test.go (1)

166-182: ⚡ Quick win

Missing test coverage for delete_config_item_property with a non-existent config item.

callDeleteConfigItemProperty doesn't guard against 0 rows being returned: if the SQL function returns nothing (config item missing), row.Properties is an empty string and json.Unmarshal([]byte(""), &props) emits unexpected end of JSON input, which is harder to diagnose than the corresponding config item not found message in callUpdateConfigItemPropertiesErr. Consider adding parity test coverage for the missing-ID case, mirroring the existing "returns an error when the config item does not exist" spec.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/config_item_properties_function_test.go` around lines 166 - 182, The
helper callDeleteConfigItemProperty can unmarshal an empty string when the SQL
function returns no rows; add a guard after the DB scan to detect an empty
row.Properties and fail with the same "config item not found" style used
elsewhere (e.g., in callUpdateConfigItemPropertiesErr) before attempting
json.Unmarshal. Update the callDeleteConfigItemProperty function to check if
row.Properties == "" (or len(row.Properties) == 0) and call Expect(...).To/Fail
with a clear "config item not found" message so tests get the same readable
error for missing-ID cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@functions/config_item_properties.sql`:
- Around line 174-213: The delete_config_item_property logic duplicates the same
jsonb_agg subquery in both the SET and the WHERE IS DISTINCT FROM check;
refactor by adding a locking/compute CTE (e.g., compute_new AS (SELECT ci.id,
ci.properties AS old_properties, (SELECT jsonb_agg(prop ORDER BY ord) FROM
jsonb_array_elements(COALESCE(ci.properties, '[]'::jsonb)) WITH ORDINALITY AS
existing(prop, ord) WHERE NOT (prop->>'creator_type' = p_creator_type AND
prop->>'created_by' = p_created_by::text AND prop->>'name' = p_property_name))
AS new_properties FROM config_items ci WHERE ci.id = p_config_id FOR UPDATE))
then use an UPDATE ... FROM compute_new to set properties =
COALESCE(compute_new.new_properties, '[]'::jsonb) only when
compute_new.old_properties IS DISTINCT FROM COALESCE(compute_new.new_properties,
'[]'::jsonb), RETURNING changed and properties in the updated CTE, and keep the
UNION ALL fallback selecting from config_items when NOT EXISTS updated;
reference compute_new, updated, p_config_id, p_creator_type, p_created_by, and
p_property_name to implement this single-computation pattern.

In `@tests/config_item_properties_function_test.go`:
- Around line 166-182: The helper callDeleteConfigItemProperty can unmarshal an
empty string when the SQL function returns no rows; add a guard after the DB
scan to detect an empty row.Properties and fail with the same "config item not
found" style used elsewhere (e.g., in callUpdateConfigItemPropertiesErr) before
attempting json.Unmarshal. Update the callDeleteConfigItemProperty function to
check if row.Properties == "" (or len(row.Properties) == 0) and call
Expect(...).To/Fail with a clear "config item not found" message so tests get
the same readable error for missing-ID cases.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8da30016-bacd-4c8e-bb39-2492a89ec21f

📥 Commits

Reviewing files that changed from the base of the PR and between 92ea337 and 4f6025d.

📒 Files selected for processing (11)
  • bench/insertion_test.go
  • functions/config_item_properties.sql
  • models/clone.go
  • models/config.go
  • models/owned_property.go
  • query/template_test.go
  • rbac/objects.go
  • tests/config_item_properties_function_test.go
  • tests/fixtures/dummy/config.go
  • tests/fixtures/dummy/config_demo_cluster.go
  • types/resource_selector_test.go
✅ Files skipped from review due to trivial changes (2)
  • query/template_test.go
  • models/owned_property.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • tests/fixtures/dummy/config_demo_cluster.go
  • models/config.go
  • types/resource_selector_test.go
  • rbac/objects.go
  • models/clone.go
  • tests/fixtures/dummy/config.go

@adityathebe
Copy link
Copy Markdown
Member Author

abandoning this approach. follow up here: #1955

@adityathebe adityathebe closed this May 6, 2026
@adityathebe adityathebe deleted the feat/config-property-provenance branch May 6, 2026 06:32
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