Add product features support on products.yml#3043
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds per-product feature flags and a derived PublicReferenceProducts subset. Products may declare Sequence Diagram(s)sequenceDiagram
participant FS as FileSystem (config files)
participant ConfigBuilder as ConfigurationFile.CreateProducts
participant ProductsConfig as ProductsConfiguration
participant Substitutions as ConfigurationFile (substitutions)
participant FrontMatter as ProductConverter / YamlSerialization
participant Changelog as docs-builder changelog
FS->>ConfigBuilder: read products.yml (with features)
ConfigBuilder->>ProductsConfig: build Products map
ConfigBuilder->>ProductsConfig: derive PublicReferenceProducts (filter public-reference)
ProductsConfig->>Substitutions: provide PublicReferenceProducts
Substitutions->>Substitutions: generate product.{id} substitutions only for PublicReferenceProducts
ProductsConfig->>FrontMatter: provide PublicReferenceProducts
FrontMatter->>FrontMatter: validate frontmatter product IDs against PublicReferenceProducts
ProductsConfig->>Changelog: provide Products (all configured products)
Changelog->>Changelog: allow changelog entries for any product (including release-notes-only)
Suggested labels
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches✨ Simplify code
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Elastic.Documentation.Configuration/Products/ProductExtensions.cs`:
- Around line 25-27: When creating products in CreateProducts
(ProductExtensions.cs), detect when
ResolveVersioningSystem(versionsConfiguration, kvp.Value.Versioning ?? kvp.Key)
returns null while the product is marked as public-reference
(kvp.Value.PublicReference == true) and throw a descriptive exception to fail
fast; keep allowing a null versioning system for release-notes-only products
(e.g., kvp.Value.ReleaseNotesOnly == true) so those still accept null. Update
both places where VersioningSystem is set (the block using
ResolveVersioningSystem at the top and the similar block at lines ~46-49) to
perform this null-check after calling ResolveVersioningSystem and throw a clear
error referencing the product key when public-reference is enabled. Ensure the
exception message includes the product identifier (kvp.Key) and the
invalid/missing versioning value to aid debugging.
🪄 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: Pro
Run ID: 9a50b598-b075-49c7-974c-a01e100de1b8
📒 Files selected for processing (21)
config/changelog.example.ymlconfig/products.ymldocs/configure/site/products.mddocs/contribute/changelog.mddocs/syntax/frontmatter.mdsrc/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cssrc/Elastic.Documentation.Configuration/Products/Product.cssrc/Elastic.Documentation.Configuration/Products/ProductExtensions.cssrc/Elastic.Markdown/Myst/FrontMatter/Products.cssrc/Elastic.Markdown/Myst/YamlSerialization.cstests-integration/Elastic.Assembler.IntegrationTests/TestHelpers.cstests-integration/Mcp.Remote.IntegrationTests/McpToolsIntegrationTestsBase.cstests/Elastic.ApiExplorer.Tests/TestHelpers.cstests/Elastic.Changelog.Tests/Changelogs/ChangelogTestBase.cstests/Elastic.Documentation.Build.Tests/TestHelpers.cstests/Elastic.Documentation.Configuration.Tests/CrossLinkRegistryTests.cstests/Elastic.Documentation.Configuration.Tests/DocumentInferrerServiceTests.cstests/Elastic.Documentation.Configuration.Tests/ProductFeaturesTests.cstests/Elastic.Documentation.Configuration.Tests/VersionInferenceTests.cstests/Elastic.Markdown.Tests/TestHelpers.cstests/authoring/Framework/Setup.fs
7a01172 to
f186f95
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/Elastic.Documentation.Configuration/Products/ProductExtensions.cs (1)
24-27:⚠️ Potential issue | 🟠 MajorFail fast when
public-referenceproduct has unresolved versioningAt Line 26,
nullversioning is only handled for non-public-reference products. Public-reference products can still be created withVersioningSystem = null, which defers failure to later paths.Suggested fix
var features = ResolveFeatures(kvp.Key, kvp.Value.Features); var versioningSystem = ResolveVersioningSystem(versionsConfiguration, kvp.Value.Versioning ?? kvp.Key); - if (versioningSystem is null && !features.PublicReference) - versioningSystem = VersioningSystem.None; + if (versioningSystem is null) + { + if (!features.PublicReference) + versioningSystem = VersioningSystem.None; + else + throw new InvalidOperationException( + $"Product '{kvp.Key}' has invalid or missing versioning '{kvp.Value.Versioning ?? kvp.Key}' while 'public-reference' is enabled." + ); + }As per coding guidelines, "Fail fast by throwing exceptions early rather than hiding errors."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Elastic.Documentation.Configuration/Products/ProductExtensions.cs` around lines 24 - 27, The code calls ResolveVersioningSystem(...) to populate versioningSystem but only defaults null to VersioningSystem.None when features.PublicReference is false; update the post-ResolveVersioningSystem check so that if versioningSystem is null and features.PublicReference is true you throw an exception (e.g., InvalidOperationException) with a clear message including the product identifier (kvp.Key or kvp.Value.Name) to fail fast; keep the existing branch that sets VersioningSystem.None when features.PublicReference is false. Ensure this change is made in ProductExtensions near the ResolveVersioningSystem(...) usage to prevent creating public-reference products with unresolved versioning.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/Elastic.Documentation.Configuration/Products/ProductExtensions.cs`:
- Around line 24-27: The code calls ResolveVersioningSystem(...) to populate
versioningSystem but only defaults null to VersioningSystem.None when
features.PublicReference is false; update the post-ResolveVersioningSystem check
so that if versioningSystem is null and features.PublicReference is true you
throw an exception (e.g., InvalidOperationException) with a clear message
including the product identifier (kvp.Key or kvp.Value.Name) to fail fast; keep
the existing branch that sets VersioningSystem.None when
features.PublicReference is false. Ensure this change is made in
ProductExtensions near the ResolveVersioningSystem(...) usage to prevent
creating public-reference products with unresolved versioning.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 428f2b9c-f25b-4a6e-88ab-4318a4b85691
📒 Files selected for processing (3)
src/Elastic.Documentation.Configuration/Products/ProductExtensions.cssrc/Elastic.Documentation.Configuration/Versions/VersionConfiguration.cstests/Elastic.Documentation.Configuration.Tests/ProductFeaturesTests.cs
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/Elastic.Documentation.Configuration.Tests/ProductFeaturesTests.cs
ValidateRedirects now uses Configuration.IsExcluded (docset globs, folder TOC excludes, include overrides) so deleted or renamed Markdown under excluded trees does not require redirects.yml entries. Adds ConfigurationFileExcludeTests for Elasticsearch-style exclude globs. Made-with: Cursor
) * Search: Add content_last_updated field for content-only change tracking Add a new content_last_updated field to DocumentationDocument that only advances when the page content (stripped_body) actually changes, ignoring metadata-only changes like navigation reordering or mapping rollovers. Uses a persistent lookup index (docs-{type}-content-dates-{env}) to preserve timestamps across index rollovers. Content hashing normalizes whitespace so reformatting doesn't trigger false updates. Also updates the sitemap to use content_last_updated instead of last_updated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Fix JSON002 lint error in ContentDateLookup Use JsonObject instead of raw string literal for index mapping to satisfy the dotnet-format JSON002 rule. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Fix thread safety, JSON escaping, and unobserved exception - Use ConcurrentDictionary for _existing and _changed since Resolve is called from Parallel.ForEachAsync - Use JsonEncodedText.Encode for AOT-safe JSON escaping that handles control characters and Unicode - Use Task.WhenAll to observe both tasks when running lookup load and orchestrator start in parallel Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Replace ContentDateLookup with Elasticsearch enrich policy Move content_last_updated resolution from an in-memory drain-and-compare approach to an Elasticsearch enrich policy + ingest pipeline. This eliminates the startup memory overhead of loading the entire lookup index into a ConcurrentDictionary and removes ~200 lines of hand-rolled PIT pagination. The ingest pipeline compares content hashes at index time via an enrich processor and painless script. After indexing, the lookup index is synced via reindex from the lexical index, which also implicitly cleans up orphaned entries for deleted pages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Harden ContentDateEnrichment error handling and refresh timing Throw InvalidOperationException on setup failures (index creation, enrich policy, pipeline) instead of logging warnings and continuing silently. This ensures CI fails fast when infrastructure setup is broken rather than indexing documents without content_last_updated. Add an explicit index refresh between reindex and enrich policy execution in SyncLookupIndexAsync so newly reindexed documents are visible when the policy snapshots the lookup index. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Fix ingest timestamp access in content-date pipeline Replace ctx._ingest.timestamp (not available in Painless script processors) with a set processor using Mustache {{{_ingest.timestamp}}}. The set processor pre-sets content_last_updated to the ingest timestamp, and the script processor only overwrites it when the enrich lookup finds a matching content hash. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Throw on failed async task start in ElasticsearchOperations DeleteByQueryAsync, ReindexAsync, and UpdateByQueryAsync now throw InvalidOperationException when PostAsyncTaskAsync returns null instead of silently skipping the poll. These are wait-for-completion methods where callers expect the operation to have succeeded on return. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enable autocomplete support for ai_questions by adding a SearchAsYouType completion sub-field with synonym analyzers. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
--- updated-dependencies: - dependency-name: MartinCostello.Logging.XUnit.v3 dependency-version: 0.7.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
--- updated-dependencies: - dependency-name: Elastic.Ingest.Elasticsearch dependency-version: 0.41.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…3099) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Changes API now queries and sorts by content_last_updated so that metadata-only changes (nav reordering, mapping rollovers) no longer surface in the feed. The API response shape is unchanged — lastUpdated is still the JSON field name — keeping this non-breaking for consumers. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…3098) * Search: Use staging index + alias swap for content date lookup sync Replace the delete-then-reindex flow in ContentDateEnrichment with a staging index + atomic alias swap to eliminate the window where the lookup index is empty. If a reindex fails, the previous lookup data remains intact behind the alias. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Harden staging index lifecycle error handling Make RefreshIndexAsync throw on failure so SwapAliasAsync never runs against an unrefreshed staging index. Distinguish 404 from transient errors in ResolveBackingIndexAsync and fail deterministically when the alias points at multiple indices. Add a GUID suffix to staging index names for collision resistance under concurrent runs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Search: Include synonym ID in synonym rule values The synonym dictionary uses the first term as the key and remaining terms as values. When publishing to Elasticsearch, only the values were included in the synonym string, omitting the key term itself. This meant e.g. "ilm" was the rule ID but not part of the synonym set, so it wouldn't match "index lifecycle management". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Change synonyms from dictionary to flat list The dictionary structure used the first term as the key and Skip(1) for values, which artificially separated one synonym from its group. This caused the key term to be omitted from synonym rules sent to Elasticsearch. Replace Dictionary<string, string[]> with IReadOnlyList<string[]> so all terms in a synonym group are treated equally. The first term is still used as the ES rule ID for readability but is no longer excluded from the synonym string. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add .superset to gitignore and remove from tracking Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Fix F# test to use IReadOnlyList for synonyms The synonyms type was changed from Dictionary<string, string[]> to IReadOnlyList<string[]> in 5cea709 but the F# authoring test was not updated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Add missing final newline to SearchConfiguration.cs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The sitemap command was querying the lexical index, which does not have content_last_updated populated. Switch to the semantic index where the field is available. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enrich policies can't be updated or deleted while referenced by a pipeline. Version the policy name with a SHA256 hash of its definition so re-runs reuse the existing policy, and definition changes create a new one alongside the old. After the pipeline is updated to reference the new policy, old policies are cleaned up. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com>
* Search: Simplify ai_questions prompt for search-friendly output The current prompt generates overly complex questions that don't match real user search behavior. Redesign the prompt to produce shorter, simpler questions (3-10 words) suitable for autocomplete and semantic search — e.g. "What is agent builder?" instead of "How do I import external tools using Model Context Protocol?" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Fix contradictory guidance in ai_questions prompt The prompt said "Avoid specific API names" but then used "What is the bulk API?" as an example. Remove the API name restriction since we want questions to reference feature/product names naturally. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Add ai_autocomplete_questions field with simplified prompt Restore the original ai_questions prompt and add a new ai_autocomplete_questions field with a prompt targeting short, simple questions (3-10 words) suitable for search bar autocomplete. Includes lexical mapping with SearchAsYouType completion multi-field and semantic text mapping. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Add completion suggest multi-field to ai_questions mappings Add suggest completion multi-field to both ai_questions and ai_autocomplete_questions, matching the approach in #3108. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Remove suggest multi-field from ai_questions mapping Keep the suggest completion field only on ai_autocomplete_questions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Dependencies: Bump Elastic.Ingest.Elasticsearch to 0.41.1 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Dependencies: Bump Elastic.Mapping to 0.41.1 Transitive dependency of Elastic.Ingest.Elasticsearch 0.41.1. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: Update config/versions.yml apm-agent-go 2.7.7 Made with ❤️️ by updatecli * chore: Update config/versions.yml terraform-google-edot-cf 0.1.3 Made with ❤️️ by updatecli --------- Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com>
Set Assembler stack.next to the upcoming minor for staging validation after feature freeze (Stack 9.4.0 GA). Refs elastic/dev#3502 Made-with: Cursor
* Search: Add post-indexing content date resolution via update_by_query HashedBulkUpdate uses bulk update actions (scripted upserts) which skip Elasticsearch ingest pipelines, so content_last_updated was never set during normal indexing. This adds a ResolveContentDatesAsync step that runs _update_by_query with the enrichment pipeline after indexing completes, and switches StopAsync to use read aliases instead of the write target (which is removed after CompleteAsync). Includes integration tests against a real Elasticsearch container validating cold-start, date preservation, change detection, and the bulk-update pipeline gap. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Fix lint warnings in content date enrichment tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Felipe Cotti <felipe.cotti@elastic.co>
) * Search: Add content_last_updated field for content-only change tracking Add a new content_last_updated field to DocumentationDocument that only advances when the page content (stripped_body) actually changes, ignoring metadata-only changes like navigation reordering or mapping rollovers. Uses a persistent lookup index (docs-{type}-content-dates-{env}) to preserve timestamps across index rollovers. Content hashing normalizes whitespace so reformatting doesn't trigger false updates. Also updates the sitemap to use content_last_updated instead of last_updated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Fix JSON002 lint error in ContentDateLookup Use JsonObject instead of raw string literal for index mapping to satisfy the dotnet-format JSON002 rule. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Fix thread safety, JSON escaping, and unobserved exception - Use ConcurrentDictionary for _existing and _changed since Resolve is called from Parallel.ForEachAsync - Use JsonEncodedText.Encode for AOT-safe JSON escaping that handles control characters and Unicode - Use Task.WhenAll to observe both tasks when running lookup load and orchestrator start in parallel Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Replace ContentDateLookup with Elasticsearch enrich policy Move content_last_updated resolution from an in-memory drain-and-compare approach to an Elasticsearch enrich policy + ingest pipeline. This eliminates the startup memory overhead of loading the entire lookup index into a ConcurrentDictionary and removes ~200 lines of hand-rolled PIT pagination. The ingest pipeline compares content hashes at index time via an enrich processor and painless script. After indexing, the lookup index is synced via reindex from the lexical index, which also implicitly cleans up orphaned entries for deleted pages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Harden ContentDateEnrichment error handling and refresh timing Throw InvalidOperationException on setup failures (index creation, enrich policy, pipeline) instead of logging warnings and continuing silently. This ensures CI fails fast when infrastructure setup is broken rather than indexing documents without content_last_updated. Add an explicit index refresh between reindex and enrich policy execution in SyncLookupIndexAsync so newly reindexed documents are visible when the policy snapshots the lookup index. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Fix ingest timestamp access in content-date pipeline Replace ctx._ingest.timestamp (not available in Painless script processors) with a set processor using Mustache {{{_ingest.timestamp}}}. The set processor pre-sets content_last_updated to the ingest timestamp, and the script processor only overwrites it when the enrich lookup finds a matching content hash. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Throw on failed async task start in ElasticsearchOperations DeleteByQueryAsync, ReindexAsync, and UpdateByQueryAsync now throw InvalidOperationException when PostAsyncTaskAsync returns null instead of silently skipping the poll. These are wait-for-completion methods where callers expect the operation to have succeeded on return. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Search: Add post-indexing content date resolution via update_by_query HashedBulkUpdate uses bulk update actions (scripted upserts) which skip Elasticsearch ingest pipelines, so content_last_updated was never set during normal indexing. This adds a ResolveContentDatesAsync step that runs _update_by_query with the enrichment pipeline after indexing completes, and switches StopAsync to use read aliases instead of the write target (which is removed after CompleteAsync). Includes integration tests against a real Elasticsearch container validating cold-start, date preservation, change detection, and the bulk-update pipeline gap. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Search: Fix lint warnings in content date enrichment tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This pull request introduces a new "features" system for products, allowing finer control over which subsystems each product participates in (such as release notes and public documentation). It enables products to be included in release notes without requiring them to be public documentation products, and updates the configuration, schema, and codebase to support this distinction. Documentation and validation logic are updated to explain and enforce the new behavior.
Features system for products
featuresmapping to product definitions inproducts.ymland documented it inproducts.md, allowing products to opt in topublic-reference(public docs) and/orrelease-notes(changelog) features. Products with nofeaturesmapping participate in all subsystems for backward compatibility. [1] [2] [3]public-referenceenabled can be used in documentation frontmatter or as page references; products with onlyrelease-notesenabled can appear in changelogs but not in docs. [1] [2] [3] [4]release-notesfeature are valid for changelogs, and provided guidance for adding such products.Codebase changes to support features
ProductFeaturestype and updated theProductandProductsConfigurationrecords to track feature participation, including a newPublicReferenceProductscollection for fast lookups. [1] [2] [3] [4]PublicReferenceProductscollection where appropriate, ensuring only eligible products are used in documentation contexts. [1] [2] [3] [4]Versioning system improvements
noneversioning system to support products that only participate in release notes and do not have public documentation or versions. [1] [2]noneversioning system to products that are not public-reference and have no explicit versioning.Test and integration updates
PublicReferenceProductsproperty inProductsConfiguration. [1] [2] [3] [4] [5] [6] [7] [8]Documentation updates
These changes make it possible to track internal tools and other non-public products in release notes without exposing them as documentation products, while maintaining backward compatibility for existing product definitions.