Skip to content

[Cases] Migrate attachment client/service layer to unified-only requests - #290961

Merged
christineweng merged 9 commits into
elastic:mainfrom
christineweng:cases-client-service-cleanup
Sep 15, 2026
Merged

christineweng merged 9 commits into
elastic:mainfrom
christineweng:cases-client-service-cleanup

Conversation

@christineweng

@christineweng christineweng commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Context:
During cases attachment v2 work, all the v2 types (such as AttachmentRequestV2) are unions of v1 and v2 for backward compatibility. After #287729, all public routes perform v1 -> v2 conversion before sending to cases client, hence union shape is no longer necessary.

This PR migrates the client/service layer to accept unified-only attachment payloads internally.

Major changes

  • Replaced ....V2 (v1+v2 union) type with ...Unified (v2 only)
  • To prevent naming confusion, split common/types/api/attachment/v2.ts into:v2.ts and v2_union.ts
  • Removed redundant decode calls
  • Renamed utils like validateRegisteredAttachments to validateUnifiedAttachments

No wire-shape changes in this PR. Follow-up PR #290934 removes the legacy wire shape from the bulk-create route.

Made with Cursor

Checklist

  • Any text added follows EUI's writing guidelines, uses sentence case text and includes i18n support
  • Documentation was added for features that require explanation or tutorials
  • Unit or functional tests were updated or added to match the most common scenarios
  • If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the docker list
  • This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The release_note:breaking label should be applied in these situations.
  • Flaky Test Runner was used on any tests changed
  • The PR description includes the appropriate Release Notes section, and the correct release_note:* label is applied per the guidelines
  • Review the backport guidelines and apply applicable backport:* labels.

christineweng and others added 2 commits September 14, 2026 12:34
Collapses the client/service/route/workflow layer onto unified-only
attachment requests (AttachmentRequestV2 / BulkCreateAttachmentsRequestV2),
adds BulkCreateUnifiedAttachmentsRequestRt / BulkGetUnifiedAttachmentsResponseRt,
and removes the now-unused BulkGetAttachmentsResponseRtV2 and legacy decode path.

Co-authored-by: Cursor <cursoragent@cursor.com>
Split attachment/v2.ts into:
- v2.ts: unified-only types (BulkCreateUnifiedAttachmentsRequest, etc.)
- v2_union.ts: v1-legacy union types (AttachmentRequestV2, etc.),
  exposed as the attachmentApiV2Union namespace.

Also migrate base_response_actions_client.ts to the unified-only
client type, matching cases_connector_executor.ts/add_alerts.ts.

@szwarckonrad szwarckonrad 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.

Defend Workflows single type name change LGTM

