Skip to content

RHCLOUD-45005: Use ResourceType/ReporterType in SchemaRepository and ResourceEvent#1332

Closed
snehagunta wants to merge 4 commits intoproject-kessel:mainfrom
snehagunta:RHCLOUD-45005-move-model-types-to-model
Closed

RHCLOUD-45005: Use ResourceType/ReporterType in SchemaRepository and ResourceEvent#1332
snehagunta wants to merge 4 commits intoproject-kessel:mainfrom
snehagunta:RHCLOUD-45005-move-model-types-to-model

Conversation

@snehagunta
Copy link
Copy Markdown
Contributor

@snehagunta snehagunta commented Apr 30, 2026

Summary

  • Replace string with model.ResourceType and model.ReporterType in SchemaRepository interface, ResourceSchema, ReporterSchema, and all implementations
  • Add ResourceType.SchemaRepositoryKey() for canonical map lookups (trim, slash replacement, lowercase)
  • Change ResourceEvent interface getters to return ResourceType/ReporterType instead of string
  • Unwrap with .String() at outbox serialization boundary in outboxevents.go

Stack

4/4 in the tiny-types series. Depends on #1331 (merge that first).

Test plan

  • go build ./... passes
  • go test ./internal/... — all 36 packages pass

Made with Cursor

Summary by CodeRabbit

  • Refactor
    • Strengthened internal type safety by replacing generic string types with strongly-typed domain identifiers across models, repositories, and services (e.g., transaction IDs, resource types, pagination tokens, lock identifiers, and versions).
    • Updated interface contracts and method signatures to use domain types consistently throughout the codebase.

snehagunta and others added 3 commits April 30, 2026 16:00
Add Decrement() method to Version type with underflow protection, and
update TestCalculateTuples to use Version type instead of raw uint.

Changes:
- Add Version.Decrement() method that returns 0 when already at 0
- Update schema_service_test.go to construct Version values with NewVersion()
- Completes the Version type conversion started in previous commits

This is a follow-up to the Version tiny type conversion (2A) to ensure
all tests use the domain type consistently.

Related: RHCLOUD-45005

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2026

Warning

Rate limit exceeded

@snehagunta has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 39 minutes and 35 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d1113ede-42e1-43e7-92c6-287a3acbf855

📥 Commits

Reviewing files that changed from the base of the PR and between 67cfd19 and 8bc5ab6.

📒 Files selected for processing (1)
  • internal/biz/model/schema_service_test.go
📝 Walkthrough

Walkthrough

This PR introduces strong typing throughout the codebase by replacing primitive string and *uint types with domain-specific types including ContinuationToken, TransactionId, Version, ResourceType, ReporterType, LockId, and LockToken. Changes span 25+ files across model definitions, repositories, services, and consumers while preserving existing control flow.

Changes

Cohort / File(s) Summary
Core Domain Type Definitions
internal/biz/model/common.go
Adds ContinuationToken type with creation, string conversion, and serialization/deserialization functions. Extends Version with Decrement() method. Adds SchemaRepositoryKey() to ResourceType for normalized key generation.
Pagination & Item Models
internal/biz/model/pagination.go, internal/biz/model/lookup_objects_item.go, internal/biz/model/lookup_subjects_item.go, internal/biz/model/read_tuples_item.go
Updates continuation token fields from string to ContinuationToken type across pagination and lookup result models. Adds NewPagination constructor and ContinuationToken() accessor.
Event Models
internal/biz/model/resource_event.go, internal/biz/model/resource_delete_event.go, internal/biz/model/resource_report_event.go
Changes ResourceType() and ReporterType() method return types from string to strongly-typed ResourceType and ReporterType domain types.
Representation & Repository Models
internal/biz/model/representations.go, internal/biz/model/resource_repository.go, internal/biz/model/schema_repository.go, internal/biz/model/schema_service.go
Replaces *uint with *Version for version fields. Updates repository/service interface signatures to accept/return strongly-typed ResourceType, ReporterType, TransactionId, and Version instead of strings/primitives.
Legacy Event Serialization
internal/biz/model_legacy/outboxevents.go, internal/biz/model_legacy/outboxevents_test.go
Converts ResourceType and ReporterType to string representations for CloudEvents fields. Updates NewOutboxEventsFromResourceEvent to accept TransactionId instead of string and converts to string only when populating tuple event fields.
Repository Implementations
internal/data/fake_resource_repository.go, internal/data/resource_repository.go, internal/data/resource_repository_test.go
Implements interface changes: transaction ID parameters now use TransactionId, version parameters use *Version, and version-selection logic now derives numeric values from typed versions. Updates idempotency and representation lookups accordingly.
Relations Repository
internal/data/grpc_relations_repository.go, internal/data/grpc_relations_repository_test.go, internal/data/relations_simple.go
Updates pagination continuation token handling to deserialize/serialize tokens using ContinuationToken type. Removes direct pass-through of raw proto string tokens.
Schema Repository Implementation
internal/data/schema_inmemory.go, internal/data/schema_inmemory_test.go
Introduces canonical key derivation for ResourceType and serialization for ReporterType. Reworks storage lookups and directory/JSON loading to use typed values. Replaces string-based key normalization with typed constructors and validators.
Tuple Operations
internal/biz/usecase/tuples/commands.go, internal/biz/usecase/tuples/tuple_crud_usecase.go, internal/biz/usecase/tuples/tuple_crud_usecase_test.go
Replaces lock-related string fields with LockId and LockToken types. Removes intermediate deserialization steps and uses typed values directly in fencing checks and lock acquisition.
Resource Service
internal/biz/usecase/resources/resource_service.go, internal/biz/usecase/resources/resource_service_test.go
Passes TransactionId directly to repository persistence methods instead of converting to string. Simplifies resource validation by inlining type references and updating schema checks.
Consumer & Event Processing
internal/consumer/consumer.go, internal/consumer/consumer_test.go
Updates relation-replication callback signatures to accept *Version instead of *uint. Removes intermediate uint conversions when passing version values to repository methods.
Tuple Service
internal/service/tuples/tuples.go, internal/service/tuples/tuples_test.go
Adds validation for lock ID/token parsing via NewLockId/NewLockToken with error handling. Updates pagination conversion to deserialize continuation tokens and serialize them in proto responses.
Inventory Service
internal/service/resources/kesselinventoryservice.go, internal/service/resources/kesselinventoryservice_test.go, internal/service/resources/pagination_test.go
Updates pagination proto conversion to parse continuation tokens into ContinuationToken type. Serializes typed tokens to strings in proto responses. Updates repository calls to use typed transaction IDs and schema types.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description is well-structured with a clear summary of changes, stack context, and test plan. However, it does not follow the repository's template structure with all required sections and checklist items. Consider filling out the full PR template including the checklist items (4-eye principle, acceptance criteria, security review, etc.) to ensure all review requirements are captured.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main change: replacing string types with ResourceType/ReporterType in SchemaRepository and ResourceEvent interfaces.
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 unit tests (beta)
  • Create PR with unit tests

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
Review rate limit: 0/1 reviews remaining, refill in 39 minutes and 35 seconds.

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

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 30, 2026

Codecov Report

❌ Patch coverage is 95.83333% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/biz/model/common.go 50.00% 1 Missing and 1 partial ⚠️
Flag Coverage Δ
main 49.75% <93.75%> (-0.02%) ⬇️
v1beta2 64.98% <95.83%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
internal/biz/model/representations.go 76.92% <100.00%> (ø)
internal/biz/model_legacy/outboxevents.go 73.68% <100.00%> (ø)
internal/biz/usecase/resources/resource_service.go 73.67% <100.00%> (ø)
internal/consumer/consumer.go 60.94% <100.00%> (-0.80%) ⬇️
internal/data/fake_resource_repository.go 90.25% <100.00%> (-0.05%) ⬇️
internal/data/resource_repository.go 84.58% <100.00%> (+0.13%) ⬆️
internal/biz/model/common.go 93.39% <50.00%> (-0.78%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/service/resources/kesselinventoryservice.go (1)

301-309: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not ignore continuation-token parse failures in request conversion.

model.NewContinuationToken errors are currently discarded, which can silently turn invalid input into an empty/zero token and alter pagination behavior.

💡 Proposed fix
-func paginationFromProto(p *pb.RequestPagination) *model.Pagination {
+func paginationFromProto(p *pb.RequestPagination) (*model.Pagination, error) {
 	if p == nil {
-		return nil
+		return nil, nil
 	}
-	out := &model.Pagination{Limit: p.Limit}
+	out := &model.Pagination{Limit: p.Limit}
 	if p.ContinuationToken != nil {
-		ct, _ := model.NewContinuationToken(*p.ContinuationToken)
+		ct, err := model.NewContinuationToken(*p.ContinuationToken)
+		if err != nil {
+			return nil, fmt.Errorf("invalid continuation token: %w", err)
+		}
 		out.Continuation = &ct
 	}
-	return out
+	return out, nil
 }
-	return resources.LookupObjectsCommand{
+	pagination, err := paginationFromProto(request.Pagination)
+	if err != nil {
+		return resources.LookupObjectsCommand{}, fmt.Errorf("invalid pagination: %w", err)
+	}
+	return resources.LookupObjectsCommand{
 		ObjectType:  objectType,
 		Relation:    relation,
 		Subject:     subjectRef,
-		Pagination:  paginationFromProto(request.Pagination),
+		Pagination:  pagination,
 		Consistency: consistencyFromProto(request.GetConsistency()),
 	}, nil
-	return resources.LookupSubjectsCommand{
+	pagination, err := paginationFromProto(request.Pagination)
+	if err != nil {
+		return resources.LookupSubjectsCommand{}, fmt.Errorf("invalid pagination: %w", err)
+	}
+	return resources.LookupSubjectsCommand{
 		Resource:        resourceRef,
 		Relation:        relation,
 		SubjectType:     model.NewRepresentationTypeRequired(subjectResType, subjectReporter),
 		SubjectRelation: subjectRelation,
-		Pagination:      paginationFromProto(request.Pagination),
+		Pagination:      pagination,
 		Consistency:     consistencyFromProto(request.GetConsistency()),
 	}, nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/service/resources/kesselinventoryservice.go` around lines 301 - 309,
The paginationFromProto function currently swallows errors from
model.NewContinuationToken which can turn invalid continuation tokens into a
zero value; change paginationFromProto to return (*model.Pagination, error),
call model.NewContinuationToken and check its error, and if parsing fails return
nil and a descriptive error (e.g., "invalid continuation token: ..."); update
all callers of paginationFromProto to handle the new error and propagate or
convert it to the appropriate gRPC/HTTP error response so invalid tokens aren’t
silently accepted.
🧹 Nitpick comments (2)
internal/biz/model/common.go (1)

102-119: 💤 Low value

ContinuationToken constructor always returns nil error.

NewContinuationToken unconditionally returns nil for the error, making the error return value unused. This is inconsistent with other similar constructors (e.g., NewConsistencyToken, NewLockToken) that validate non-empty input.

If empty continuation tokens are intentionally allowed (e.g., for initial pagination requests), consider either:

  1. Removing the error return since it's never used
  2. Documenting that empty tokens are valid

This appears intentional based on pagination semantics, so approving as-is but flagging for clarity.

♻️ Suggested simplification (if empty is always valid)
-func NewContinuationToken(token string) (ContinuationToken, error) {
-	return ContinuationToken(token), nil
-}
+func NewContinuationToken(token string) ContinuationToken {
+	return ContinuationToken(token)
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/biz/model/common.go` around lines 102 - 119, The
NewContinuationToken function currently returns an unused error; if empty
continuation tokens are valid, change NewContinuationToken to return only
(ContinuationToken) by removing the error from its signature and implementation,
update all call sites to accept a single-return value, and adjust related
helpers (e.g., any places that call NewContinuationToken) while leaving
ContinuationToken.String, Serialize, and DeserializeContinuationToken unchanged;
alternatively, if empty tokens must be rejected, add validation in
NewContinuationToken to return an error for empty input and ensure callers
handle the error.
internal/data/fake_resource_repository.go (1)

256-311: ⚡ Quick win

Fake repository doesn't respect operationType for previous version lookup.

The real repository (per context snippet from resource_repository.go:313-318) only queries for the previous version (cv-1) when operationType is not Created:

if operationType.OperationType() == bizmodel.OperationTypeCreated {
    query = query.Where("cr.version = ?", cv)
} else {
    query = query.Where("(cr.version = ? OR cr.version = ?)", cv, cv-1)
}

However, this fake implementation always attempts to look up cv-1 (lines 294-307) regardless of operationType. This could cause test behavior to diverge from production behavior for Created operations.

♻️ Proposed fix to respect operationType
 	if entry, ok := versionMap[cv]; ok {
 		v := bizmodel.NewVersion(entry.commonVersion)
 		var err error
 		current, err = bizmodel.NewRepresentations(
 			bizmodel.Representation(cloneJsonObject(entry.commonData)),
 			&v,
 			nil,
 			nil,
 		)
 		if err != nil {
 			return nil, nil, err
 		}
 	}

-	if cv > 0 {
+	// Only look up previous version for non-Created operations (matching real repository behavior)
+	if cv > 0 && operationType.OperationType() != bizmodel.OperationTypeCreated {
 		if entry, ok := versionMap[cv-1]; ok {
 			v := bizmodel.NewVersion(entry.commonVersion)
 			var err error
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/data/fake_resource_repository.go` around lines 256 - 311, The fake
implementation of FindCurrentAndPreviousVersionedRepresentations ignores
operationType and always tries to load the previous version; update
fakeResourceRepository.FindCurrentAndPreviousVersionedRepresentations to only
attempt the cv-1 lookup when operationType.OperationType() !=
bizmodel.OperationTypeCreated (and cv > 0). Concretely, wrap the block that
checks versionMap[cv-1] and constructs previous (the code using cv-1, v :=
bizmodel.NewVersion(...), and bizmodel.NewRepresentations(...)) in a conditional
that checks operationType.OperationType() and cv>0, matching the real repo
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/biz/usecase/resources/resource_service.go`:
- Around line 177-182: Save currently diverges between cmd.TransactionId and the
local request-scoped txid (used in createResource/updateResource and outbox),
and the retry path rewrites cmd.TransactionId without updating the captured
txid; fix by computing a single effective transaction ID up front (e.g.
effectiveTxID := first non-nil of cmd.TransactionId and request txid or generate
one) and use that same effectiveTxID everywhere: set cmd.TransactionId =
effectiveTxID before any retries, pass effectiveTxID into uc.createResource and
uc.updateResource calls and into the outbox invocation, or alternatively rename
and separate concepts explicitly (e.g. IdempotencyKey vs OutboxCorrelationId)
and wire them consistently so resource model (cmd.TransactionId), the local txid
variable, and outbox events always carry the same identifier.

In `@internal/data/resource_repository.go`:
- Around line 313-317: The query builds a previous-version predicate using cv-1
which underflows when currentCommonVersion is 0; update the branch that sets
query (around the use of currentCommonVersion.Uint() and
operationType.OperationType() == bizmodel.OperationTypeCreated) to only include
the "(cr.version = ? OR cr.version = ?)" clause when cv > 0, otherwise fall back
to the single "cr.version = ?" predicate; ensure the same cv variable is used
and that the cv-1 expression is only evaluated after checking cv > 0 so you
avoid unsigned underflow and invalid bound values being passed to the SQL
driver.

In `@internal/data/schema_inmemory.go`:
- Around line 298-317: The current branch treats any error from
loadResourceSchema as a missing file and logs "No schema found", which masks
real read failures; change the logic around loadResourceSchema(resourceLabel,
reporterLabel, resourceDir) so that if err != nil you return or propagate that
error (including context about resourceLabel/reporterLabel), only treat
isReporterSchemaExists == false as the benign "no schema" case that should log a
warning, and keep the existing successful path that constructs resRT/repRT and
calls repository.CreateReporterSchema with
validationSchemaFromString(reporterSchema).

---

Outside diff comments:
In `@internal/service/resources/kesselinventoryservice.go`:
- Around line 301-309: The paginationFromProto function currently swallows
errors from model.NewContinuationToken which can turn invalid continuation
tokens into a zero value; change paginationFromProto to return
(*model.Pagination, error), call model.NewContinuationToken and check its error,
and if parsing fails return nil and a descriptive error (e.g., "invalid
continuation token: ..."); update all callers of paginationFromProto to handle
the new error and propagate or convert it to the appropriate gRPC/HTTP error
response so invalid tokens aren’t silently accepted.

---

Nitpick comments:
In `@internal/biz/model/common.go`:
- Around line 102-119: The NewContinuationToken function currently returns an
unused error; if empty continuation tokens are valid, change
NewContinuationToken to return only (ContinuationToken) by removing the error
from its signature and implementation, update all call sites to accept a
single-return value, and adjust related helpers (e.g., any places that call
NewContinuationToken) while leaving ContinuationToken.String, Serialize, and
DeserializeContinuationToken unchanged; alternatively, if empty tokens must be
rejected, add validation in NewContinuationToken to return an error for empty
input and ensure callers handle the error.

In `@internal/data/fake_resource_repository.go`:
- Around line 256-311: The fake implementation of
FindCurrentAndPreviousVersionedRepresentations ignores operationType and always
tries to load the previous version; update
fakeResourceRepository.FindCurrentAndPreviousVersionedRepresentations to only
attempt the cv-1 lookup when operationType.OperationType() !=
bizmodel.OperationTypeCreated (and cv > 0). Concretely, wrap the block that
checks versionMap[cv-1] and constructs previous (the code using cv-1, v :=
bizmodel.NewVersion(...), and bizmodel.NewRepresentations(...)) in a conditional
that checks operationType.OperationType() and cv>0, matching the real repo
behavior.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 13d979ad-9f71-40ee-adad-6f60621b1e4e

📥 Commits

Reviewing files that changed from the base of the PR and between 9b3d4db and 67cfd19.

📒 Files selected for processing (35)
  • internal/biz/model/common.go
  • internal/biz/model/lookup_objects_item.go
  • internal/biz/model/lookup_subjects_item.go
  • internal/biz/model/pagination.go
  • internal/biz/model/read_tuples_item.go
  • internal/biz/model/representations.go
  • internal/biz/model/resource_delete_event.go
  • internal/biz/model/resource_event.go
  • internal/biz/model/resource_report_event.go
  • internal/biz/model/resource_repository.go
  • internal/biz/model/schema_repository.go
  • internal/biz/model/schema_service.go
  • internal/biz/model/schema_service_test.go
  • internal/biz/model_legacy/outboxevents.go
  • internal/biz/model_legacy/outboxevents_test.go
  • internal/biz/usecase/resources/resource_service.go
  • internal/biz/usecase/resources/resource_service_test.go
  • internal/biz/usecase/tuples/commands.go
  • internal/biz/usecase/tuples/tuple_crud_usecase.go
  • internal/biz/usecase/tuples/tuple_crud_usecase_test.go
  • internal/consumer/consumer.go
  • internal/consumer/consumer_test.go
  • internal/data/fake_resource_repository.go
  • internal/data/grpc_relations_repository.go
  • internal/data/grpc_relations_repository_test.go
  • internal/data/relations_simple.go
  • internal/data/resource_repository.go
  • internal/data/resource_repository_test.go
  • internal/data/schema_inmemory.go
  • internal/data/schema_inmemory_test.go
  • internal/service/resources/kesselinventoryservice.go
  • internal/service/resources/kesselinventoryservice_test.go
  • internal/service/resources/pagination_test.go
  • internal/service/tuples/tuples.go
  • internal/service/tuples/tuples_test.go

Comment on lines +177 to +182
return uc.updateResource(tx, cmd, res, txid)
}

log.Info("Creating new resource")
operationType = model.OperationTypeCreated
return uc.createResource(tx, cmd, txid.String())
return uc.createResource(tx, cmd, txid)
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Inspecting the resource write path and outbox txid usage..."
fd 'resource_service.go$' internal -x sed -n '120,310p' {}
fd 'resource_repository.go$' internal -x sed -n '120,220p;360,430p' {}
fd 'outboxevents.go$' internal -x sed -n '200,250p' {}

echo
echo "Searching for the two transaction-id flows..."
rg -n 'HasTransactionIdBeenProcessed\(|model\.NewResource\(|existingResource\.Update\(|resourceRepository\.Save\(' internal -g '!**/*_test.go'

Repository: project-kessel/inventory-api

Length of output: 19958


Resolve transaction ID inconsistency across the write path.

The resource model stores *cmd.TransactionId (lines 251, 283) while Save() receives and forwards a separate request-scoped txid to the outbox (lines 269, 296). When a caller supplies an idempotency key, these diverge from the start. The retry logic (lines 188–191) compounds this by rewriting cmd.TransactionId without updating the captured txid, causing the same operation to persist with different transaction IDs in the resource vs. outbox event stream. This breaks correlation between stored state and published events, compromising idempotency tracking and event replay.

Either thread a single effective transaction ID through both the resource model and outbox calls, or explicitly split these into distinct idempotency key and outbox correlation ID concepts with clear naming to prevent future regressions.

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

In `@internal/biz/usecase/resources/resource_service.go` around lines 177 - 182,
Save currently diverges between cmd.TransactionId and the local request-scoped
txid (used in createResource/updateResource and outbox), and the retry path
rewrites cmd.TransactionId without updating the captured txid; fix by computing
a single effective transaction ID up front (e.g. effectiveTxID := first non-nil
of cmd.TransactionId and request txid or generate one) and use that same
effectiveTxID everywhere: set cmd.TransactionId = effectiveTxID before any
retries, pass effectiveTxID into uc.createResource and uc.updateResource calls
and into the outbox invocation, or alternatively rename and separate concepts
explicitly (e.g. IdempotencyKey vs OutboxCorrelationId) and wire them
consistently so resource model (cmd.TransactionId), the local txid variable, and
outbox events always carry the same identifier.

Comment on lines +313 to +317
cv := currentCommonVersion.Uint()
if operationType.OperationType() == bizmodel.OperationTypeCreated {
query = query.Where("cr.version = ?", *currentCommonVersion)
query = query.Where("cr.version = ?", cv)
} else {
query = query.Where("(cr.version = ? OR cr.version = ?)", *currentCommonVersion, *currentCommonVersion-1)

query = query.Where("(cr.version = ? OR cr.version = ?)", cv, cv-1)
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid unsigned underflow in the previous-version query.

When currentCommonVersion is 0 on a non-create path, cv-1 wraps to math.MaxUint and gets bound into the SQL predicate. That can produce the wrong lookup, and some drivers reject that value for signed integer columns. Gate the previous-version clause behind cv > 0 so the query matches the guard you already use later in the loop.

Suggested fix
 	cv := currentCommonVersion.Uint()
-	if operationType.OperationType() == bizmodel.OperationTypeCreated {
+	if operationType.OperationType() == bizmodel.OperationTypeCreated || cv == 0 {
 		query = query.Where("cr.version = ?", cv)
 	} else {
 		query = query.Where("(cr.version = ? OR cr.version = ?)", cv, cv-1)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cv := currentCommonVersion.Uint()
if operationType.OperationType() == bizmodel.OperationTypeCreated {
query = query.Where("cr.version = ?", *currentCommonVersion)
query = query.Where("cr.version = ?", cv)
} else {
query = query.Where("(cr.version = ? OR cr.version = ?)", *currentCommonVersion, *currentCommonVersion-1)
query = query.Where("(cr.version = ? OR cr.version = ?)", cv, cv-1)
cv := currentCommonVersion.Uint()
if operationType.OperationType() == bizmodel.OperationTypeCreated || cv == 0 {
query = query.Where("cr.version = ?", cv)
} else {
query = query.Where("(cr.version = ? OR cr.version = ?)", cv, cv-1)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/data/resource_repository.go` around lines 313 - 317, The query
builds a previous-version predicate using cv-1 which underflows when
currentCommonVersion is 0; update the branch that sets query (around the use of
currentCommonVersion.Uint() and operationType.OperationType() ==
bizmodel.OperationTypeCreated) to only include the "(cr.version = ? OR
cr.version = ?)" clause when cv > 0, otherwise fall back to the single
"cr.version = ?" predicate; ensure the same cv variable is used and that the
cv-1 expression is only evaluated after checking cv > 0 so you avoid unsigned
underflow and invalid bound values being passed to the SQL driver.

Comment thread internal/data/schema_inmemory.go Outdated
Comment on lines +298 to +317
reporterSchema, isReporterSchemaExists, err := loadResourceSchema(resourceLabel, reporterLabel, resourceDir)
if err == nil && isReporterSchemaExists {
resRT, err := bizmodel.NewResourceType(resourceLabel)
if err != nil {
return nil, fmt.Errorf("invalid resource type %q: %w", resourceLabel, err)
}
repRT, err := bizmodel.NewReporterType(reporterLabel)
if err != nil {
return nil, fmt.Errorf("invalid reporter type %q: %w", reporterLabel, err)
}
err = repository.CreateReporterSchema(ctx, bizmodel.ReporterSchema{
ResourceType: resourceType,
ReporterType: reporterType,
ResourceType: resRT,
ReporterType: repRT,
ValidationSchema: validationSchemaFromString(reporterSchema),
})
if err != nil {
return nil, err
}
} else {
log.Warnf("No schema found for %s:%s", resourceType, reporterType)
log.Warnf("No schema found for %s:%s", resourceLabel, reporterLabel)
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return real reporter schema load failures instead of downgrading them to warnings.

loadResourceSchema only returns isReporterSchemaExists == false for a genuinely missing file. Any other error here means the reporter schema could not be read, but the current else path logs "No schema found" and continues with a partially loaded repository.

Suggested fix
 			reporterLabel := reporter.Name()
 			reporterSchema, isReporterSchemaExists, err := loadResourceSchema(resourceLabel, reporterLabel, resourceDir)
-			if err == nil && isReporterSchemaExists {
+			if err != nil {
+				return nil, fmt.Errorf("failed to load reporter schema %s:%s: %w", resourceLabel, reporterLabel, err)
+			}
+			if isReporterSchemaExists {
 				resRT, err := bizmodel.NewResourceType(resourceLabel)
 				if err != nil {
 					return nil, fmt.Errorf("invalid resource type %q: %w", resourceLabel, err)
@@
 				if err != nil {
 					return nil, err
 				}
 			} else {
 				log.Warnf("No schema found for %s:%s", resourceLabel, reporterLabel)
 			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
reporterSchema, isReporterSchemaExists, err := loadResourceSchema(resourceLabel, reporterLabel, resourceDir)
if err == nil && isReporterSchemaExists {
resRT, err := bizmodel.NewResourceType(resourceLabel)
if err != nil {
return nil, fmt.Errorf("invalid resource type %q: %w", resourceLabel, err)
}
repRT, err := bizmodel.NewReporterType(reporterLabel)
if err != nil {
return nil, fmt.Errorf("invalid reporter type %q: %w", reporterLabel, err)
}
err = repository.CreateReporterSchema(ctx, bizmodel.ReporterSchema{
ResourceType: resourceType,
ReporterType: reporterType,
ResourceType: resRT,
ReporterType: repRT,
ValidationSchema: validationSchemaFromString(reporterSchema),
})
if err != nil {
return nil, err
}
} else {
log.Warnf("No schema found for %s:%s", resourceType, reporterType)
log.Warnf("No schema found for %s:%s", resourceLabel, reporterLabel)
reporterSchema, isReporterSchemaExists, err := loadResourceSchema(resourceLabel, reporterLabel, resourceDir)
if err != nil {
return nil, fmt.Errorf("failed to load reporter schema %s:%s: %w", resourceLabel, reporterLabel, err)
}
if isReporterSchemaExists {
resRT, err := bizmodel.NewResourceType(resourceLabel)
if err != nil {
return nil, fmt.Errorf("invalid resource type %q: %w", resourceLabel, err)
}
repRT, err := bizmodel.NewReporterType(reporterLabel)
if err != nil {
return nil, fmt.Errorf("invalid reporter type %q: %w", reporterLabel, err)
}
err = repository.CreateReporterSchema(ctx, bizmodel.ReporterSchema{
ResourceType: resRT,
ReporterType: repRT,
ValidationSchema: validationSchemaFromString(reporterSchema),
})
if err != nil {
return nil, err
}
} else {
log.Warnf("No schema found for %s:%s", resourceLabel, reporterLabel)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/data/schema_inmemory.go` around lines 298 - 317, The current branch
treats any error from loadResourceSchema as a missing file and logs "No schema
found", which masks real read failures; change the logic around
loadResourceSchema(resourceLabel, reporterLabel, resourceDir) so that if err !=
nil you return or propagate that error (including context about
resourceLabel/reporterLabel), only treat isReporterSchemaExists == false as the
benign "no schema" case that should log a warning, and keep the existing
successful path that constructs resRT/repRT and calls
repository.CreateReporterSchema with validationSchemaFromString(reporterSchema).

@snehagunta snehagunta force-pushed the RHCLOUD-45005-move-model-types-to-model branch from 67cfd19 to 8bc5ab6 Compare May 1, 2026 13:36
@snehagunta
Copy link
Copy Markdown
Contributor Author

Replaced by new PR on dedicated branch

@snehagunta snehagunta closed this May 1, 2026
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