Skip to content
Draft
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9abe711
Introduce VersionSpec, a model meant for advanced version handling fo…
cotti Dec 7, 2025
73e8dd7
Apply usage of VersionSpec in Applicability
cotti Dec 8, 2025
e6d710f
Show base version if we don't get a specified version for a versioned…
cotti Dec 8, 2025
4bd996a
Adjust tests to better match the currently expected output
cotti Dec 8, 2025
7ead716
Add VersionSpec to YamlSerialization
cotti Dec 8, 2025
fcae628
Typo!
cotti Dec 8, 2025
e435de9
No need for it to be initialized here after all.
cotti Dec 8, 2025
7f5f2be
Handle "all" explicitly
cotti Dec 8, 2025
00ab8d0
Adopting a few review suggestions
cotti Dec 8, 2025
2613a13
Fix warnings on docs-builder docs
cotti Dec 8, 2025
d4cce98
Include applicability table in req.md
cotti Dec 8, 2025
043c470
Fix markdown formatting
cotti Dec 8, 2025
11580bd
Products with versions should show their base versions in badges with…
cotti Dec 8, 2025
56af075
Introduce implicit semantics for multiple lifecycles
cotti Dec 11, 2025
7d0f792
Add more examples in docs
cotti Dec 11, 2025
13301d0
Typo
cotti Dec 11, 2025
7cd899b
Fix interpretation of lifecycle - using ranges after current stack ve…
cotti Dec 11, 2025
072479e
Change popup to a popover component, alongside static descriptions
cotti Dec 12, 2025
8db2669
Preview: send a limited assembler build to the preview environment
cotti Dec 12, 2025
d953e71
Merge remote-tracking branch 'origin/main' into feat/versionspec
cotti Dec 12, 2025
817cfc6
Fix Call to the Renderer
cotti Dec 12, 2025
0f2ce56
Remove unused var
cotti Dec 12, 2025
917d18b
typo
cotti Dec 12, 2025
6a1fde8
Invalidate correct subfolder
cotti Dec 12, 2025
0458208
Fix policy path
cotti Dec 12, 2025
cd331f0
Fix policy
cotti Dec 12, 2025
d728b6a
Send path-prefix to assembler.
cotti Dec 12, 2025
b799f69
Revert temporary assembler build for now
cotti Dec 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions docs/_snippets/applies_to-version.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
`applies_to` accepts the following version formats:

* `Major.Minor`
* `Major.Minor.Patch`
* **Greater than or equal to**: `x.x+`, `x.x`, `x.x.x+`, `x.x.x` (default behavior when no operator specified)
* **Range (inclusive)**: `x.x-y.y`, `x.x.x-y.y.y`, `x.x-y.y.y`, `x.x.x-y.y`
* **Exact version**: `=x.x`, `=x.x.x`

Regardless of the version format used in the source file, the version number is always rendered in the `Major.Minor.Patch` format.
**Version Display:**

- Versions are always displayed as **Major.Minor** (e.g., `9.1`) in badges, regardless of the format used in source files.
- Each version represents the **latest patch** of that minor version (e.g., `9.1` means 9.1.0, 9.1.1, 9.1.6, etc.).
- The `+` symbol indicates "this version and later" (e.g., `9.1+` means 9.1.0 and all subsequent releases).
- Ranges show both versions (e.g., `9.0-9.2`) when both are released, or convert to `+` format if the end version is unreleased.

:::{note}
**Automatic Version Sorting**: When you specify multiple versions for the same product, the build system automatically sorts them in descending order (highest version first) regardless of the order you write them in the source file. For example, `stack: ga 8.18.6, ga 9.1.2, ga 8.19.2, ga 9.0.6` will be displayed as `stack: ga 9.1.2, ga 9.0.6, ga 8.19.2, ga 8.18.6`. Items without versions (like `ga` without a version or `all`) are sorted last.
**Automatic Version Sorting**: When you specify multiple versions for the same product, the build system automatically sorts them in descending order (highest version first) regardless of the order you write them in the source file. For example, `stack: ga 9.1, beta 9.0, preview 8.18` will be displayed with the highest priority lifecycle and version first. Items without versions are sorted last.
:::
4 changes: 2 additions & 2 deletions docs/syntax/_snippets/inline-level-applies-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ This example shows how to use directly a key from the second level of the `appli
::::{tab-item} Output

- {applies_to}`serverless: ga` {applies_to}`stack: ga 9.1.0`
- {applies_to}`edot_python: preview 1.7.0, ga 1.8.0` {applies_to}`apm_agent_java: beta 1.0.0, ga 1.2.0`
- {applies_to}`edot_python: preview =1.7.0, ga 1.8.0` {applies_to}`apm_agent_java: beta =1.0.0, ga 1.2.0`
- {applies_to}`stack: ga 9.0` {applies_to}`eck: ga 3.0`

::::

::::{tab-item} Markdown
```markdown
- {applies_to}`serverless: ga` {applies_to}`stack: ga 9.1.0`
- {applies_to}`edot_python: preview 1.7.0, ga 1.8.0` {applies_to}`apm_agent_java: beta 1.0.0, ga 1.2.0`
- {applies_to}`edot_python: preview =1.7.0, ga 1.8.0` {applies_to}`apm_agent_java: beta =1.0.0, ga 1.2.0`
- {applies_to}`stack: ga 9.0` {applies_to}`eck: ga 3.0`
```
::::
Expand Down
91 changes: 83 additions & 8 deletions docs/syntax/applies.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,41 @@ Where:
- The lifecycle is mandatory.
- The version is optional.

### Version Syntax

Versions can be specified using several formats to indicate different applicability scenarios:

| Description | Syntax | Example | Badge Display |
|:------------|:-------|:--------|:--------------|
| **Greater than or equal to** (default) | `x.x+` `x.x` `x.x.x+` `x.x.x` | `ga 9.1` or `ga 9.1+` | `9.1+` |
| **Range** (inclusive) | `x.x-y.y` `x.x.x-y.y.y` | `preview 9.0-9.2` | `9.0-9.2` or `9.0+`* |
| **Exact version** | `=x.x` `=x.x.x` | `beta =9.1` | `9.1` |

\* Range display depends on release status of the second version.

**Important notes:**

- Versions are always displayed as **Major.Minor** (e.g., `9.1`) in badges, regardless of whether you specify patch versions in the source.
- Each version statement corresponds to the **latest patch** of the specified minor version (e.g., `9.1` represents 9.1.0, 9.1.1, 9.1.6, etc.).
- When critical patch-level differences exist, use plain text descriptions alongside the badge rather than specifying patch versions.

### Version Validation Rules

The build process enforces the following validation rules:

- **One version per lifecycle**: Each lifecycle (GA, Preview, Beta, etc.) can only have one version declaration.
- ✅ `stack: ga 9.2+, beta 9.0-9.1`
- ❌ `stack: ga 9.2, ga 9.3`
- **One "greater than" per key**: Only one lifecycle per product key can use the `+` (greater than or equal to) syntax.
- ✅ `stack: ga 9.2+, beta 9.0-9.1`
- ❌ `stack: ga 9.2+, beta 9.0+`
- **Valid range order**: In ranges, the first version must be less than or equal to the second version.
- ✅ `stack: preview 9.0-9.2`
- ❌ `stack: preview 9.2-9.0`
- **No version overlaps**: Versions for the same key cannot overlap (ranges are inclusive).
- ✅ `stack: ga 9.2+, beta 9.0-9.1`
- ❌ `stack: ga 9.2+, beta 9.0-9.2`

### Page level

Page level annotations are added in the YAML frontmatter, starting with the `applies_to` key and following the [key-value reference](#key-value-reference). For example:
Expand Down Expand Up @@ -134,6 +169,22 @@ Use the following key-value reference to find the appropriate key and value for

## Examples

### Version Syntax Examples

The following table demonstrates the various version syntax options and their rendered output:

| Source Syntax | Description | Badge Display | Notes |
|:-------------|:------------|:--------------|:------|
| `stack: ga 9.1` | Greater than or equal to 9.1 | `Stack│9.1+` | Default behavior, equivalent to `9.1+` |
| `stack: ga 9.1+` | Explicit greater than or equal to | `Stack│9.1+` | Explicit `+` syntax |
| `stack: preview 9.0-9.2` | Range from 9.0 to 9.2 (inclusive) | `Stack│Preview 9.0-9.2` | Shows range if 9.2.0 is released |
| `stack: preview 9.0-9.3` | Range where end is unreleased | `Stack│Preview 9.0+` | Shows `+` if 9.3.0 is not released |
| `stack: beta =9.1` | Exact version 9.1 only | `Stack│Beta 9.1` | No `+` symbol for exact versions |
| `stack: ga 9.2+, beta 9.0-9.1` | Multiple lifecycles | `Stack│9.2+` | Only highest priority lifecycle shown |
| `stack: ga 9.3, beta 9.1+` | Unreleased GA with Preview | `Stack│Beta 9.1+` | Shows Beta when GA unreleased with 2+ lifecycles |
| `serverless: ga` | No version (base 99999) | `Serverless` | No version badge for unversioned products |
| `deployment:`<br/>` ece: ga 9.0+` | Nested deployment syntax | `ECE│9.0+` | Deployment products shown separately |

### Versioning examples

Versioned products require a `version` tag to be used with the `lifecycle` tag:
Expand Down Expand Up @@ -240,22 +291,46 @@ applies_to:

## Look and feel

### Version Syntax Demonstrations

:::::{dropdown} New version syntax examples

The following examples demonstrate the new version syntax capabilities:

**Greater than or equal to:**
- {applies_to}`stack: ga 9.1` (implicit `+`)
- {applies_to}`stack: ga 9.1+` (explicit `+`)
- {applies_to}`stack: preview 9.0+`

**Ranges:**
- {applies_to}`stack: preview 9.0-9.2` (range display when both released)
- {applies_to}`stack: beta 9.1-9.3` (converts to `+` if end unreleased)

**Exact versions:**
- {applies_to}`stack: beta =9.1` (no `+` symbol)
- {applies_to}`stack: deprecated =9.0`

**Multiple lifecycles:**
- {applies_to}`stack: ga 9.2+, beta 9.0-9.1` (shows highest priority)

:::::

### Block

:::::{dropdown} Block examples

```{applies_to}
stack: preview 9.1
stack: preview 9.1+
serverless: ga

apm_agent_dotnet: ga 1.0.0
apm_agent_java: beta 1.0.0
edot_dotnet: preview 1.0.0
apm_agent_dotnet: ga 1.0+
apm_agent_java: beta 1.0+
edot_dotnet: preview 1.0+
edot_python:
edot_node: ga 1.0.0
elasticsearch: preview 9.0.0
security: removed 9.0.0
observability: deprecated 9.0.0
edot_node: ga 1.0+
elasticsearch: preview 9.0+
security: removed 9.0
observability: deprecated 9.0+
```
:::::

Expand Down
96 changes: 91 additions & 5 deletions docs/testing/req.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ mapped_pages:
# Requirements

```{applies_to}
stack: preview 9.0, ga 9.1
stack: preview =9.0, ga 9.1
```

1. Select **Create** to create a new policy, or select **Edit** {icon}`pencil` to open an existing policy.
Expand All @@ -20,13 +20,9 @@ stack: preview 9.0, ga 9.1
This tutorial is based on Elasticsearch 9.0. This tutorial is based on Elasticsearch 9.0.
This tutorial is based on Elasticsearch 9.0.

what


To follow this tutorial you will need to install the following components:



- An installation of Elasticsearch, based on our hosted [Elastic Cloud](https://www.elastic.co/cloud) service (which includes a free trial period), or a self-hosted service that you run on your own computer. See the Install Elasticsearch section above for installation instructions.
- A [Python](https://python.org) interpreter. Make sure it is a recent version, such as Python 3.8 or newer.

Expand All @@ -38,3 +34,93 @@ The tutorial assumes that you have no previous knowledge of Elasticsearch or gen


{applies_to}`ece: removed`

## Applies To Badge Scenarios

Below is a table of `applies_to` badge scenarios.

### No version specified (serverless)

| Badge | Raw Markdown |
|-------|--------------|
| {applies_to}`serverless: ga` | `` {applies_to}`serverless: ga` `` |
| {applies_to}`serverless: preview` | `` {applies_to}`serverless: preview` `` |
| {applies_to}`serverless: beta` | `` {applies_to}`serverless: beta` `` |
| {applies_to}`serverless: deprecated` | `` {applies_to}`serverless: deprecated` `` |
| {applies_to}`serverless: removed` | `` {applies_to}`serverless: removed` `` |

### No version specified (stack)

| Badge | Raw Markdown |
|-------|--------------|
| {applies_to}`stack: ga` | `` {applies_to}`stack: ga` `` |
| {applies_to}`stack: preview` | `` {applies_to}`stack: preview` `` |
| {applies_to}`stack: beta` | `` {applies_to}`stack: beta` `` |
| {applies_to}`stack: deprecated` | `` {applies_to}`stack: deprecated` `` |
| {applies_to}`stack: removed` | `` {applies_to}`stack: removed` `` |

### No version specified (product)

| Badge | Raw Markdown |
|-------|--------------|
| {applies_to}`apm_agent_python: ga` | `` {applies_to}`apm_agent_python: ga` `` |
| {applies_to}`apm_agent_python: preview` | `` {applies_to}`apm_agent_python: preview` `` |
| {applies_to}`apm_agent_python: beta` | `` {applies_to}`apm_agent_python: beta` `` |
| {applies_to}`apm_agent_python: deprecated` | `` {applies_to}`apm_agent_python: deprecated` `` |
| {applies_to}`apm_agent_python: removed` | `` {applies_to}`apm_agent_python: removed` `` |


### Greater than or equal to (x.x+ / x.x)

| Badge | Raw Markdown |
|-------|--------------|
| {applies_to}`stack: ga 9.1` | `` {applies_to}`stack: ga 9.1` `` |
| {applies_to}`stack: ga 9.1+` | `` {applies_to}`stack: ga 9.1+` `` |
| {applies_to}`stack: preview 9.0+` | `` {applies_to}`stack: preview 9.0+` `` |
| {applies_to}`stack: beta 9.1+` | `` {applies_to}`stack: beta 9.1+` `` |
| {applies_to}`stack: deprecated 9.0+` | `` {applies_to}`stack: deprecated 9.0+` `` |
| {applies_to}`stack: removed 9.0` | `` {applies_to}`stack: removed 9.0` `` |
| {applies_to}`apm_agent_python: ga 6.0` | `` {applies_to}`apm_agent_python: ga 6.0` `` |
| {applies_to}`apm_agent_python: ga 6.5+` | `` {applies_to}`apm_agent_python: ga 6.5+` `` |
| {applies_to}`apm_agent_python: preview 6.24+` | `` {applies_to}`apm_agent_python: preview 6.24+` `` |
| {applies_to}`apm_agent_python: beta 6.1+` | `` {applies_to}`apm_agent_python: beta 6.1+` `` |
| {applies_to}`apm_agent_python: deprecated 6.0+` | `` {applies_to}`apm_agent_python: deprecated 6.0+` `` |
| {applies_to}`apm_agent_python: removed 6.0` | `` {applies_to}`apm_agent_python: removed 6.0` `` |


### Range (x.x-y.y)

| Badge | Raw Markdown |
|-------|--------------|
| {applies_to}`stack: ga 9.0-9.2` | `` {applies_to}`stack: ga 9.0-9.2` `` |
| {applies_to}`stack: preview 9.0-9.2` | `` {applies_to}`stack: preview 9.0-9.2` `` |
| {applies_to}`stack: beta 9.0-9.1` | `` {applies_to}`stack: beta 9.0-9.1` `` |
| {applies_to}`stack: deprecated 9.0-9.2` | `` {applies_to}`stack: deprecated 9.0-9.2` `` |
| {applies_to}`apm_agent_python: ga 6.0-6.23` | `` {applies_to}`apm_agent_python: ga 6.0-6.23` `` |

### Exact version (=x.x)

| Badge | Raw Markdown |
|-------|--------------|
| {applies_to}`stack: ga =9.1` | `` {applies_to}`stack: ga =9.1` `` |
| {applies_to}`stack: preview =9.0` | `` {applies_to}`stack: preview =9.0` `` |
| {applies_to}`stack: beta =9.1` | `` {applies_to}`stack: beta =9.1` `` |
| {applies_to}`stack: deprecated =9.0` | `` {applies_to}`stack: deprecated =9.0` `` |
| {applies_to}`stack: removed =9.0` | `` {applies_to}`stack: removed =9.0` `` |
| {applies_to}`apm_agent_python: ga =6.20` | `` {applies_to}`apm_agent_python: ga =6.20` `` |

### Multiple lifecycles

| Badge | Raw Markdown |
|-------|--------------|
| {applies_to}`stack: ga 9.2+, beta 9.0-9.1` | `` {applies_to}`stack: ga 9.2+, beta 9.0-9.1` `` |
| {applies_to}`stack: ga 9.2+, preview 9.0-9.1` | `` {applies_to}`stack: ga 9.2+, preview 9.0-9.1` `` |

### Deployment types

| Badge | Raw Markdown |
|-------|--------------|
| {applies_to}`ece: ga 9.0+` | `` {applies_to}`ece: ga 9.0+` `` |
| {applies_to}`eck: preview 9.1+` | `` {applies_to}`eck: preview 9.1+` `` |
| {applies_to}`ece: deprecated 6.7+` | `` {applies_to}`ece: deprecated 6.7+` `` |
| {applies_to}`ece: removed` | `` {applies_to}`ece: removed` `` |
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ private bool ShouldIncludeOperation(OpenApiOperation operation, string product)
return true; // Could not parse version, safe to include

// Get current version for the product
var versioningSystemId = product == "elasticsearch"
var versioningSystemId = product.Equals("elasticsearch", StringComparison.OrdinalIgnoreCase)
? VersioningSystemId.Stack
: VersioningSystemId.Stack; // Both use Stack for now

Expand Down Expand Up @@ -294,14 +294,14 @@ private static ProductLifecycle ParseLifecycle(string stateValue)
/// <summary>
/// Parses the version from "Added in X.Y.Z" pattern in the x-state string.
/// </summary>
private static SemVersion? ParseVersion(string stateValue)
private static VersionSpec? ParseVersion(string stateValue)
{
var match = AddedInVersionRegex().Match(stateValue);
if (!match.Success)
return null;

var versionString = match.Groups[1].Value;
return SemVersion.TryParse(versionString, out var version) ? version : null;
return VersionSpec.TryParse(versionString, out var version) ? version : null;
}

/// <summary>
Expand Down
20 changes: 10 additions & 10 deletions src/Elastic.Documentation/AppliesTo/Applicability.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public static bool TryParse(string? value, IList<(Severity, string)> diagnostics
return false;

// Sort by version in descending order (the highest version first)
// Items without versions (AllVersions.Instance) are sorted last
// Items without versions (AllVersionsSpec.Instance) are sorted last
var sortedApplications = applications.OrderDescending().ToArray();
availability = new AppliesCollection(sortedApplications);
return true;
Expand Down Expand Up @@ -98,12 +98,12 @@ public override string ToString()
public record Applicability : IComparable<Applicability>, IComparable
{
public ProductLifecycle Lifecycle { get; init; }
public SemVersion? Version { get; init; }
public VersionSpec? Version { get; init; }

public static Applicability GenerallyAvailable { get; } = new()
{
Lifecycle = ProductLifecycle.GenerallyAvailable,
Version = AllVersions.Instance
Version = AllVersionsSpec.Instance
};


Expand All @@ -126,8 +126,8 @@ public string GetLifeCycleName() =>
/// <inheritdoc />
public int CompareTo(Applicability? other)
{
var xIsNonVersioned = Version is null || ReferenceEquals(Version, AllVersions.Instance);
var yIsNonVersioned = other?.Version is null || ReferenceEquals(other.Version, AllVersions.Instance);
var xIsNonVersioned = Version is null || ReferenceEquals(Version, AllVersionsSpec.Instance);
var yIsNonVersioned = other?.Version is null || ReferenceEquals(other.Version, AllVersionsSpec.Instance);

if (xIsNonVersioned && yIsNonVersioned)
return 0;
Expand Down Expand Up @@ -158,7 +158,7 @@ public override string ToString()
_ => throw new ArgumentOutOfRangeException()
};
_ = sb.Append(lifecycle);
if (Version is not null && Version != AllVersions.Instance)
if (Version is not null && Version != AllVersionsSpec.Instance)
_ = sb.Append(' ').Append(Version);
return sb.ToString();
}
Expand Down Expand Up @@ -224,10 +224,10 @@ public static bool TryParse(string? value, IList<(Severity, string)> diagnostics
? null
: tokens[1] switch
{
null => AllVersions.Instance,
"all" => AllVersions.Instance,
"" => AllVersions.Instance,
var t => SemVersionConverter.TryParse(t, out var v) ? v : null
null => AllVersionsSpec.Instance,
"all" => AllVersionsSpec.Instance,
"" => AllVersionsSpec.Instance,
var t => VersionSpec.TryParse(t, out var v) ? v : null
};
availability = new Applicability { Version = version, Lifecycle = lifecycle };
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,25 @@ public static Applicability GetPrimaryApplicability(IEnumerable<Applicability> a
};

var availableApplicabilities = applicabilityList
.Where(a => a.Version is null || a.Version is AllVersions || a.Version <= currentVersion)
.Where(a => a.Version is null || a.Version is AllVersionsSpec || a.Version.Min <= currentVersion)
.ToList();

if (availableApplicabilities.Count != 0)
{
return availableApplicabilities
.OrderByDescending(a => a.Version ?? new SemVersion(0, 0, 0))
.OrderByDescending(a => a.Version?.Min ?? new SemVersion(0, 0, 0))
.ThenBy(a => lifecycleOrder.GetValueOrDefault(a.Lifecycle, 999))
.First();
}

var futureApplicabilities = applicabilityList
.Where(a => a.Version is not null && a.Version is not AllVersions && a.Version > currentVersion)
.Where(a => a.Version is not null && a.Version is not AllVersionsSpec && a.Version.Min > currentVersion)
.ToList();

if (futureApplicabilities.Count != 0)
{
return futureApplicabilities
.OrderBy(a => a.Version!.CompareTo(currentVersion))
.OrderBy(a => a.Version!.Min.CompareTo(currentVersion))
.ThenBy(a => lifecycleOrder.GetValueOrDefault(a.Lifecycle, 999))
.First();
}
Expand Down
Loading
Loading