@christineweng christineweng self-assigned this Sep 14, 2026
@christineweng christineweng added release_note:skip Skip the PR/issue when compiling release notes backport:skip This PR does not require backporting v9.6.0 Team:Lantern labels Sep 14, 2026
// validation re-runs through the per-type Zod schemas.
return createAttachments({
attachments: attachments as unknown as attachmentApiV2.BulkCreateAttachmentsRequestV2,
attachments: attachments as unknown as attachmentApiV2Union.BulkCreateAttachmentsRequestV2,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

still using union because actual routes accept both right now, #290934 updates the route to be unified only

@radtke-vale radtke-vale 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.

LGTM 🚀 Very nice refactoring! I tried REST API locally + attaching events/files. All looks good.

isUnifiedOnlyAttachment(attachment)
);
// The client accepts unified payloads only; convert the mixed wire body here.
const attachments = rawAttachments.map((attachment) =>

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.

My understanding is that it's one of the main changes - shifting the v1 conversion one level up to the route level 👍 So all internal requests expect unified, but for the end users we'll make conversion on the route level

Comment thread x-pack/platform/plugins/shared/cases/server/common/models/case_with_comments.ts Outdated
christineweng added a commit that referenced this pull request Sep 15, 2026
…91173)

## Summary

`validateCreateCommentRequest` blocks alerts and events from being
attached to a closed case. The event check matched by legacy type only
(`AttachmentType.event`, i.e. `'event'`), so unified events
(`security.event`) never triggered the guard and were silently accepted
on closed cases.

Alerts already used the unified-aware `isAlertAttachmentType` predicate.
This aligns events to the same pattern via `isEventAttachmentType`,
which matches both `'event'` (legacy) and `'security.event'` (unified).

Also removes the now-dead `getAttachmentsByType` helper (its only call
site was the buggy event check).

**Found by** @radtke-vale, validated on staging in [this review
comment](#290961 (comment))
on #290961.

## Before / after (validated on staging)

Before:
```
POST kbn:/api/cases/{caseId}/comments
{
  "type": "security.event",
  "owner": "securitySolution",
  "attachmentId": "...",
  "metadata": { "index": "..." }
}
```
→ `200`, attachment created on a closed case.

After:
```json
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Event cannot be attached to a closed case"
}
```

### Checklist

- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This was checked for breaking HTTP API changes, and any breaking
changes have been approved by the breaking-change committee. The
`release_note:breaking` label should be applied in these situations.
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/docs/extend/kibana/contributing/workflow/how-we-use-github#release-notes)
- [x] Review the [backport
guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)
and apply applicable `backport:*` labels.

Made with Cursor
- extract_observables.test.ts: fix stale legacyAlertAttachment reference
  left over from merge conflict resolution (branch only defines
  alertAttachment, a unified-only fixture)
- case_with_comments.test.ts: remove duplicate closed-case-unified-event
  test; the merged-in top-level unifiedEventComment fixture already
  covers this case in the pre-existing 'throws if trying to add an
  event or alert to a closed case' test
@christineweng
christineweng enabled auto-merge (squash) September 15, 2026 16:23
@kibanamachine

Copy link
Copy Markdown
Contributor

💛 Build succeeded, but was flaky

Failed CI Steps

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
shared-plugins 3901 3902 +1

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
shared-plugins 12.2MB 12.2MB +174.0B
Unknown metric groups

total optimizer output size

id before after diff
all 63.9MB 63.9MB +174.0B

Test Failures

  • [job] [logs] Scout Lane #114 - serverless-observability_complete / default / local-serverless-observability_complete - Synthetics manage rules privilege - lets a read user with can_manage_rules enable default alerting
  • [job] [logs] Scout Lane #24 - stateful-classic / default / local-stateful-classic - Synthetics manage rules privilege - lets a read user with can_manage_rules enable default alerting
  • [job] [logs] Defend Workflows Cypress Tests #4 / Response console Host Isolation: "before all" hook for "should isolate a host from response console" "before all" hook for "should isolate a host from response console"

History

cc @christineweng

@christineweng
christineweng merged commit b1320ee into elastic:main Sep 15, 2026
43 checks passed
christineweng added a commit that referenced this pull request Sep 15, 2026
…ck (#291173) (#291204)

# Backport

This will backport the following commits from `main` to `9.5`:
- [[Cases] Fix unified event attachments bypassing closed-case check
(#291173)](#291173)

<!--- Backport version: 12.0.4 -->

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

<!--BACKPORT
[{"author":{"name":"christineweng","email":"18648970+christineweng@users.noreply.github.com"},"sourceCommit":{"committedDate":"2026-09-15T15:55:18Z","message":"[Cases]
Fix unified event attachments bypassing closed-case check
(#291173)\n\n## Summary\n\n`validateCreateCommentRequest` blocks alerts
and events from being\nattached to a closed case. The event check
matched by legacy type only\n(`AttachmentType.event`, i.e. `'event'`),
so unified events\n(`security.event`) never triggered the guard and were
silently accepted\non closed cases.\n\nAlerts already used the
unified-aware `isAlertAttachmentType` predicate.\nThis aligns events to
the same pattern via `isEventAttachmentType`,\nwhich matches both
`'event'` (legacy) and `'security.event'` (unified).\n\nAlso removes the
now-dead `getAttachmentsByType` helper (its only call\nsite was the
buggy event check).\n\n**Found by** @radtke-vale, validated on staging
in [this
review\ncomment](https://github.com/elastic/kibana/pull/290961#discussion_r4014165733)\non
#290961.\n\n## Before / after (validated on
staging)\n\nBefore:\n```\nPOST kbn:/api/cases/{caseId}/comments\n{\n
\"type\": \"security.event\",\n \"owner\": \"securitySolution\",\n
\"attachmentId\": \"...\",\n \"metadata\": { \"index\": \"...\"
}\n}\n```\n→ `200`, attachment created on a closed
case.\n\nAfter:\n```json\n{\n \"statusCode\": 400,\n \"error\": \"Bad
Request\",\n \"message\": \"Event cannot be attached to a closed
case\"\n}\n```\n\n### Checklist\n\n- [ ] Any text added follows [EUI's
writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\nsentence case text and includes
[i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n-
[
]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas
added for features that require explanation or tutorials\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [ ] If a plugin
configuration key changed, check if it needs to be\nallowlisted in the
cloud and added to the
[docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n-
[ ] This was checked for breaking HTTP API changes, and any
breaking\nchanges have been approved by the breaking-change committee.
The\n`release_note:breaking` label should be applied in these
situations.\n- [ ] [Flaky
Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\nused on any tests changed\n- [x] The PR description includes the
appropriate Release Notes section,\nand the correct `release_note:*`
label is applied per
the\n[guidelines](https://www.elastic.co/docs/extend/kibana/contributing/workflow/how-we-use-github#release-notes)\n-
[x] Review the
[backport\nguidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)\nand
apply applicable `backport:*` labels.\n\nMade with
Cursor","sha":"0b5aba9fe5761d0fd2fc2ce576e6d36146231465","branchLabelMapping":{"^v9.6.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","backport:version","v9.6.0","v9.5.5","Team:Lantern"],"title":"[Cases]
Fix unified event attachments bypassing closed-case
check","number":291173,"url":"https://github.com/elastic/kibana/pull/291173","mergeCommit":{"message":"[Cases]
Fix unified event attachments bypassing closed-case check
(#291173)\n\n## Summary\n\n`validateCreateCommentRequest` blocks alerts
and events from being\nattached to a closed case. The event check
matched by legacy type only\n(`AttachmentType.event`, i.e. `'event'`),
so unified events\n(`security.event`) never triggered the guard and were
silently accepted\non closed cases.\n\nAlerts already used the
unified-aware `isAlertAttachmentType` predicate.\nThis aligns events to
the same pattern via `isEventAttachmentType`,\nwhich matches both
`'event'` (legacy) and `'security.event'` (unified).\n\nAlso removes the
now-dead `getAttachmentsByType` helper (its only call\nsite was the
buggy event check).\n\n**Found by** @radtke-vale, validated on staging
in [this
review\ncomment](https://github.com/elastic/kibana/pull/290961#discussion_r4014165733)\non
#290961.\n\n## Before / after (validated on
staging)\n\nBefore:\n```\nPOST kbn:/api/cases/{caseId}/comments\n{\n
\"type\": \"security.event\",\n \"owner\": \"securitySolution\",\n
\"attachmentId\": \"...\",\n \"metadata\": { \"index\": \"...\"
}\n}\n```\n→ `200`, attachment created on a closed
case.\n\nAfter:\n```json\n{\n \"statusCode\": 400,\n \"error\": \"Bad
Request\",\n \"message\": \"Event cannot be attached to a closed
case\"\n}\n```\n\n### Checklist\n\n- [ ] Any text added follows [EUI's
writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\nsentence case text and includes
[i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n-
[
]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas
added for features that require explanation or tutorials\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [ ] If a plugin
configuration key changed, check if it needs to be\nallowlisted in the
cloud and added to the
[docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n-
[ ] This was checked for breaking HTTP API changes, and any
breaking\nchanges have been approved by the breaking-change committee.
The\n`release_note:breaking` label should be applied in these
situations.\n- [ ] [Flaky
Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\nused on any tests changed\n- [x] The PR description includes the
appropriate Release Notes section,\nand the correct `release_note:*`
label is applied per
the\n[guidelines](https://www.elastic.co/docs/extend/kibana/contributing/workflow/how-we-use-github#release-notes)\n-
[x] Review the
[backport\nguidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)\nand
apply applicable `backport:*` labels.\n\nMade with
Cursor","sha":"0b5aba9fe5761d0fd2fc2ce576e6d36146231465"}},"sourceBranch":"main","suggestedTargetBranches":["9.5"],"targetPullRequestStates":[{"branch":"main","label":"v9.6.0","branchLabelMappingKey":"^v9.6.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/291173","number":291173,"mergeCommit":{"message":"[Cases]
Fix unified event attachments bypassing closed-case check
(#291173)\n\n## Summary\n\n`validateCreateCommentRequest` blocks alerts
and events from being\nattached to a closed case. The event check
matched by legacy type only\n(`AttachmentType.event`, i.e. `'event'`),
so unified events\n(`security.event`) never triggered the guard and were
silently accepted\non closed cases.\n\nAlerts already used the
unified-aware `isAlertAttachmentType` predicate.\nThis aligns events to
the same pattern via `isEventAttachmentType`,\nwhich matches both
`'event'` (legacy) and `'security.event'` (unified).\n\nAlso removes the
now-dead `getAttachmentsByType` helper (its only call\nsite was the
buggy event check).\n\n**Found by** @radtke-vale, validated on staging
in [this
review\ncomment](https://github.com/elastic/kibana/pull/290961#discussion_r4014165733)\non
#290961.\n\n## Before / after (validated on
staging)\n\nBefore:\n```\nPOST kbn:/api/cases/{caseId}/comments\n{\n
\"type\": \"security.event\",\n \"owner\": \"securitySolution\",\n
\"attachmentId\": \"...\",\n \"metadata\": { \"index\": \"...\"
}\n}\n```\n→ `200`, attachment created on a closed
case.\n\nAfter:\n```json\n{\n \"statusCode\": 400,\n \"error\": \"Bad
Request\",\n \"message\": \"Event cannot be attached to a closed
case\"\n}\n```\n\n### Checklist\n\n- [ ] Any text added follows [EUI's
writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\nsentence case text and includes
[i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n-
[
]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas
added for features that require explanation or tutorials\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [ ] If a plugin
configuration key changed, check if it needs to be\nallowlisted in the
cloud and added to the
[docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n-
[ ] This was checked for breaking HTTP API changes, and any
breaking\nchanges have been approved by the breaking-change committee.
The\n`release_note:breaking` label should be applied in these
situations.\n- [ ] [Flaky
Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\nused on any tests changed\n- [x] The PR description includes the
appropriate Release Notes section,\nand the correct `release_note:*`
label is applied per
the\n[guidelines](https://www.elastic.co/docs/extend/kibana/contributing/workflow/how-we-use-github#release-notes)\n-
[x] Review the
[backport\nguidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)\nand
apply applicable `backport:*` labels.\n\nMade with
Cursor","sha":"0b5aba9fe5761d0fd2fc2ce576e6d36146231465"}},{"branch":"9.5","label":"v9.5.5","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip This PR does not require backporting release_note:skip Skip the PR/issue when compiling release notes Team:Lantern v9.6.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants