Skip to content

[Fleet] Write policy_base_id field at agent enrollment - #7422

Merged
ycombinator merged 12 commits into
elastic:mainfrom
juliaElastic:feat/policy-base-id
Jul 28, 2026
Merged

[Fleet] Write policy_base_id field at agent enrollment#7422
ycombinator merged 12 commits into
elastic:mainfrom
juliaElastic:feat/policy-base-id

Conversation

@juliaElastic

@juliaElastic juliaElastic commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a denormalized policy_base_id field written at agent enrollment to enable cheap term/terms queries in place of expensive prefix/wildcard queries.

Background

Fleet supports version-specific agent policies where policy_id gets a #major.minor suffix (e.g. my-policy#9.2). Querying agents by policy currently requires:

{ "bool": { "should": [{ "term": { "policy_id": "my-policy" } }, { "prefix": { "policy_id": "my-policy#" } }] } }

The policy_base_id field stores the canonical base ID (suffix stripped), enabling a single term: { policy_base_id: "my-policy" } query instead.

Changes

  • internal/pkg/dl/constants.go: Add FieldPolicyBaseID = "policy_base_id" constant
  • internal/pkg/model/schema.go: Add PolicyBaseID string json:"policy_base_id,omitempty" field to the Agent model
  • internal/pkg/api/handleEnroll.go: Set PolicyBaseID: policyBaseID(policyID) in the agent struct at enrollment; add policyBaseID helper that strips the #version suffix

Notes

Part of elastic/ingest-dev#8624 (Workstream B2).

@mergify

mergify Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

This pull request does not have a backport label. Could you fix it @juliaElastic? 🙏
To fixup this pull request, you need to add the backport labels for the needed
branches, such as:

  • backport-./d./d is the label to automatically backport to the 8./d branch. /d is the digit
  • backport-active-all is the label that automatically backports to all active branches.
  • backport-active-8 is the label that automatically backports to all active minor branches for the 8 major.
  • backport-active-9 is the label that automatically backports to all active minor branches for the 9 major.

@github-actions

This comment has been minimized.

@mergify

mergify Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

This pull request is now in conflicts. Could you fix it @juliaElastic? 🙏
To fixup this pull request, you can check out it locally. See documentation: https://help.github.com/articles/checking-out-pull-requests-locally/

git fetch upstream
git checkout -b feat/policy-base-id upstream/feat/policy-base-id
git merge upstream/main
git push upstream feat/policy-base-id

@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

TL;DR

The check-ci Buildkite job fails because go-licenser modifies four generated Go files during the header check, leaving the worktree dirty. This is a generated-file/license-header drift issue; no compile or test failure is shown.

Remediation

  • Regenerate the affected files on the PR branch (or run the repository generation target) so the generated outputs include the required Elastic license headers, and commit the resulting changes. The affected files are internal/pkg/api/openapi.gen.go, internal/pkg/model/schema.go, pkg/api/client.gen.go, and pkg/api/types.gen.go.
  • Re-run .buildkite/scripts/check_ci.sh or mage check:ci and confirm the header check leaves no working-tree changes.
Investigation details

Root Cause

.buildkite/scripts/check_ci.sh runs mage check:ci; the repository header target invokes go-licenser with the Elasticv2 license. On this build, that check added the standard four-line copyright header to the four generated files above. Because the generated files were not already synchronized with the header tool, the subsequent git update-index --refresh pre-exit check failed.

Evidence

  • Build: https://buildkite.com/elastic/fleet-server/builds/15575
  • Job/step: Run check-ci
  • Key log excerpt: internal/pkg/api/openapi.gen.go: needs update, internal/pkg/model/schema.go: needs update, pkg/api/client.gen.go: needs update, pkg/api/client.gen.go: needs update, followed by Error: git update-index failure.

Verification

The supplied Buildkite log was inspected locally. The PR checkout was not available in the workspace, so no PR-specific rerun was performed.


