operations skill
RESTful 엔드포인트의 API 작업 수명 주기, 보안 표현식, 검증 그룹 및 파라미터 수준의 제약 조건을 설정합니다.
- GitHub 스타
- 29
- 파일
- 1
- 마지막 커밋
- 2026년 10월 22일
- 라이선스
- —
- 허용된 도구
- —
operations skill은 무엇을 하나요?
operations은(는) agent skill입니다. RESTful 엔드포인트의 API 작업 수명 주기, 보안 표현식, 검증 그룹 및 파라미터 수준의 제약 조건을 설정합니다.
skill의 SKILL.md에서 생성된 요약입니다.
operations skill 정보
이 SKILL은 API Platform을 사용하는 개발자가 RESTful 엔드포인트의 API 작업 수명 주기, 보안 및 검증을 구성할 수 있도록 지원합니다. 요청의 읽기 및 쓰기 단계에 대해 세밀한 제어가 가능하며, 파일 다운로드나 커스텀 upsert 로직과 같은 작업을 처리하기 위해 provider와 processor를 전환할 수 있습니다. 또한 다양한 HTTP 메서드에 대해 읽기 및 쓰기 플래그를 명시적으로 설정하여 CQRS 분리를 관리할 수 있습니다.
수명 주기 관리 외에도, Symfony ExpressionLanguage를 사용하여 역할 기반 또는 객체 기반 액세스 제어를 통해 엔드포인트를 보호하는 도구를 제공합니다. 쿼리 및 헤더 파라미터에 대한 파라미터 수준의 보안도 처리합니다. 데이터 무결성을 위해 생성과 수정 제약 조건을 구분하는 validation groups와 역직렬화(denormalization) 실패 시 오류 수집 기능을 지원합니다. 또한 특정 헤더를 통해 엔드포인트의 deprecation을 관리하고, JSON merge-patch 시나리오에서 중첩된 PATCH 요청을 처리하는 가이드를 제공합니다.
agent가 수행할 수 있는 작업
- 1역할별 엔드포인트 액세스 제한AI 에이전트가 POST 작업을 구성하여 관리자 권한이 있는 사용자만 실행할 수 있도록 설정할 수 있습니다.
- 2서로 다른 검증 규칙 적용개발자가 에이전트에게 사용자 리소스 생성과 수정 시 서로 다른 validation groups를 사용하도록 지시할 수 있습니다.
- 3특정 쿼리 파라미터 보안 적용provider가 실행되기 전에 특정 쿼리 파라미터가 보안 표현식에 따라 검증되도록 요청할 수 있습니다.
- 4API deprecation 알림에이전트가 기존 리소스에 deprecation 사유와 sunset 날짜를 추가하여 클라이언트에게 향후 변경 사항을 알릴 수 있습니다.
- 5상세한 오류 보고 활성화개발자가 API를 구성하여 첫 번째 오류에서 멈추지 않고 잘못된 모든 필드 오류를 한 번에 반환하도록 설정할 수 있습니다.
skill의 SKILL.md를 기반으로 작성되었습니다. 모든 사용 사례는 해당 문서의 인용구와 대조 확인되었습니다.
operations skill 설치 방법
각 명령은 skill 폴더를 클라이언트의 skills 디렉터리에 복사합니다. 클라이언트를 재시작하거나 새 세션을 시작하여 로드하세요.플러그인에 operations skill 설치
/plugin marketplace add api-platform/skillset /plugin install api-platform@api-platform-skillset
Claude Code에 operations skill 설치
~/.claude/skills/operations/SKILL.md
$ npx degit api-platform/skillset/skills/operations ~/.claude/skills/operationsClaude Code (project)에 operations skill 설치
.claude/skills/operations/SKILL.md
$ npx degit api-platform/skillset/skills/operations .claude/skills/operationsCodex에 operations skill 설치
~/.agents/skills/operations/SKILL.md
$ npx degit api-platform/skillset/skills/operations ~/.agents/skills/operationsGemini CLI에 operations skill 설치
~/.gemini/skills/operations/SKILL.md
$ npx degit api-platform/skillset/skills/operations ~/.gemini/skills/operationsClaude.ai에 operations skill 설치
npx degit api-platform/skillset/skills/operations operations && zip -r operations.zip operations # claude.ai → Customize → Skills → + → Create skill → Upload a skill → operations.zip
SKILL.md
GitHub에서 보기Operations: Security, Validation & Lifecycle
Operation-level configuration that sits between the resource shape (see api-resource) and the read/write logic (see state-provider / state-processor).
Read & write phases (the provider/processor lifecycle)
Every operation runs two phases, each toggled by a boolean flag:
| Phase | Flag | Runs | Maps to (CQRS) |
|---|---|---|---|
| Read | read |
a provider fetches the data | Query |
| Write | write |
a processor persists / acts on the data | Command |
A full request is read → deserialize → validate → write → serialize. The provider
supplies the object the rest of the chain operates on; the processor performs the
side effect (persist, delete, send mail, build a file). This is API Platform's CQRS
split: GET-style reads go through a provider, state changes go through a
processor.
The flags default from the request, not the verb
You leave read/write unset and API Platform resolves them at runtime from the
HTTP request (see MainController / WriteListener in core):
writedefaults to "the method is not safe" —falseforGET/HEAD,trueforPOST,PUT,PATCH,DELETE.readdefaults to "the operation has URI variables, or the method is safe" — so item operations (which carry{id}) and allGETs read, but a collectionPOST(no URI variables, unsafe) does not.
| Operation | read |
write |
|---|---|---|
Get (item) |
true (has {id}) |
false |
GetCollection |
true (safe) |
false |
Post (collection) |
false (no URI vars, unsafe) |
true |
Put, Patch (item) |
true (has {id}) |
true |
Delete (item) |
true (has {id}) |
true |
So a Get skips the processor by default, and a collection Post runs a processor
without a provider. Override either flag to decouple the phase from this default:
// Run a processor on a GET (file download, report generation, counter bump):
new Get(
uriTemplate: '/orders/{id}/download',
write: true, // turn the write phase ON — processor now fires
processor: OrderDownloadProcessor::class,
)
// Skip the built-in fetch on an item write (upsert: processor handles a missing row):
new Put(
read: false, // no provider runs; $data comes from deserialization only
processor: UpsertProcessor::class,
)
write: true is the common case: a Get whose write defaults to false would
never invoke its processor — flipping the flag is what enables it. Conversely
read: false stops the built-in provider on an item operation from a needless fetch.
Securing operations
The security attribute takes a Symfony ExpressionLanguage string evaluated
before the operation runs. Available variables: user, object (item operations),
request parameters when explicitly exposed.
Role-based
#[ApiResource(
operations: [
new GetCollection(),
new Post(security: "is_granted('ROLE_ADMIN')"),
]
)]
class Invoice {}
Object-based (item operations)
object is the fetched resource — use it for ownership checks on Get, Put,
Patch, Delete:
new Get(security: "object.getOwner() == user")
new Patch(security: "object.getOwner() == user or is_granted('ROLE_ADMIN')")
securityPostDenormalize runs after the request body is applied — use it when the
decision depends on incoming values (e.g. preventing privilege escalation on PATCH).
Parameter-based
Each parameter carries its own security expression, where the parameter name
becomes a variable bound to the submitted value. Declare them in parameters as
QueryParameter or HeaderParameter:
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\HeaderParameter;
use ApiPlatform\Metadata\QueryParameter;
use Symfony\Component\TypeInfo\Type\BuiltinType;
use Symfony\Component\TypeInfo\TypeIdentifier;
#[GetCollection(
parameters: [
'name' => new QueryParameter(security: 'is_granted("ROLE_ADMIN")'),
'auth' => new HeaderParameter(security: '"secured" == auth', nativeType: new BuiltinType(TypeIdentifier::STRING)),
'secret' => new QueryParameter(security: '"secured" == secret', nativeType: new BuiltinType(TypeIdentifier::STRING)),
],
)]
?name=foo evaluates is_granted("ROLE_ADMIN") (403 otherwise); the auth header
or ?secret=… must equal secured or the request is rejected. The parameter is
only checked when present — an absent or value-less parameter passes.
For query-level data isolation (multi-tenant, soft-delete) that must apply to every query regardless of operation, use a Doctrine extension or link handler instead — see securing-collections.
securityonly guards an already-fetched object; it does not scope collections.
Validation
API Platform runs Symfony's Validator on the deserialized object before the processor. Constraints live on the resource/input DTO (see custom-validator).
Validation groups per operation
#[ApiResource(
operations: [
new Post(validationContext: ['groups' => ['Default', 'user:create']]),
new Patch(validationContext: ['groups' => ['Default', 'user:update']]),
]
)]
#[Assert\NotBlank(groups: ['user:create'])] // required on create only
#[Assert\Email(groups: ['user:create', 'user:update'])] // checked on both
public ?string $email = null;
Collect denormalization errors
By default a type mismatch in the body throws on the first bad field. Enable
collect_denormalization_errors to report every malformed field at once in the 422
violations array:
#[Post(validationContext: ['collect_denormalization_errors' => true])]
Each malformed field becomes a violations entry with a propertyPath, a
This value should be of type … message, and a hint explaining the failure.
Validating query parameters
Attach constraints to a QueryParameter (or HeaderParameter); invalid values
yield 422 before the provider runs:
use ApiPlatform\Metadata\QueryParameter;
use Symfony\Component\Validator\Constraints as Assert;
#[GetCollection(
parameters: [
'page' => new QueryParameter(constraints: [new Assert\Positive()]),
],
)]
Deprecating endpoints
deprecationReason adds a Deprecation header; sunset adds a Sunset header
with the removal date. Apply at resource or operation level.
#[ApiResource(
deprecationReason: 'Use /v2/invoices instead.',
sunset: '2026-01-01T00:00:00+00:00',
)]
class Invoice {}
deprecationReason emits a Deprecation header and sunset a Sunset header. To
also advertise a migration doc, add an explicit operation links: entry —
new Link('deprecation', 'https://…') renders Link: <…>; rel="deprecation".
Nested PATCH gotcha
With application/merge-patch+json, to update an existing nested resource you
must include its identifier. Omitting it makes the serializer treat the nested
object as new (and attempt to create it):
{ "shippingAddress": { "id": 12, "city": "Lyon" } }
Without "id", API Platform tries to create a new Address rather than patch #12.
Laravel
Per-operation metadata (security, validationContext groups, collect_denormalization_errors,
deprecationReason/sunset, parameter validation/security) is mostly shared, but
auth and validation wiring differ:
- Authorization integrates with Laravel policies, not Symfony voters. Once a
policy exists, API Platform auto-maps operations to methods: GET collection →
viewAny, GET →view, POST →create, PATCH/PUT →update(PUT →createif absent), DELETE →delete. Override the mapping with apolicy:property on the operation:new Patch(policy: 'myCustomPolicy'). ThesecurityExpressionLanguage string also works. - Authentication / middleware is attached with the Laravel
middleware:property per operation (new Patch(middleware: 'auth:sanctum')) or globally underdefaults.middlewareinconfig/api-platform.php. - Validation uses Laravel
rules(array / closure /FormRequest) per resource or operation rather than SymfonyvalidationContextgroups — see the custom-validator Laravel section.AuthenticationException→ 401 andAuthorizationException→ 403 are mapped by default in the config'sexception_to_status. - Query/header
Parameterconstraintsare Laravel validation rule strings (e.g.'min:2'), not Symfony constraints.
Checklist
- Write operations restricted with
security/securityPostDenormalize - Collection isolation handled by an extension, not
security(see securing-collections) -
validationContextgroups split create vs update constraints -
collect_denormalization_errorsenabled where clients need full error lists - Query parameters carry validation constraints
- Deprecated endpoints set
deprecationReason+sunset
이 스킬의 파일
1operations skill 질문
Claude Code에 operations skill을 어떻게 설치하나요?
npx degit api-platform/skillset/skills/operations ~/.claude/skills/operations를 실행합니다. Claude Code는 ~/.claude/skills(모든 프로젝트) 또는 .claude/skills(한 프로젝트)에서 skills를 로드하며, 설명이 작업과 일치할 때 skill을 사용합니다.
operations skill을 사용할 수 있는 클라이언트는 무엇인가요?
Agent Skills 형식을 읽는 모든 클라이언트(Claude Code, Codex, Gemini CLI, claude.ai(ZIP으로 업로드))가 사용할 수 있습니다. skill 내 스크립트는 클라이언트가 코드 실행을 허용하는 경우에만 실행됩니다.
엔드포인트를 관리자에게만 제한하려면 어떻게 해야 하나요?
is_granted('ROLE_ADMIN')와 같은 Symfony ExpressionLanguage 문자열을 security 속성에 사용하면 됩니다.
리소스 생성과 수정 시 서로 다른 검증 규칙을 적용하려면 어떻게 하나요?
operation의 validationContext 내에서 validation groups를 사용하여 서로 다른 제약 조건 세트를 지정할 수 있습니다.
첫 번째 오류만 보고하는 대신 잘못된 모든 필드를 한 번에 보고하려면 어떻게 하나요?
validationContext에서 collect_denormalization_errors 플래그를 활성화하면 422 응답과 함께 위반 사항 전체 목록을 반환합니다.