feat: Studio 응답 봉투를 전송 경계에서 언랩한다

studio-v1.yaml v3.0.0(ADR-006)에 맞춰 계약을 재생성하고, 성공은
{success,data,meta}, 실패는 {success,error,meta} 봉투를 전송 경계에서
언랩하는 envelopeData/envelopeError validator를 도입한다. 앱·도메인
계층은 기존과 같은 payload/ProblemDetails 모양을 계속 받고,
StudioGateway 포트 시그니처는 무변경이다.

- tech-log-studio-contract-contribution.ts: envelopeData/envelopeError
  도입, 18개 operation의 outputValidator를 passthrough에서 envelopeData로
  교체
- studio-error-mapping.ts: 봉투 오류의 status(항상 0)를
  outcome.metadata.status로 덮는다. SafeResponseMetadata.status가
  실제 필드명이며(httpStatus 아님) PROBLEM outcome에서 필수 필드다
- contract.ts: 삭제된 ProblemDetails 생성 스키마를 손으로 유지 — 앱
  계층·mock 게이트웨이가 그 모양을 계속 소비한다
- asset-upload-transport.ts: multipart 업로드는 일반 계약 런타임을
  거치지 않는 별도 seam이지만 같은 wire 봉투를 쓴다 — envelopeData/
  envelopeError를 재사용해 이 경로도 언랩한다 (브리프 파일 목록 밖의
  발견, report에 기록)
- 테스트: 신규 studio-envelope-unwrap.test.ts(TDD) + 봉투 뼈대를 직접
  만드는 기존 테스트(asset-upload-transport, studio-csrf-composition,
  contract-generation)를 봉투 형태로 갱신

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-08-18 21:55:45 +09:00
co-authored by Claude Opus 5
parent d84b57bb3f
commit 25a6b63d27
11 changed files with 640 additions and 216 deletions
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: Tech Log Studio API
version: 2.0.0
version: 3.0.0
description: |
Tech Log Studio orchestration 계약이다.
@@ -109,7 +109,7 @@ paths:
tags: [Session]
summary: 현재 Studio 세션과 CSRF 토큰을 조회한다
responses:
"200": { description: 인증된 Studio 세션, content: { application/json: { schema: { $ref: "#/components/schemas/StudioSession" } } } }
"200": { description: 인증된 Studio 세션, content: { application/json: { schema: { $ref: "#/components/schemas/StudioSessionEnvelope" } } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"503": { $ref: "#/components/responses/StudioUnavailable" }
@@ -123,7 +123,7 @@ paths:
`nextAction`을 포함한 모든 workflow 상태는 서버가 계산한다.
Frontend는 여러 endpoint를 조합해 workflow 상태를 재추론하지 않는다.
responses:
"200": { description: Dashboard lists and totals, content: { application/json: { schema: { $ref: "#/components/schemas/StudioDashboard" } } } }
"200": { description: Dashboard lists and totals, content: { application/json: { schema: { $ref: "#/components/schemas/StudioDashboardEnvelope" } } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"503": { $ref: "#/components/responses/StudioUnavailable" }
@@ -150,7 +150,7 @@ paths:
- { $ref: "#/components/parameters/Cursor" }
- { $ref: "#/components/parameters/Limit" }
responses:
"200": { description: Working-copy cursor page, content: { application/json: { schema: { $ref: "#/components/schemas/DocumentPage" } } } }
"200": { description: Working-copy cursor page, content: { application/json: { schema: { $ref: "#/components/schemas/DocumentPageEnvelope" } } } }
"400": { $ref: "#/components/responses/MalformedRequest" }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
@@ -169,7 +169,7 @@ paths:
"201":
description: Created working copy
headers: { Idempotency-Replayed: { $ref: "#/components/headers/IdempotencyReplayed" } }
content: { application/json: { schema: { $ref: "#/components/schemas/WorkingCopy" } } }
content: { application/json: { schema: { $ref: "#/components/schemas/WorkingCopyEnvelope" } } }
"400": { $ref: "#/components/responses/MalformedRequest" }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
@@ -184,7 +184,7 @@ paths:
tags: [Documents]
summary: Get a working copy and its current state
responses:
"200": { description: Working-copy detail, content: { application/json: { schema: { $ref: "#/components/schemas/WorkingCopyDetail" } } } }
"200": { description: Working-copy detail, content: { application/json: { schema: { $ref: "#/components/schemas/WorkingCopyDetailEnvelope" } } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"404": { $ref: "#/components/responses/DocumentNotFound" }
@@ -197,8 +197,8 @@ paths:
편집 가능한 content field만 저장한다. lifecycle 전이는 명시적인 Domain
Action이 담당한다. 저장은 Public Projection을 변경하지 않는다.
`expectedVersion` 불일치는 `VERSION_CONFLICT`이며 응답 `ProblemDetails`
`latestDocument`로 현재 상태를 함께 제공한다.
`expectedVersion` 불일치는 `VERSION_CONFLICT`이며 응답 `error.details`
(`VersionConflictDetails`)의 `latestDocument`로 현재 상태를 함께 제공한다.
parameters:
- { $ref: "#/components/parameters/IdempotencyKey" }
- { $ref: "#/components/parameters/CsrfToken" }
@@ -207,7 +207,7 @@ paths:
"200":
description: Saved working-copy detail
headers: { Idempotency-Replayed: { $ref: "#/components/headers/IdempotencyReplayed" } }
content: { application/json: { schema: { $ref: "#/components/schemas/WorkingCopyDetail" } } }
content: { application/json: { schema: { $ref: "#/components/schemas/WorkingCopyDetailEnvelope" } } }
"400": { $ref: "#/components/responses/MalformedRequest" }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
@@ -244,7 +244,7 @@ paths:
"200":
description: Validation report
headers: { Idempotency-Replayed: { $ref: "#/components/headers/IdempotencyReplayed" } }
content: { application/json: { schema: { $ref: "#/components/schemas/ValidationReport" } } }
content: { application/json: { schema: { $ref: "#/components/schemas/ValidationReportEnvelope" } } }
"400": { $ref: "#/components/responses/MalformedRequest" }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
@@ -264,7 +264,7 @@ paths:
anonymous `/preview/{token}` 계약은 폐기되었다. Preview는 인증된
Studio API로만 조회한다.
responses:
"200": { description: Preview detail, content: { application/json: { schema: { $ref: "#/components/schemas/PreviewDetail" } } } }
"200": { description: Preview detail, content: { application/json: { schema: { $ref: "#/components/schemas/PreviewDetailEnvelope" } } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"404": { $ref: "#/components/responses/PreviewNotFound" }
@@ -285,7 +285,7 @@ paths:
"201":
description: Created preview
headers: { Idempotency-Replayed: { $ref: "#/components/headers/IdempotencyReplayed" } }
content: { application/json: { schema: { $ref: "#/components/schemas/PublicPreview" } } }
content: { application/json: { schema: { $ref: "#/components/schemas/PublicPreviewEnvelope" } } }
"400": { $ref: "#/components/responses/MalformedRequest" }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
@@ -329,7 +329,7 @@ paths:
"200":
description: Publication aggregate and immutable event
headers: { Idempotency-Replayed: { $ref: "#/components/headers/IdempotencyReplayed" } }
content: { application/json: { schema: { $ref: "#/components/schemas/PublishResult" } } }
content: { application/json: { schema: { $ref: "#/components/schemas/PublishResultEnvelope" } } }
"400": { $ref: "#/components/responses/MalformedRequest" }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
@@ -350,7 +350,7 @@ paths:
- { $ref: "#/components/parameters/Cursor" }
- { $ref: "#/components/parameters/Limit" }
responses:
"200": { description: Publication cursor page, content: { application/json: { schema: { $ref: "#/components/schemas/PublicationPage" } } } }
"200": { description: Publication cursor page, content: { application/json: { schema: { $ref: "#/components/schemas/PublicationPageEnvelope" } } } }
"400": { $ref: "#/components/responses/MalformedRequest" }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
@@ -379,7 +379,7 @@ paths:
"200":
description: Updated publication aggregate and event
headers: { Idempotency-Replayed: { $ref: "#/components/headers/IdempotencyReplayed" } }
content: { application/json: { schema: { $ref: "#/components/schemas/PublishResult" } } }
content: { application/json: { schema: { $ref: "#/components/schemas/PublishResultEnvelope" } } }
"400": { $ref: "#/components/responses/MalformedRequest" }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
@@ -401,7 +401,7 @@ paths:
`UNPUBLISHED` Event는 자체 snapshot을 갖지 않는다. 이 경우
`sourcePublishedEventId`가 가리키는 마지막 공개 Snapshot을 사용한다.
responses:
"200": { description: Publication snapshot, content: { application/json: { schema: { $ref: "#/components/schemas/PublicationSnapshot" } } } }
"200": { description: Publication snapshot, content: { application/json: { schema: { $ref: "#/components/schemas/PublicationSnapshotEnvelope" } } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"404": { $ref: "#/components/responses/PublicationSnapshotNotFound" }
@@ -430,7 +430,7 @@ paths:
- { $ref: "#/components/parameters/Cursor" }
- { $ref: "#/components/parameters/Limit" }
responses:
"200": { description: Catalog cursor page, content: { application/json: { schema: { $ref: "#/components/schemas/CatalogPage" } } } }
"200": { description: Catalog cursor page, content: { application/json: { schema: { $ref: "#/components/schemas/CatalogPageEnvelope" } } } }
"400": { $ref: "#/components/responses/MalformedRequest" }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
@@ -449,7 +449,7 @@ paths:
- { $ref: "#/components/parameters/Cursor" }
- { $ref: "#/components/parameters/Limit" }
responses:
"200": { description: Asset cursor page, content: { application/json: { schema: { $ref: "#/components/schemas/AssetPage" } } } }
"200": { description: Asset cursor page, content: { application/json: { schema: { $ref: "#/components/schemas/AssetPageEnvelope" } } } }
"400": { $ref: "#/components/responses/MalformedRequest" }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
@@ -483,7 +483,7 @@ paths:
"201":
description: Stored asset
headers: { Idempotency-Replayed: { $ref: "#/components/headers/IdempotencyReplayed" } }
content: { application/json: { schema: { $ref: "#/components/schemas/Asset" } } }
content: { application/json: { schema: { $ref: "#/components/schemas/AssetEnvelope" } } }
"400": { $ref: "#/components/responses/MalformedRequest" }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
@@ -500,7 +500,7 @@ paths:
tags: [Assets]
summary: Get an asset with its usage
responses:
"200": { description: Asset detail, content: { application/json: { schema: { $ref: "#/components/schemas/AssetDetail" } } } }
"200": { description: Asset detail, content: { application/json: { schema: { $ref: "#/components/schemas/AssetDetailEnvelope" } } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"404": { $ref: "#/components/responses/AssetNotFound" }
@@ -520,7 +520,7 @@ paths:
"200":
description: Updated asset
headers: { Idempotency-Replayed: { $ref: "#/components/headers/IdempotencyReplayed" } }
content: { application/json: { schema: { $ref: "#/components/schemas/Asset" } } }
content: { application/json: { schema: { $ref: "#/components/schemas/AssetEnvelope" } } }
"400": { $ref: "#/components/responses/MalformedRequest" }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
@@ -593,26 +593,26 @@ components:
IdempotencyReplayed: { description: True when the original result was replayed, schema: { type: boolean } }
responses:
MalformedRequest: { description: Malformed request, x-error-codes: [REQUEST_VALIDATION_FAILED], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
AuthenticationRequired: { description: Authentication required, x-error-codes: [AUTHENTICATION_REQUIRED], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
AccessDenied: { description: Studio access denied, x-error-codes: [STUDIO_ACCESS_DENIED], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
DocumentNotFound: { description: Document not found, x-error-codes: [DOCUMENT_NOT_FOUND], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
PreviewNotFound: { description: Document or preview not found, x-error-codes: [DOCUMENT_NOT_FOUND, PREVIEW_NOT_FOUND], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
PublicationNotFound: { description: Publication not found, x-error-codes: [PUBLICATION_NOT_FOUND], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
PublicationSnapshotNotFound: { description: Publication event or snapshot not found, x-error-codes: [PUBLICATION_EVENT_NOT_FOUND, PUBLICATION_SNAPSHOT_NOT_FOUND], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
AssetNotFound: { description: Asset not found, x-error-codes: [ASSET_NOT_FOUND], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
MalformedRequest: { description: Malformed request, x-error-codes: [REQUEST_VALIDATION_FAILED], content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
AuthenticationRequired: { description: Authentication required, x-error-codes: [AUTHENTICATION_REQUIRED], content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
AccessDenied: { description: Studio access denied, x-error-codes: [STUDIO_ACCESS_DENIED], content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
DocumentNotFound: { description: Document not found, x-error-codes: [DOCUMENT_NOT_FOUND], content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
PreviewNotFound: { description: Document or preview not found, x-error-codes: [DOCUMENT_NOT_FOUND, PREVIEW_NOT_FOUND], content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
PublicationNotFound: { description: Publication not found, x-error-codes: [PUBLICATION_NOT_FOUND], content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
PublicationSnapshotNotFound: { description: Publication event or snapshot not found, x-error-codes: [PUBLICATION_EVENT_NOT_FOUND, PUBLICATION_SNAPSHOT_NOT_FOUND], content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
AssetNotFound: { description: Asset not found, x-error-codes: [ASSET_NOT_FOUND], content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
CommandConflict:
description: |
Command conflicts with current state, freshness, or idempotency.
`ASSET_IN_USE`는 사용 중이거나 공개 이력이 있는 Asset의 hard delete 시도다.
x-error-codes: [VERSION_CONFLICT, PUBLICATION_CONFLICT, VALIDATION_STALE, PREVIEW_STALE, PREVIEW_EXPIRED, IDEMPOTENCY_KEY_REUSED, ASSET_IN_USE]
content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } }
RequestValidationFailed: { description: Request validation failed, x-error-codes: [REQUEST_VALIDATION_FAILED], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } }
RequestValidationFailed: { description: Request validation failed, x-error-codes: [REQUEST_VALIDATION_FAILED], content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
PreviewRejected:
description: Preview 생성이 도메인 규칙으로 거절되었다
x-error-codes: [REQUEST_VALIDATION_FAILED, VALIDATION_FAILED, ASSET_NOT_READY, ASSET_QUARANTINED]
content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } }
content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } }
PublishRejected:
description: |
Publication validation이 실패했다.
@@ -620,16 +620,205 @@ components:
`WARNING_ACKNOWLEDGEMENT_REQUIRED`는 `acknowledgedWarningCodes`가
현재 Validation의 WARNING 집합을 덮지 못한 경우다.
x-error-codes: [REQUEST_VALIDATION_FAILED, VALIDATION_FAILED, WARNING_ACKNOWLEDGEMENT_REQUIRED, ASSET_NOT_READY, ASSET_QUARANTINED]
content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } }
content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } }
AssetRejected:
description: Asset metadata 변경이 거절되었다
x-error-codes: [REQUEST_VALIDATION_FAILED, ASSET_NOT_READY, ASSET_QUARANTINED]
content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } }
PayloadTooLarge: { description: Upload exceeds the configured size limit, x-error-codes: [PAYLOAD_TOO_LARGE], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
UnsupportedMediaType: { description: Unsupported media type, x-error-codes: [UNSUPPORTED_MEDIA_TYPE], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
StudioUnavailable: { description: Studio unavailable, x-error-codes: [STUDIO_UNAVAILABLE], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } }
PayloadTooLarge: { description: Upload exceeds the configured size limit, x-error-codes: [PAYLOAD_TOO_LARGE], content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
UnsupportedMediaType: { description: Unsupported media type, x-error-codes: [UNSUPPORTED_MEDIA_TYPE], content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
StudioUnavailable: { description: Studio unavailable, x-error-codes: [STUDIO_UNAVAILABLE], content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } }
schemas:
# ------------------------------------------------------------- envelope
# wire format은 봉투다 (ADR-006). payload 스키마는 그대로 두고
# 응답만 <Payload>Envelope으로 감싼다.
ResponseMeta:
type: object
additionalProperties: false
required: [requestId, traceId]
properties:
requestId: { type: string, minLength: 1, maxLength: 200 }
traceId: { type: string, minLength: 1, maxLength: 200 }
correlationId: { type: [string, "null"], maxLength: 200 }
page: { type: "null", description: Studio는 body 안 cursor 페이지네이션을 쓰므로 항상 null이다 }
ApiError:
type: object
additionalProperties: false
required: [code, category, message, retryable]
properties:
code:
type: string
enum: [AUTHENTICATION_REQUIRED, STUDIO_ACCESS_DENIED, DOCUMENT_NOT_FOUND, VERSION_CONFLICT,
REQUEST_VALIDATION_FAILED, VALIDATION_FAILED, VALIDATION_STALE, PREVIEW_NOT_FOUND,
PREVIEW_STALE, PREVIEW_EXPIRED, PUBLICATION_NOT_FOUND, PUBLICATION_CONFLICT,
PUBLICATION_EVENT_NOT_FOUND, PUBLICATION_SNAPSHOT_NOT_FOUND,
WARNING_ACKNOWLEDGEMENT_REQUIRED, IDEMPOTENCY_KEY_REUSED, ASSET_NOT_FOUND,
ASSET_NOT_READY, ASSET_IN_USE, ASSET_QUARANTINED, PAYLOAD_TOO_LARGE,
UNSUPPORTED_MEDIA_TYPE, STUDIO_UNAVAILABLE]
category:
type: string
enum: [VALIDATION, AUTH, AUTHZ, NOT_FOUND, CONFLICT, RATE_LIMIT,
TRANSIENT_DEPENDENCY, PERMANENT_DEPENDENCY, DATA_INTEGRITY, INTERNAL]
message: { type: string, minLength: 1, maxLength: 5000 }
retryable: { type: boolean }
details:
oneOf:
- $ref: "#/components/schemas/ValidationErrorDetails"
- $ref: "#/components/schemas/VersionConflictDetails"
- $ref: "#/components/schemas/PublicationConflictDetails"
- type: "null"
ErrorEnvelope:
type: object
additionalProperties: false
required: [success, error, meta]
properties:
success: { type: boolean, const: false }
error: { $ref: "#/components/schemas/ApiError" }
meta: { $ref: "#/components/schemas/ResponseMeta" }
ValidationErrorDetails:
type: object
additionalProperties: false
required: [fieldErrors]
properties:
fieldErrors: { type: array, maxItems: 200, items: { $ref: "#/components/schemas/FieldError" } }
VersionConflictDetails:
type: object
additionalProperties: false
required: [latestDocument]
properties:
latestDocument: { $ref: "#/components/schemas/WorkingCopyDetail" }
conflictingFields:
type: array
uniqueItems: true
maxItems: 200
items: { type: string, pattern: "^(?:/(?:[^~/]|~0|~1)*)*$" }
PublicationConflictDetails:
type: object
additionalProperties: false
required: [latestPublication]
properties:
latestPublication: { $ref: "#/components/schemas/PublicationAggregate" }
StudioSessionEnvelope:
type: object
additionalProperties: false
required: [success, data, meta]
properties:
success: { type: boolean, const: true }
data: { $ref: "#/components/schemas/StudioSession" }
meta: { $ref: "#/components/schemas/ResponseMeta" }
StudioDashboardEnvelope:
type: object
additionalProperties: false
required: [success, data, meta]
properties:
success: { type: boolean, const: true }
data: { $ref: "#/components/schemas/StudioDashboard" }
meta: { $ref: "#/components/schemas/ResponseMeta" }
DocumentPageEnvelope:
type: object
additionalProperties: false
required: [success, data, meta]
properties:
success: { type: boolean, const: true }
data: { $ref: "#/components/schemas/DocumentPage" }
meta: { $ref: "#/components/schemas/ResponseMeta" }
WorkingCopyDetailEnvelope:
type: object
additionalProperties: false
required: [success, data, meta]
properties:
success: { type: boolean, const: true }
data: { $ref: "#/components/schemas/WorkingCopyDetail" }
meta: { $ref: "#/components/schemas/ResponseMeta" }
WorkingCopyEnvelope:
type: object
additionalProperties: false
required: [success, data, meta]
properties:
success: { type: boolean, const: true }
data: { $ref: "#/components/schemas/WorkingCopy" }
meta: { $ref: "#/components/schemas/ResponseMeta" }
ValidationReportEnvelope:
type: object
additionalProperties: false
required: [success, data, meta]
properties:
success: { type: boolean, const: true }
data: { $ref: "#/components/schemas/ValidationReport" }
meta: { $ref: "#/components/schemas/ResponseMeta" }
PreviewDetailEnvelope:
type: object
additionalProperties: false
required: [success, data, meta]
properties:
success: { type: boolean, const: true }
data: { $ref: "#/components/schemas/PreviewDetail" }
meta: { $ref: "#/components/schemas/ResponseMeta" }
PublicPreviewEnvelope:
type: object
additionalProperties: false
required: [success, data, meta]
properties:
success: { type: boolean, const: true }
data: { $ref: "#/components/schemas/PublicPreview" }
meta: { $ref: "#/components/schemas/ResponseMeta" }
PublishResultEnvelope:
type: object
additionalProperties: false
required: [success, data, meta]
properties:
success: { type: boolean, const: true }
data: { $ref: "#/components/schemas/PublishResult" }
meta: { $ref: "#/components/schemas/ResponseMeta" }
PublicationPageEnvelope:
type: object
additionalProperties: false
required: [success, data, meta]
properties:
success: { type: boolean, const: true }
data: { $ref: "#/components/schemas/PublicationPage" }
meta: { $ref: "#/components/schemas/ResponseMeta" }
PublicationSnapshotEnvelope:
type: object
additionalProperties: false
required: [success, data, meta]
properties:
success: { type: boolean, const: true }
data: { $ref: "#/components/schemas/PublicationSnapshot" }
meta: { $ref: "#/components/schemas/ResponseMeta" }
CatalogPageEnvelope:
type: object
additionalProperties: false
required: [success, data, meta]
properties:
success: { type: boolean, const: true }
data: { $ref: "#/components/schemas/CatalogPage" }
meta: { $ref: "#/components/schemas/ResponseMeta" }
AssetPageEnvelope:
type: object
additionalProperties: false
required: [success, data, meta]
properties:
success: { type: boolean, const: true }
data: { $ref: "#/components/schemas/AssetPage" }
meta: { $ref: "#/components/schemas/ResponseMeta" }
AssetDetailEnvelope:
type: object
additionalProperties: false
required: [success, data, meta]
properties:
success: { type: boolean, const: true }
data: { $ref: "#/components/schemas/AssetDetail" }
meta: { $ref: "#/components/schemas/ResponseMeta" }
AssetEnvelope:
type: object
additionalProperties: false
required: [success, data, meta]
properties:
success: { type: boolean, const: true }
data: { $ref: "#/components/schemas/Asset" }
meta: { $ref: "#/components/schemas/ResponseMeta" }
# ---------------------------------------------------------------- session
StudioSession:
type: object
@@ -1645,51 +1834,3 @@ components:
pattern: "^(?:/(?:[^~/]|~0|~1)*)*$"
description: JSON Pointer to the invalid field
message: { type: string, minLength: 1, maxLength: 1000 }
ProblemDetails:
type: object
additionalProperties: true
required: [type, title, status, detail, code]
properties:
type: { type: string, format: uri-reference }
title: { type: string, minLength: 1, maxLength: 200 }
status: { type: integer, minimum: 400, maximum: 599 }
detail: { type: string, minLength: 1, maxLength: 5000 }
code:
type: string
enum:
- AUTHENTICATION_REQUIRED
- STUDIO_ACCESS_DENIED
- DOCUMENT_NOT_FOUND
- VERSION_CONFLICT
- REQUEST_VALIDATION_FAILED
- VALIDATION_FAILED
- VALIDATION_STALE
- PREVIEW_NOT_FOUND
- PREVIEW_STALE
- PREVIEW_EXPIRED
- PUBLICATION_NOT_FOUND
- PUBLICATION_CONFLICT
- PUBLICATION_EVENT_NOT_FOUND
- PUBLICATION_SNAPSHOT_NOT_FOUND
- WARNING_ACKNOWLEDGEMENT_REQUIRED
- IDEMPOTENCY_KEY_REUSED
- ASSET_NOT_FOUND
- ASSET_NOT_READY
- ASSET_IN_USE
- ASSET_QUARANTINED
- PAYLOAD_TOO_LARGE
- UNSUPPORTED_MEDIA_TYPE
- STUDIO_UNAVAILABLE
instance: { type: string, format: uri-reference }
traceId: { type: string, maxLength: 200 }
fieldErrors: { type: array, maxItems: 200, items: { $ref: "#/components/schemas/FieldError" } }
latestDocument: { $ref: "#/components/schemas/WorkingCopyDetail" }
latestPublication: { $ref: "#/components/schemas/PublicationAggregate" }
conflictingFields:
type: array
uniqueItems: true
maxItems: 200
items:
type: string
pattern: "^(?:/(?:[^~/]|~0|~1)*)*$"
retryable: { type: boolean }