What is this? | From workflow: PR Buildkite Detective

Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@juliaElastic
juliaElastic marked this pull request as ready for review July 22, 2026 10:51
@juliaElastic
juliaElastic requested a review from a team as a code owner July 22, 2026 10:51
juliaElastic and others added 2 commits July 22, 2026 13:00
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@juliaElastic juliaElastic added backport-9.4 Automated backport to 9.4 branch. backport-9.5 Automated backport to the 9.5 branch and removed backport-skip Skip notification from the automated backport with mergify labels Jul 22, 2026
@juliaElastic
juliaElastic requested a review from Copilot July 22, 2026 11:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a denormalized policy_base_id field on agent documents to enable efficient term/terms queries for “base policy ID” lookups when policy_id may include a #major.minor version suffix.

Changes:

  • Adds policy_base_id to the Agent schema/model and corresponding DL constant.
  • Populates PolicyBaseID during agent creation in the enrollment handler via a policyBaseID() helper.
  • Adds a unit test for the policyBaseID() helper and a changelog fragment.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
model/schema.json Adds policy_base_id to the agent schema definition.
internal/pkg/model/schema.go Regenerates model output to include Agent.PolicyBaseID.
internal/pkg/dl/constants.go Adds FieldPolicyBaseID constant for ES field naming.
internal/pkg/api/handleEnroll.go Writes PolicyBaseID on agent creation and adds policyBaseID() helper.
internal/pkg/api/handleEnroll_test.go Adds unit coverage for policyBaseID() helper behavior.
changelog/fragments/1784718180-write-policy-base-id-on-enrollment.yaml Records the feature in the changelog.
Files not reviewed (1)
  • internal/pkg/model/schema.go: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/pkg/api/handleEnroll.go
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cmacknz cmacknz added Team:Elastic-Agent-Data-Plane Label for the Agent Data Plane team Team:Elastic-Agent-Control-Plane Label for the Agent Control Plane team labels Jul 27, 2026

@ycombinator ycombinator left a comment

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.

internal/pkg/api/handleEnroll_test.goTestEnrollWithAgentIDExistingActive (lines 382–410)

TestEnrollWithAgentIDExistingActive exercises the re-enrollment UpdateFields path — the exact path where the original omission was caught by Copilot review. The mock accepts any args on Update, so a missing FieldPolicyBaseID would pass silently. Suggested change: use a versioned policy ID and assert the field is written.

// before (lines 382–410)
	source := fmt.Sprintf(`{"active":true,"agent":{"id":"1234","version":"8.9.0"},"type":"PERMANENT","policy_id":"1234","replace_token":"%s"}`, replaceHash)
	// ... bulker mock setup ...
	resp, _ := et._enroll(ctx, rb, zlog, req, "1234", []string{}, "8.9.0")
	// only resp.Action and resp.Item.Id are asserted
}
// after
	policyID := "my-policy#9.2"
	source := fmt.Sprintf(`{"active":true,"agent":{"id":"1234","version":"8.9.0"},"type":"PERMANENT","policy_id":"%s","replace_token":"%s"}`, policyID, replaceHash)
	// ... bulker mock setup unchanged ...
	resp, _ := et._enroll(ctx, rb, zlog, req, policyID, []string{}, "8.9.0")

	if resp.Action != "created" {
		t.Fatal("enroll failed")
	}
	if resp.Item.Id != agentID {
		t.Fatalf("agent ID should have been %s (not %s)", agentID, resp.Item.Id)
	}

	var updateBody []byte
	for _, c := range bulker.Calls {
		if c.Method == "Update" {
			updateBody = c.Arguments.Get(3).([]byte)
			break
		}
	}
	var updateDoc struct {
		Doc map[string]any `json:"doc"`
	}
	assert.NoError(t, json.Unmarshal(updateBody, &updateDoc))
	assert.Equal(t, "my-policy", updateDoc.Doc[dl.FieldPolicyBaseID])
}

@ycombinator

Copy link
Copy Markdown
Contributor

While this PR handles the enrollment path, my concern was around the policy reassignment path. But it looks like the companion Kibana PR takes care of setting both policy_id and the new base_policy_id fields on the Agent doc during policy reassignment as well. 👍

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@juliaElastic

juliaElastic commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

@ycombinator thanks for the review, updated the test

If you ready to approve, can you help with the merge too? I don't have access.

@juliaElastic
juliaElastic requested a review from ycombinator July 28, 2026 07:16
@ycombinator
ycombinator merged commit 761825e into elastic:main Jul 28, 2026
12 checks passed
juliaElastic added a commit to elastic/kibana that referenced this pull request Jul 28, 2026
…olicy queries (B2) (#279716)

## Summary

Adds a denormalized `policy_base_id` field to `fleet-agents` and
`.fleet-policies` Elasticsearch indices so readers can use cheap
`term`/`terms` queries instead of `bool.should[term, prefix]` for
version-specific policy lookups.

### Background

Fleet supports version-specific agent policies where `policy_id` gets a
`#major.minor` suffix (e.g. `my-policy#9.2`). Every "find agents on this
policy" query currently requires:

```json
{ "bool": { "should": [{ "term": { "policy_id": "my-policy" } }, { "prefix": { "policy_id": "my-policy#" } }] } }
```

`prefix` queries are expensive and incompatible with
`search.allow_expensive_queries: false`. `policy_base_id` stores the
canonical base ID (suffix stripped), enabling `{ "term": {
"policy_base_id": "my-policy" } }` instead.

**Mixed-version rollout fallback**
(`buildPolicyBaseIdWithFallbackEsFilter` /
`buildPolicyBaseIdsWithFallbackEsFilter`):

Agents enrolled via an older fleet-server during a rollout will lack
`policy_base_id`. The four ES DSL query sites use a transitional
fallback:

```
policy_base_id:<id>
OR (NOT _exists_:policy_base_id AND policy_id:<id>)
```

The fallback uses exact `term`/`terms` on `policy_id` only — no `prefix`
— to remain compatible with `search.allow_expensive_queries: false`.
Versioned `policy_id` values (e.g. `policy-id#9.3`) without
`policy_base_id` can only arise in the narrow overlap of a mixed-version
upgrade; the startup backfill covers all pre-existing documents. Both
helpers carry a TODO to remove once all fleet-server versions in use
populate the field.

### Deployment order

This PR must be deployed together with (or after):
1. **elastic/elasticsearch#154521** — mapping for `policy_base_id` field
2. **elastic/fleet-server#7422** — writes `policy_base_id` at agent
enrollment

Closes
[elastic/ingest-dev#8624](elastic/ingest-dev#8624)
(Workstream B2).

### Screenshots

<img width="1586" height="539" alt="image"
src="https://github.com/user-attachments/assets/a4e8e305-c918-43c8-9702-da20abc9897e"
/>
<img width="655" height="991" alt="image"
src="https://github.com/user-attachments/assets/e3d899fc-b084-4b14-973f-785d1350243c"
/>
<img width="619" height="611" alt="image"
src="https://github.com/user-attachments/assets/e58c0e70-1c55-4d9d-a9b1-e6d72fe645b7"
/>

Example output from Kibana logs during Fleet setup when there are docs
missing `policy_base_id` e.g. enrolled previously or with old
fleet-server version.
```
# first run
[2026-07-22T11:26:38.363+02:00][DEBUG][plugins.fleet] Backfilling policy_base_id on fleet-agents and fleet-policies
[2026-07-22T11:26:38.402+02:00][DEBUG][plugins.fleet] Backfilled policy_base_id on 2 fleet-agents documents (0 noops)
[2026-07-22T11:26:38.456+02:00][DEBUG][plugins.fleet] Backfilled policy_base_id on 12 fleet-policies documents (0 noops)

# second run
[2026-07-22T12:38:45.848+02:00][DEBUG][plugins.fleet] Backfilling policy_base_id on fleet-agents and fleet-policies
[2026-07-22T12:38:45.850+02:00][DEBUG][plugins.fleet] Backfilled policy_base_id on 0 fleet-agents documents (0 noops)
[2026-07-22T12:38:45.850+02:00][DEBUG][plugins.fleet] Backfilled policy_base_id on 0 fleet-policies documents (0 noops)
```

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
kibanamachine added a commit to elastic/kibana that referenced this pull request Jul 29, 2026
…gent policy queries (B2) (#279716) (#281330)

# Backport

This will backport the following commits from `main` to `9.5`:
- [[Fleet] Add policy_base_id field to eliminate prefix/wildcard agent
policy queries (B2)
(#279716)](#279716)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Julia
Bardi","email":"90178898+juliaElastic@users.noreply.github.com"},"sourceCommit":{"committedDate":"2026-07-28T16:25:20Z","message":"[Fleet]
Add policy_base_id field to eliminate prefix/wildcard agent policy
queries (B2) (#279716)\n\n## Summary\n\nAdds a denormalized
`policy_base_id` field to `fleet-agents` and\n`.fleet-policies`
Elasticsearch indices so readers can use cheap\n`term`/`terms` queries
instead of `bool.should[term, prefix]` for\nversion-specific policy
lookups.\n\n### Background\n\nFleet supports version-specific agent
policies where `policy_id` gets a\n`#major.minor` suffix (e.g.
`my-policy#9.2`). Every \"find agents on this\npolicy\" query currently
requires:\n\n```json\n{ \"bool\": { \"should\": [{ \"term\": {
\"policy_id\": \"my-policy\" } }, { \"prefix\": { \"policy_id\":
\"my-policy#\" } }] } }\n```\n\n`prefix` queries are expensive and
incompatible with\n`search.allow_expensive_queries: false`.
`policy_base_id` stores the\ncanonical base ID (suffix stripped),
enabling `{ \"term\": {\n\"policy_base_id\": \"my-policy\" } }`
instead.\n\n**Mixed-version rollout
fallback**\n(`buildPolicyBaseIdWithFallbackEsFilter`
/\n`buildPolicyBaseIdsWithFallbackEsFilter`):\n\nAgents enrolled via an
older fleet-server during a rollout will lack\n`policy_base_id`. The
four ES DSL query sites use a
transitional\nfallback:\n\n```\npolicy_base_id:<id>\nOR (NOT
_exists_:policy_base_id AND policy_id:<id>)\n```\n\nThe fallback uses
exact `term`/`terms` on `policy_id` only — no `prefix`\n— to remain
compatible with `search.allow_expensive_queries: false`.\nVersioned
`policy_id` values (e.g. `policy-id#9.3`) without\n`policy_base_id` can
only arise in the narrow overlap of a mixed-version\nupgrade; the
startup backfill covers all pre-existing documents. Both\nhelpers carry
a TODO to remove once all fleet-server versions in use\npopulate the
field.\n\n### Deployment order\n\nThis PR must be deployed together with
(or after):\n1. **elastic/elasticsearch#154521** — mapping for
`policy_base_id` field\n2. **elastic/fleet-server#7422** — writes
`policy_base_id` at
agent\nenrollment\n\nCloses\n[elastic/ingest-dev#8624](https://github.com/elastic/ingest-dev/issues/8624)\n(Workstream
B2).\n\n### Screenshots\n\n<img width=\"1586\" height=\"539\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/a4e8e305-c918-43c8-9702-da20abc9897e\"\n/>\n<img
width=\"655\" height=\"991\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/e3d899fc-b084-4b14-973f-785d1350243c\"\n/>\n<img
width=\"619\" height=\"611\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/e58c0e70-1c55-4d9d-a9b1-e6d72fe645b7\"\n/>\n\nExample
output from Kibana logs during Fleet setup when there are docs\nmissing
`policy_base_id` e.g. enrolled previously or with old\nfleet-server
version.\n```\n# first
run\n[2026-07-22T11:26:38.363+02:00][DEBUG][plugins.fleet] Backfilling
policy_base_id on fleet-agents and
fleet-policies\n[2026-07-22T11:26:38.402+02:00][DEBUG][plugins.fleet]
Backfilled policy_base_id on 2 fleet-agents documents (0
noops)\n[2026-07-22T11:26:38.456+02:00][DEBUG][plugins.fleet] Backfilled
policy_base_id on 12 fleet-policies documents (0 noops)\n\n# second
run\n[2026-07-22T12:38:45.848+02:00][DEBUG][plugins.fleet] Backfilling
policy_base_id on fleet-agents and
fleet-policies\n[2026-07-22T12:38:45.850+02:00][DEBUG][plugins.fleet]
Backfilled policy_base_id on 0 fleet-agents documents (0
noops)\n[2026-07-22T12:38:45.850+02:00][DEBUG][plugins.fleet] Backfilled
policy_base_id on 0 fleet-policies documents (0
noops)\n```\n\n---------\n\nCo-authored-by: Claude Sonnet 4.6
<noreply@anthropic.com>\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by:
github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>","sha":"83bf19763c00e563edd02e6ffcb9282e8be5d522","branchLabelMapping":{"^v9.6.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:Fleet","backport:version","v9.6.0","v9.4.5","v9.5.1"],"title":"[Fleet]
Add policy_base_id field to eliminate prefix/wildcard agent policy
queries
(B2)","number":279716,"url":"https://github.com/elastic/kibana/pull/279716","mergeCommit":{"message":"[Fleet]
Add policy_base_id field to eliminate prefix/wildcard agent policy
queries (B2) (#279716)\n\n## Summary\n\nAdds a denormalized
`policy_base_id` field to `fleet-agents` and\n`.fleet-policies`
Elasticsearch indices so readers can use cheap\n`term`/`terms` queries
instead of `bool.should[term, prefix]` for\nversion-specific policy
lookups.\n\n### Background\n\nFleet supports version-specific agent
policies where `policy_id` gets a\n`#major.minor` suffix (e.g.
`my-policy#9.2`). Every \"find agents on this\npolicy\" query currently
requires:\n\n```json\n{ \"bool\": { \"should\": [{ \"term\": {
\"policy_id\": \"my-policy\" } }, { \"prefix\": { \"policy_id\":
\"my-policy#\" } }] } }\n```\n\n`prefix` queries are expensive and
incompatible with\n`search.allow_expensive_queries: false`.
`policy_base_id` stores the\ncanonical base ID (suffix stripped),
enabling `{ \"term\": {\n\"policy_base_id\": \"my-policy\" } }`
instead.\n\n**Mixed-version rollout
fallback**\n(`buildPolicyBaseIdWithFallbackEsFilter`
/\n`buildPolicyBaseIdsWithFallbackEsFilter`):\n\nAgents enrolled via an
older fleet-server during a rollout will lack\n`policy_base_id`. The
four ES DSL query sites use a
transitional\nfallback:\n\n```\npolicy_base_id:<id>\nOR (NOT
_exists_:policy_base_id AND policy_id:<id>)\n```\n\nThe fallback uses
exact `term`/`terms` on `policy_id` only — no `prefix`\n— to remain
compatible with `search.allow_expensive_queries: false`.\nVersioned
`policy_id` values (e.g. `policy-id#9.3`) without\n`policy_base_id` can
only arise in the narrow overlap of a mixed-version\nupgrade; the
startup backfill covers all pre-existing documents. Both\nhelpers carry
a TODO to remove once all fleet-server versions in use\npopulate the
field.\n\n### Deployment order\n\nThis PR must be deployed together with
(or after):\n1. **elastic/elasticsearch#154521** — mapping for
`policy_base_id` field\n2. **elastic/fleet-server#7422** — writes
`policy_base_id` at
agent\nenrollment\n\nCloses\n[elastic/ingest-dev#8624](https://github.com/elastic/ingest-dev/issues/8624)\n(Workstream
B2).\n\n### Screenshots\n\n<img width=\"1586\" height=\"539\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/a4e8e305-c918-43c8-9702-da20abc9897e\"\n/>\n<img
width=\"655\" height=\"991\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/e3d899fc-b084-4b14-973f-785d1350243c\"\n/>\n<img
width=\"619\" height=\"611\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/e58c0e70-1c55-4d9d-a9b1-e6d72fe645b7\"\n/>\n\nExample
output from Kibana logs during Fleet setup when there are docs\nmissing
`policy_base_id` e.g. enrolled previously or with old\nfleet-server
version.\n```\n# first
run\n[2026-07-22T11:26:38.363+02:00][DEBUG][plugins.fleet] Backfilling
policy_base_id on fleet-agents and
fleet-policies\n[2026-07-22T11:26:38.402+02:00][DEBUG][plugins.fleet]
Backfilled policy_base_id on 2 fleet-agents documents (0
noops)\n[2026-07-22T11:26:38.456+02:00][DEBUG][plugins.fleet] Backfilled
policy_base_id on 12 fleet-policies documents (0 noops)\n\n# second
run\n[2026-07-22T12:38:45.848+02:00][DEBUG][plugins.fleet] Backfilling
policy_base_id on fleet-agents and
fleet-policies\n[2026-07-22T12:38:45.850+02:00][DEBUG][plugins.fleet]
Backfilled policy_base_id on 0 fleet-agents documents (0
noops)\n[2026-07-22T12:38:45.850+02:00][DEBUG][plugins.fleet] Backfilled
policy_base_id on 0 fleet-policies documents (0
noops)\n```\n\n---------\n\nCo-authored-by: Claude Sonnet 4.6
<noreply@anthropic.com>\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by:
github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>","sha":"83bf19763c00e563edd02e6ffcb9282e8be5d522"}},"sourceBranch":"main","suggestedTargetBranches":["9.4","9.5"],"targetPullRequestStates":[{"branch":"main","label":"v9.6.0","branchLabelMappingKey":"^v9.6.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/279716","number":279716,"mergeCommit":{"message":"[Fleet]
Add policy_base_id field to eliminate prefix/wildcard agent policy
queries (B2) (#279716)\n\n## Summary\n\nAdds a denormalized
`policy_base_id` field to `fleet-agents` and\n`.fleet-policies`
Elasticsearch indices so readers can use cheap\n`term`/`terms` queries
instead of `bool.should[term, prefix]` for\nversion-specific policy
lookups.\n\n### Background\n\nFleet supports version-specific agent
policies where `policy_id` gets a\n`#major.minor` suffix (e.g.
`my-policy#9.2`). Every \"find agents on this\npolicy\" query currently
requires:\n\n```json\n{ \"bool\": { \"should\": [{ \"term\": {
\"policy_id\": \"my-policy\" } }, { \"prefix\": { \"policy_id\":
\"my-policy#\" } }] } }\n```\n\n`prefix` queries are expensive and
incompatible with\n`search.allow_expensive_queries: false`.
`policy_base_id` stores the\ncanonical base ID (suffix stripped),
enabling `{ \"term\": {\n\"policy_base_id\": \"my-policy\" } }`
instead.\n\n**Mixed-version rollout
fallback**\n(`buildPolicyBaseIdWithFallbackEsFilter`
/\n`buildPolicyBaseIdsWithFallbackEsFilter`):\n\nAgents enrolled via an
older fleet-server during a rollout will lack\n`policy_base_id`. The
four ES DSL query sites use a
transitional\nfallback:\n\n```\npolicy_base_id:<id>\nOR (NOT
_exists_:policy_base_id AND policy_id:<id>)\n```\n\nThe fallback uses
exact `term`/`terms` on `policy_id` only — no `prefix`\n— to remain
compatible with `search.allow_expensive_queries: false`.\nVersioned
`policy_id` values (e.g. `policy-id#9.3`) without\n`policy_base_id` can
only arise in the narrow overlap of a mixed-version\nupgrade; the
startup backfill covers all pre-existing documents. Both\nhelpers carry
a TODO to remove once all fleet-server versions in use\npopulate the
field.\n\n### Deployment order\n\nThis PR must be deployed together with
(or after):\n1. **elastic/elasticsearch#154521** — mapping for
`policy_base_id` field\n2. **elastic/fleet-server#7422** — writes
`policy_base_id` at
agent\nenrollment\n\nCloses\n[elastic/ingest-dev#8624](https://github.com/elastic/ingest-dev/issues/8624)\n(Workstream
B2).\n\n### Screenshots\n\n<img width=\"1586\" height=\"539\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/a4e8e305-c918-43c8-9702-da20abc9897e\"\n/>\n<img
width=\"655\" height=\"991\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/e3d899fc-b084-4b14-973f-785d1350243c\"\n/>\n<img
width=\"619\" height=\"611\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/e58c0e70-1c55-4d9d-a9b1-e6d72fe645b7\"\n/>\n\nExample
output from Kibana logs during Fleet setup when there are docs\nmissing
`policy_base_id` e.g. enrolled previously or with old\nfleet-server
version.\n```\n# first
run\n[2026-07-22T11:26:38.363+02:00][DEBUG][plugins.fleet] Backfilling
policy_base_id on fleet-agents and
fleet-policies\n[2026-07-22T11:26:38.402+02:00][DEBUG][plugins.fleet]
Backfilled policy_base_id on 2 fleet-agents documents (0
noops)\n[2026-07-22T11:26:38.456+02:00][DEBUG][plugins.fleet] Backfilled
policy_base_id on 12 fleet-policies documents (0 noops)\n\n# second
run\n[2026-07-22T12:38:45.848+02:00][DEBUG][plugins.fleet] Backfilling
policy_base_id on fleet-agents and
fleet-policies\n[2026-07-22T12:38:45.850+02:00][DEBUG][plugins.fleet]
Backfilled policy_base_id on 0 fleet-agents documents (0
noops)\n[2026-07-22T12:38:45.850+02:00][DEBUG][plugins.fleet] Backfilled
policy_base_id on 0 fleet-policies documents (0
noops)\n```\n\n---------\n\nCo-authored-by: Claude Sonnet 4.6
<noreply@anthropic.com>\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by:
github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>","sha":"83bf19763c00e563edd02e6ffcb9282e8be5d522"}},{"branch":"9.4","label":"v9.4.5","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"9.5","label":"v9.5.1","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Julia Bardi <90178898+juliaElastic@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
ycombinator pushed a commit that referenced this pull request Jul 29, 2026
…ollment (#7498)

* [Fleet] Write policy_base_id field at agent enrollment (#7422)

* Add FieldPolicyBaseID constant

* Add PolicyBaseID field to Agent model

* Write policy_base_id at agent enrollment

* Add policy_base_id to agent schema (source for go generate)

* Regenerate schema.go (go generate ./...)

* chore: add Elastic license headers to generated Go files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add unit tests for policyBaseID

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: add changelog fragment for policy_base_id enrollment feature

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: set policy_base_id in re-enrollment UpdateFields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: assert policy_base_id written in re-enrollment UpdateFields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
(cherry picked from commit 761825e)

# Conflicts:
#	model/schema.json

* fix: resolve merge conflict in model/schema.json

Keep expanded enum with "reenrolled" value from backport.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
ycombinator pushed a commit that referenced this pull request Jul 29, 2026
* Add FieldPolicyBaseID constant

* Add PolicyBaseID field to Agent model

* Write policy_base_id at agent enrollment

* Add policy_base_id to agent schema (source for go generate)

* Regenerate schema.go (go generate ./...)

* chore: add Elastic license headers to generated Go files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add unit tests for policyBaseID

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: add changelog fragment for policy_base_id enrollment feature

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: set policy_base_id in re-enrollment UpdateFields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: assert policy_base_id written in re-enrollment UpdateFields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
(cherry picked from commit 761825e)
ycombinator pushed a commit that referenced this pull request Jul 29, 2026
* Add FieldPolicyBaseID constant

* Add PolicyBaseID field to Agent model

* Write policy_base_id at agent enrollment

* Add policy_base_id to agent schema (source for go generate)

* Regenerate schema.go (go generate ./...)

* chore: add Elastic license headers to generated Go files



* test: add unit tests for policyBaseID



* chore: add changelog fragment for policy_base_id enrollment feature



* fix: set policy_base_id in re-enrollment UpdateFields



* test: assert policy_base_id written in re-enrollment UpdateFields



---------


(cherry picked from commit 761825e)

Co-authored-by: Julia Bardi <90178898+juliaElastic@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-9.4 Automated backport to 9.4 branch. backport-9.5 Automated backport to the 9.5 branch Team:Elastic-Agent-Control-Plane Label for the Agent Control Plane team Team:Elastic-Agent-Data-Plane Label for the Agent Data Plane team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants