Skip to content

Conversation

@sirisjo
Copy link
Contributor

@sirisjo sirisjo commented Oct 17, 2025

Description

This change enables exposure values per zone in Enterprise.

Motivation and Context

Resolves BED-6601

Because the domain selectors model and the openAPI schema are part of the foss model, we need to update them here in order to enable exposure values per zone in enterprise. The new fields have been labeled "enterprise only" (since this is a limitation of the OpenAPI schema, we cannot actually mark them as enterprise only).

How Has This Been Tested?

Please describe in detail how you tested your changes.
Include details of your testing environment, and the tests you ran to
see how your change affects other areas of the code, etc.

Nothing to test here.

Screenshots (optional):

Types of changes

  • New feature (non-breaking change which adds functionality)

Checklist:

Summary by CodeRabbit

  • New Features
    • Domain selector responses now include enterprise-only fields: an impact value metric and an exposures array.
    • Exposures report exposure percentages alongside associated asset group tag information for finer exposure tracking.
  • Other
    • Asset group tags gained a helper to identify tier-zero tags.

@sirisjo sirisjo self-assigned this Oct 17, 2025
@sirisjo sirisjo added enhancement New feature or request api A pull request containing changes affecting the API code. labels Oct 17, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 17, 2025

Walkthrough

Adds enterprise-facing exposure data to the DomainSelector model: new exported types Exposure and Exposures, a new Exposures field on the Go model, an IsTierZero() helper on AssetGroupTag, and corresponding OpenAPI/schema additions (impactValue, exposures, and model.asset-group-tag).

Changes

Cohort / File(s) Summary
Go model additions
cmd/api/src/model/search.go
Added exported types Exposure (fields: ExposurePercent int, AssetGroupTag AssetGroupTag) and Exposures (slice of Exposure); added Exposures Exposures \json:"exposures,omitempty"`toDomainSelector`.
Asset group helper
cmd/api/src/model/assetgrouptags.go
Added method func (s AssetGroupTag) IsTierZero() bool that checks Position.ValueOrZero() == AssetGroupTierZeroPosition.
OpenAPI / schema updates
packages/go/openapi/src/schemas/model.domain-selector.yaml, packages/go/openapi/doc/openapi.json
Extended model.domain-selector with impactValue (integer, readOnly) and exposures (array, readOnly) items containing exposure_percent and asset_group_tag (ref model.asset-group-tag); added/adjusted schemas including model.asset-group-tag and null.boolean.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant API as "API / Handler"
  participant Model as "DomainSelector Model"
  participant OpenAPI as "OpenAPI Schema"

  Client->>API: Request domain selector (read)
  API->>Model: Load DomainSelector (includes exposures, impactValue)
  Note right of Model #D6EAF8: New Exposures slice and Exposure items
  Model-->>API: DomainSelector with Exposures[]
  API->>Client: JSON response (includes exposures, impactValue)
  API->>OpenAPI: Serve updated schema (exposures, impactValue)
  OpenAPI-->>Client: Schema documents enterprise-only fields
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • juggernot325

Poem

🐇
I nibbled bytes in burrowed stacks,
Placed percents, tags along the tracks.
Now DomainSelector shows the view,
Exposures bloom — a carrot for you! 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "feat: BED-6601 Add exposure values to Domain Selectors" is directly aligned with the core changes made across the codebase. The title clearly and specifically describes the main objective—adding exposure values to domain selectors—which is reflected in the new Exposure and Exposures types, the updated DomainSelector struct, and the corresponding OpenAPI schema updates. The title is concise and uses a conventional commit format that follows best practices, making it immediately understandable to team members reviewing the commit history.
Description Check ✅ Passed The pull request description follows the required template structure and includes all major sections: a clear description of the change, motivation and context with the associated ticket reference (BED-6601), a testing section, type of change selection, and a completed checklist. The description provides specific context about why the change is needed—enabling enterprise exposure-per-zone functionality in the FOSS model—and explains the "enterprise only" labeling approach. While the testing section states "Nothing to test here," the description is substantive and not vague or off-topic, meeting the threshold for a passing description check.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BED-6601

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.

Copy link
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: 0

🧹 Nitpick comments (1)
packages/go/openapi/src/schemas/model.domain-selector.yaml (1)

35-46: Naming inconsistency confirmed; schema reference verified.

The referenced schema model.asset-group-tag.yaml exists. However, the naming inconsistency is real: impactValue uses camelCase (line 31) while the nested properties use snake_case (exposure_percent, asset_group_tag). Ensure this naming variance is intentional or aligns with project conventions.

Optional improvement: Extract the inline items schema (lines 39-46) to a separate file for reusability:

exposures:
  type: array
  readOnly: true
  description: "Enterprise Only"
  items:
    $ref: './model.exposure.yaml'
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c28d5c4 and bcb2ad2.

📒 Files selected for processing (3)
  • cmd/api/src/model/search.go (1 hunks)
  • packages/go/openapi/doc/openapi.json (2 hunks)
  • packages/go/openapi/src/schemas/model.domain-selector.yaml (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
cmd/api/src/model/search.go (1)
packages/go/graphschema/common/common.go (3)
  • Name (53-53)
  • ObjectID (52-52)
  • Collected (57-57)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build BloodHound Container Image / Build and Package Container
  • GitHub Check: run-analysis
  • GitHub Check: run-tests
  • GitHub Check: run-tests
  • GitHub Check: build-ui
🔇 Additional comments (3)
cmd/api/src/model/search.go (2)

43-43: LGTM: Exposures field addition is backward compatible.

The omitempty tag ensures this change is non-breaking for existing clients.


48-53: Clarify JSON naming convention—inconsistency exists but broader codebase uses snake_case.

The JSON tags for Exposure fields use snake_case (exposure_percent, asset_group_tag), while DomainSelector.ImpactValue uses camelCase. However, sampling the broader model package reveals that snake_case is used consistently elsewhere (e.g., OIDCProvider.client_id, SSOProvider.oidc_provider), suggesting this may be an intentional pattern.

  • AssetGroupTag type is confirmed to exist in cmd/api/src/model/assetgrouptags.go:93
  • Verify whether the intended convention is snake_case (matching broader model patterns) or if DomainSelector should be updated for consistency
  • Clarify whether ExposurePercent should have validation constraints
packages/go/openapi/src/schemas/model.domain-selector.yaml (1)

31-34: LGTM: impactValue field properly documented.

The field is correctly marked as readOnly and documented as enterprise-only.

Comment on lines +18893 to +18904
"null.boolean": {
"type": "object",
"properties": {
"bool": {
"type": "boolean"
},
"valid": {
"description": "Valid is true if `boolean` is not `null`",
"type": "boolean"
}
}
},
Copy link
Contributor

Choose a reason for hiding this comment

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

Was just looking at the PR because I'm trying to dive into the BE, but this jumped up to me so wanted to just confirm if it was intended or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good eye! This file is auto-generated, I'm not sure why it decided to move these things up in the file, but it looks like that's what it did.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good, TIL! Thank you for the explanation 😊

Copy link
Contributor

@ALCooper12 ALCooper12 left a comment

Choose a reason for hiding this comment

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

LGTM! 😄

Copy link
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: 4

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bcb2ad2 and e36216d.

📒 Files selected for processing (3)
  • cmd/api/src/model/search.go (1 hunks)
  • packages/go/openapi/doc/openapi.json (2 hunks)
  • packages/go/openapi/src/schemas/model.domain-selector.yaml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/go/openapi/src/schemas/model.domain-selector.yaml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: run-tests
  • GitHub Check: run-tests
  • GitHub Check: run-analysis
  • GitHub Check: build-ui

Comment on lines +38 to 44
Type string `json:"type"`
Name string `json:"name"`
ObjectID string `json:"id"`
Collected bool `json:"collected"`
ImpactValue *int `json:"impactValue,omitempty"`
Exposures Exposures `json:"exposures,omitempty"`
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add documentation for the new enterprise-only fields.

The new ImpactValue and Exposures fields lack godoc comments. Since these are public fields on a public type and are marked as "enterprise only" in the PR description, documentation should clarify:

  • Their purpose and usage
  • That they are enterprise-only features
  • Valid ranges/constraints (e.g., for ImpactValue)

Additionally, there's no visible enforcement mechanism in the code to ensure these fields are only populated in enterprise contexts. Consider documenting where this enforcement occurs or adding validation.

Apply this diff to add documentation:

+// DomainSelector represents a domain or tenant entity with optional enterprise features.
 type DomainSelector struct {
 	Type        string    `json:"type"`
 	Name        string    `json:"name"`
 	ObjectID    string    `json:"id"`
 	Collected   bool      `json:"collected"`
+	// ImpactValue represents the impact value for this domain (Enterprise only).
 	ImpactValue *int      `json:"impactValue,omitempty"`
+	// Exposures contains per-zone exposure information (Enterprise only).
 	Exposures   Exposures `json:"exposures,omitempty"`
 }
📝 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
Type string `json:"type"`
Name string `json:"name"`
ObjectID string `json:"id"`
Collected bool `json:"collected"`
ImpactValue *int `json:"impactValue,omitempty"`
Exposures Exposures `json:"exposures,omitempty"`
}
// DomainSelector represents a domain or tenant entity with optional enterprise features.
type DomainSelector struct {
Type string `json:"type"`
Name string `json:"name"`
ObjectID string `json:"id"`
Collected bool `json:"collected"`
// ImpactValue represents the impact value for this domain (Enterprise only).
ImpactValue *int `json:"impactValue,omitempty"`
// Exposures contains per-zone exposure information (Enterprise only).
Exposures Exposures `json:"exposures,omitempty"`
}
🤖 Prompt for AI Agents
In cmd/api/src/model/search.go around lines 38 to 44, the public fields
ImpactValue and Exposures are missing godoc and enterprise-only guidance; add
concise godoc comments above each field describing their purpose/usage, that
they are enterprise-only features, any constraints (e.g., ImpactValue valid
range like 0–100 or nullable semantics) and examples of when Exposures is
populated; also reference where enterprise enforcement/validation occurs (point
to the package/function that gates population) or add/annotate a validation
check to ensure these fields are only set in enterprise contexts.

