feat: add the Studio release editor and point the footer at the changelog
The public site shipped a Releases page and a footer link to a release, and neither could ever have content: the read path existed, the write path did not. This adds the seven release operations to the contract contribution and the gateway, and a Studio screen that can actually write one. The editor is six markdown fields rather than one, because that is what the contract models and what a release note is — why, what, what a reader notices, what it leaves in the code, how it was verified, what is still open. Publishing is separate from saving: a draft saves in any state, but the public query keys on PUBLISHED alone, so publish is where completeness is demanded. No new CSS. The screen reuses the document editor's field classes and the working-copy list's row classes, so it inherits Studio's spacing and type instead of introducing a second look. The footer previously linked `/releases/0.1.0` — a version that did not exist, so the link 404'd, and one that would have gone stale at 0.2.0 anyway. It now points at the changelog index, which is the only place that knows what the latest release is and which reads correctly when there are none. Route inventory, navigation order, message catalog, manual accessibility evidence, artifact baseline, and the pinned gate-shape digest all move with the new route. The digest was recomputed by first reproducing the previous constant from the previous gates.json, so the computation is known to be the one it was pinned under.
This commit is contained in:
@@ -5,11 +5,11 @@ info:
|
||||
description: |-
|
||||
⚠ 봉투 결정(ADR-006) 부분 반영 — 이 파일에는 두 모양이 공존한다.
|
||||
|
||||
topics 4개와 projects 5개는 studio-v1.yaml과 같은 방식으로 변환했다
|
||||
(`application/json` + ErrorEnvelope / <Payload>Envelope). 그 9개가 첫 구현
|
||||
대상이자 첫 소비자이기 때문이다.
|
||||
topics 4개, projects 5개, releases 7개를 studio-v1.yaml과 같은 방식으로
|
||||
변환했다 (`application/json` + ErrorEnvelope / <Payload>Envelope). 그 16개가
|
||||
구현된 것이자 소비자가 있는 것이기 때문이다.
|
||||
|
||||
나머지 70개는 아직 bare payload + `application/problem+json` + ProblemDetails
|
||||
나머지 63개는 아직 bare payload + `application/problem+json` + ProblemDetails
|
||||
다. 구현에 착수할 때 같은 방식으로 따라온다 — 소비자가 없는 operation을 미리
|
||||
변환해 두면 검증되지 않은 모양이 계약에 고정된다.
|
||||
|
||||
@@ -3630,49 +3630,49 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateDraftResponse'
|
||||
$ref: '#/components/schemas/CreateDraftResponseEnvelope'
|
||||
'400':
|
||||
description: Bad Request
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'403':
|
||||
description: Forbidden
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'404':
|
||||
description: Not Found
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'409':
|
||||
description: Conflict
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'422':
|
||||
description: Unprocessable Content
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'500':
|
||||
description: Internal Server Error
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -3705,37 +3705,37 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReleaseIndexPage'
|
||||
$ref: '#/components/schemas/ReleaseIndexPageEnvelope'
|
||||
'400':
|
||||
description: Bad Request
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'403':
|
||||
description: Forbidden
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'404':
|
||||
description: Not Found
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'500':
|
||||
description: Internal Server Error
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
security:
|
||||
- sessionCookie: []
|
||||
/api/v1/studio/releases/{id}:
|
||||
@@ -3756,37 +3756,37 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReleaseEditResponse'
|
||||
$ref: '#/components/schemas/ReleaseEditResponseEnvelope'
|
||||
'400':
|
||||
description: Bad Request
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'403':
|
||||
description: Forbidden
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'404':
|
||||
description: Not Found
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'500':
|
||||
description: Internal Server Error
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
security:
|
||||
- sessionCookie: []
|
||||
put:
|
||||
@@ -3807,49 +3807,49 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReleaseEditResponse'
|
||||
$ref: '#/components/schemas/ReleaseEditResponseEnvelope'
|
||||
'400':
|
||||
description: Bad Request
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'403':
|
||||
description: Forbidden
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'404':
|
||||
description: Not Found
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'409':
|
||||
description: Conflict
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'422':
|
||||
description: Unprocessable Content
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'500':
|
||||
description: Internal Server Error
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -3876,45 +3876,45 @@ paths:
|
||||
'400':
|
||||
description: Bad Request
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'403':
|
||||
description: Forbidden
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'404':
|
||||
description: Not Found
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'409':
|
||||
description: Conflict
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'422':
|
||||
description: Unprocessable Content
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'500':
|
||||
description: Internal Server Error
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -3942,49 +3942,49 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PublishResponse'
|
||||
$ref: '#/components/schemas/PublishResponseEnvelope'
|
||||
'400':
|
||||
description: Bad Request
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'403':
|
||||
description: Forbidden
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'404':
|
||||
description: Not Found
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'409':
|
||||
description: Conflict
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'422':
|
||||
description: Unprocessable Content
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'500':
|
||||
description: Internal Server Error
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -4012,49 +4012,49 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReleaseEditResponse'
|
||||
$ref: '#/components/schemas/ReleaseEditResponseEnvelope'
|
||||
'400':
|
||||
description: Bad Request
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'403':
|
||||
description: Forbidden
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'404':
|
||||
description: Not Found
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'409':
|
||||
description: Conflict
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'422':
|
||||
description: Unprocessable Content
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
'500':
|
||||
description: Internal Server Error
|
||||
content:
|
||||
application/problem+json:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetails'
|
||||
$ref: '#/components/schemas/ErrorEnvelope'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -5267,6 +5267,9 @@ components:
|
||||
- PROJECT_NOT_FOUND
|
||||
- PROJECT_SLUG_TAKEN
|
||||
- PROJECT_IN_USE
|
||||
- RELEASE_NOT_FOUND
|
||||
- RELEASE_VERSION_TAKEN
|
||||
- RELEASE_NOT_PUBLISHABLE
|
||||
- INTERNAL_ERROR
|
||||
description: '`INTERNAL_ERROR` 는 이 기능이 아니라 스켈레톤의 공통 처리기가 내는 코드다. 계약이 그것까지 열거해야 500 응답이 계약을 벗어나지 않는다.'
|
||||
category:
|
||||
@@ -5392,6 +5395,51 @@ components:
|
||||
$ref: '#/components/schemas/ProjectIndexPage'
|
||||
meta:
|
||||
$ref: '#/components/schemas/ResponseMeta'
|
||||
ReleaseIndexPageEnvelope:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- success
|
||||
- data
|
||||
- meta
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
const: true
|
||||
data:
|
||||
$ref: '#/components/schemas/ReleaseIndexPage'
|
||||
meta:
|
||||
$ref: '#/components/schemas/ResponseMeta'
|
||||
ReleaseEditResponseEnvelope:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- success
|
||||
- data
|
||||
- meta
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
const: true
|
||||
data:
|
||||
$ref: '#/components/schemas/ReleaseEditResponse'
|
||||
meta:
|
||||
$ref: '#/components/schemas/ResponseMeta'
|
||||
PublishResponseEnvelope:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- success
|
||||
- data
|
||||
- meta
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
const: true
|
||||
data:
|
||||
$ref: '#/components/schemas/PublishResponse'
|
||||
meta:
|
||||
$ref: '#/components/schemas/ResponseMeta'
|
||||
ProjectEditResponseEnvelope:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
|
||||
Reference in New Issue
Block a user