Comment on lines +48 to +51
type Exposure struct {
ExposurePercent int `json:"exposure_percent"`
AssetGroupTag AssetGroupTag `json:"asset_group_tag"`
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Add documentation and consider validation for ExposurePercent.

The Exposure type has several concerns:

  1. Missing documentation: This public type lacks godoc comments explaining its purpose and usage.

  2. ExposurePercent validation: The field is an int with no apparent validation. Percentages typically range from 0-100. Consider:

    • Adding validation to ensure values are within valid range
    • Using a pointer *int if you need to distinguish between 0% exposure and unset/unknown
  3. JSON naming inconsistency: This struct uses snake_case for JSON tags (exposure_percent, asset_group_tag), while DomainSelector uses camelCase (impactValue). Consider standardizing to camelCase for consistency.

Apply this diff to add documentation:

+// Exposure represents exposure information for a specific asset group zone (Enterprise only).
 type Exposure struct {
+	// ExposurePercent is the exposure percentage (0-100).
 	ExposurePercent int           `json:"exposure_percent"`
+	// AssetGroupTag identifies the asset group zone.
 	AssetGroupTag   AssetGroupTag `json:"asset_group_tag"`
 }

Run the following script to verify the AssetGroupTag type exists and check for any existing validation:


🏁 Script executed:

#!/bin/bash
# Verify AssetGroupTag type exists and check for validation

# Find AssetGroupTag type definition
echo "=== Searching for AssetGroupTag type definition ==="
ast-grep --pattern 'type AssetGroupTag $_'

# Search for any validation on ExposurePercent
echo -e "\n=== Searching for ExposurePercent validation ==="
rg -nP --type=go '\bExposurePercent\b' -A5 -B5

Length of output: 2200


[/run_scripts]


🏁 Script executed:

#!/bin/bash
# Verify DomainSelector JSON naming pattern and check model.go structure

echo "=== Searching for DomainSelector type definition ==="
rg -nP --type=go 'type DomainSelector struct' -A20

echo -e "\n=== Check for other struct naming patterns in search.go ==="
rg -nP --type=go 'type \w+ struct' cmd/api/src/model/search.go -A8

Length of output: 1938


Add documentation and consider validation for ExposurePercent.

The Exposure type has the following concerns:

  1. Missing documentation: This public type lacks godoc comments explaining its purpose and usage.

  2. ExposurePercent validation: The field is an int with no apparent validation. Percentages typically range from 0-100. Consider:

    • Adding validation to ensure values are within valid range
    • Using a pointer *int if you need to distinguish between 0% exposure and unset/unknown

Apply this diff to add documentation:

+// Exposure represents exposure information for a specific asset group zone (Enterprise only).
 type Exposure struct {
+	// ExposurePercent is the exposure percentage (0-100).
 	ExposurePercent int           `json:"exposure_percent"`
+	// AssetGroupTag identifies the asset group zone.
 	AssetGroupTag   AssetGroupTag `json:"asset_group_tag"`
 }
📝 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
type Exposure struct {
ExposurePercent int `json:"exposure_percent"`
AssetGroupTag AssetGroupTag `json:"asset_group_tag"`
}
// Exposure represents exposure information for a specific asset group zone (Enterprise only).
type Exposure struct {
// ExposurePercent is the exposure percentage (0-100).
ExposurePercent int `json:"exposure_percent"`
// AssetGroupTag identifies the asset group zone.
AssetGroupTag AssetGroupTag `json:"asset_group_tag"`
}
🤖 Prompt for AI Agents
In cmd/api/src/model/search.go around lines 48 to 51, add a godoc comment for
the Exposure type and its fields explaining its purpose and semantics, and
enforce/clarify valid values for ExposurePercent: either change ExposurePercent
to *int if you must distinguish unset vs 0, or keep it as int and add a
Validate() method (or validation in existing validation flow) that returns an
error if ExposurePercent is outside 0–100; ensure the godoc notes the chosen
representation (pointer vs value) and that valid range is 0–100 so callers know
to call/expect validation.

AssetGroupTag AssetGroupTag `json:"asset_group_tag"`
}

type Exposures []Exposure
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add documentation for the Exposures type.

This public type alias lacks a godoc comment.

Apply this diff:

+// Exposures is a collection of exposure information across multiple asset group zones (Enterprise only).
 type Exposures []Exposure
📝 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
type Exposures []Exposure
// Exposures is a collection of exposure information across multiple asset group zones (Enterprise only).
type Exposures []Exposure
🤖 Prompt for AI Agents
In cmd/api/src/model/search.go around line 53, the exported type alias
`Exposures` lacks a godoc comment; add a concise godoc comment immediately above
the `type Exposures []Exposure` line that explains what Exposures represents
(e.g., a slice of Exposure objects used to represent multiple exposure
records/results) so the public type is documented for GoDoc.

Copy link
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: 0

🧹 Nitpick comments (1)
cmd/api/src/model/assetgrouptags.go (1)

186-188: Implementation is correct; consider adding documentation and tests.

The logic correctly identifies tier-zero tags by comparing the position to AssetGroupTierZeroPosition. The use of ValueOrZero() properly handles the nullable Position field.

However, consider the following improvements:

  1. Add godoc comment: Public methods should document their purpose and behavior.
  2. Add unit tests: The PR states "Nothing to test here," but this new method has testable logic (position = 1, position = null, position ≠ 1). Simple unit tests would prevent future regressions.

Example documentation:

+// IsTierZero returns true if this asset group tag represents a Tier Zero asset.
+// It checks whether the Position field equals AssetGroupTierZeroPosition (1).
 func (s AssetGroupTag) IsTierZero() bool {
 	return s.Position.ValueOrZero() == AssetGroupTierZeroPosition
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e36216d and 24b3def.

📒 Files selected for processing (1)
  • cmd/api/src/model/assetgrouptags.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
cmd/api/src/model/assetgrouptags.go (2)
packages/javascript/js-client-library/src/types.ts (1)
  • AssetGroupTag (99-110)
packages/go/analysis/tiering/tiering.go (1)
  • IsTierZero (37-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build BloodHound Container Image / Build and Package Container
  • GitHub Check: build-ui
  • GitHub Check: run-tests
  • GitHub Check: run-analysis
  • GitHub Check: run-tests

@sirisjo sirisjo merged commit 8633aa2 into main Oct 22, 2025
9 checks passed
@sirisjo sirisjo deleted the BED-6601 branch October 22, 2025 15:35
@github-actions github-actions bot locked and limited conversation to collaborators Oct 22, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

api A pull request containing changes affecting the API code. enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants