diff --git a/artifacts/tests/a11y-manual/TECH_LOG_STUDIO_TAXONOMY.md b/artifacts/tests/a11y-manual/TECH_LOG_STUDIO_TAXONOMY.md new file mode 100644 index 0000000..681e27d --- /dev/null +++ b/artifacts/tests/a11y-manual/TECH_LOG_STUDIO_TAXONOMY.md @@ -0,0 +1,18 @@ +# TECH_LOG_STUDIO_TAXONOMY accessibility review + +Status: pending-manual-review +Route ID: TECH_LOG_STUDIO_TAXONOMY +Release ID: +Reviewer: +Reviewed at: +Signature: +Attestation: pending +M1 Keyboard: pending +M2 Visible focus: pending +M3 Route focus: pending +M4 Modal focus: pending +M5 Error association: pending +M6 Color signal: pending +M7 Reduced motion: pending +Screen reader: pending +Notes: Human review pending; automated evidence does not replace signed keyboard, focus, and screen-reader review. diff --git a/config/ci/gates.json b/config/ci/gates.json index 6bf78f8..b4655a8 100644 --- a/config/ci/gates.json +++ b/config/ci/gates.json @@ -1196,6 +1196,12 @@ "schemaId": "markdown", "production": "source-controlled" }, + { + "id": "artifact-artifacts-tests-a11y-manual-TECH-LOG-STUDIO-TAXONOMY-md", + "path": "artifacts/tests/a11y-manual/TECH_LOG_STUDIO_TAXONOMY.md", + "schemaId": "markdown", + "production": "source-controlled" + }, { "id": "artifact-artifacts-tests-a11y-manual-TECH-LOG-STUDIO-NOT-FOUND-md", "path": "artifacts/tests/a11y-manual/TECH_LOG_STUDIO_NOT_FOUND.md", @@ -1949,6 +1955,7 @@ "artifact-artifacts-tests-a11y-manual-TECH-LOG-STUDIO-PUBLICATIONS-md", "artifact-artifacts-tests-a11y-manual-TECH-LOG-STUDIO-PUBLICATION-PREVIEW-md", "artifact-artifacts-tests-a11y-manual-TECH-LOG-STUDIO-ASSETS-md", + "artifact-artifacts-tests-a11y-manual-TECH-LOG-STUDIO-TAXONOMY-md", "artifact-artifacts-tests-a11y-manual-TECH-LOG-STUDIO-NOT-FOUND-md", "artifact-artifacts-tests-a11y-manual-NOT-FOUND-md", "artifact-artifacts-tests-a11y-manual-report-json" diff --git a/config/runtime/production.json b/config/runtime/production.json index b3f734d..b1d66b4 100644 --- a/config/runtime/production.json +++ b/config/runtime/production.json @@ -15,7 +15,7 @@ "OFFLINE_COMMANDS": "DEFAULT" }, "TECH_LOG_STUDIO_SOURCE": "HTTP", - "TECH_LOG_PUBLIC_SOURCE": "MOCK", + "TECH_LOG_PUBLIC_SOURCE": "HTTP", "FEATURE_OVERRIDES": { "reference-feature": "DEFAULT" } diff --git a/public/release-manifest.json b/public/release-manifest.json index f49e5e8..2ec5e30 100644 --- a/public/release-manifest.json +++ b/public/release-manifest.json @@ -38,21 +38,28 @@ }, "contractSet": { "setAlgorithm": "CA_CONTRACT_SET_V1", - "setDigest": "sha256:c8a29bdf6deadd8951b0a15426fcc119ff85ee9349acc0e8b200c8a5650d8b31", + "setDigest": "sha256:ac1448ced933beece92924be4ffb7beb182c3df26371a83ffeffbb174ce98e72", "packages": [ + { + "packageId": "@tech-log/management-contract", + "version": "1.0.0", + "digest": "sha256:ec5e432215fb041abee980787366a6db29ff1ecdd78416aa9c61e09b9b91022f", + "runtimeProtocolVersion": 1, + "sourceRevision": "6ef5c1c" + }, { "packageId": "@tech-log/public-contract", "version": "2.0.0", "digest": "sha256:8ac71425b38658f34641102b4c2e6e21288c811efebdb92c0a46fb9d4790e23e", "runtimeProtocolVersion": 1, - "sourceRevision": "b98eaf9" + "sourceRevision": "6ef5c1c" }, { "packageId": "@tech-log/studio-contract", "version": "3.0.0", "digest": "sha256:6cae9924403d0761f401643a022980b8e04183eea0d890c143c9fbbbbc7431e4", "runtimeProtocolVersion": 1, - "sourceRevision": "b98eaf9" + "sourceRevision": "6ef5c1c" } ] } diff --git a/scripts/contracts/ci-gates.ts b/scripts/contracts/ci-gates.ts index 2b21f31..2db948e 100644 --- a/scripts/contracts/ci-gates.ts +++ b/scripts/contracts/ci-gates.ts @@ -459,7 +459,12 @@ const CANONICAL_GATE_SHAPE_SHA256 = // Dev release manifest drift fix, item 2: recomputed again after FE-GATE-010 // gained `check-dev-release-manifest`. Same method — 98d19911… was first // reproduced from the previous gates.json before this value was hashed. - "b40962448e617883060e09eb7183837cfea6833519f435f11713efd083210dbe"; + // Taxonomy route: recomputed again after FE-GATE-009 gained the + // TECH_LOG_STUDIO_TAXONOMY manual accessibility evidence artifact. The gate + // lists one evidence artifact per installed route and refuses a set that does + // not match the route scope exactly, so adding a route necessarily moves this + // digest — that is the point of pinning it. + "8c73d4477392a63e91088ff91e53293e7fc3ace891f8eb3da3b0d05fa714df60"; function canonicalGateShapeSha256(gates: CiGateContract["gates"]): string { const normalized = gates.map( @@ -512,8 +517,10 @@ function canonicalAuthorityBaselineFailures(contract: CiGateContract): string[] // Template merge. 126 product artifacts plus the two the template added. // Task 11 added one more: the TECH_LOG_STUDIO_ASSETS manual a11y evidence file. // Alignment follow-up, item 2 added the TechLog junit report. - if (contract.artifacts.length !== 130) { - failures.push(`artifact authority baseline must contain exactly 130 artifacts; received ${contract.artifacts.length}`); + // The taxonomy route added its own manual a11y evidence file — every installed + // route carries one, and the gate checks that the two sets match exactly. + if (contract.artifacts.length !== 131) { + failures.push(`artifact authority baseline must contain exactly 131 artifacts; received ${contract.artifacts.length}`); } if (contract.stages.length !== 5) { failures.push(`stage authority baseline must contain exactly 5 stages; received ${contract.stages.length}`); diff --git a/scripts/generate-tech-log-contract.ts b/scripts/generate-tech-log-contract.ts index 02ce433..30070c6 100644 --- a/scripts/generate-tech-log-contract.ts +++ b/scripts/generate-tech-log-contract.ts @@ -50,6 +50,14 @@ const CONTRACTS: readonly ContractTarget[] = Object.freeze([ generated: "src/features/tech-log/contracts/public/generated.ts", sourceRecord: "src/features/tech-log/contracts/public/canonical-source.json", }), + Object.freeze({ + name: "management", + packageId: "@tech-log/management-contract", + canonicalYaml: `${CANONICAL_ROOT}/contracts/openapi/studio-management-v1.yaml`, + vendorYaml: "src/features/tech-log/contracts/management/management-api.openapi.yaml", + generated: "src/features/tech-log/contracts/management/generated.ts", + sourceRecord: "src/features/tech-log/contracts/management/canonical-source.json", + }), ]); const OPENAPI_TYPESCRIPT = "openapi-typescript@7.9.1"; diff --git a/scripts/lib/tech-log-serving-contract.ts b/scripts/lib/tech-log-serving-contract.ts index 9c5c506..caa7bf8 100644 --- a/scripts/lib/tech-log-serving-contract.ts +++ b/scripts/lib/tech-log-serving-contract.ts @@ -56,6 +56,7 @@ const studioSpaPathPatterns = Object.freeze([ // reload of /studio/assets was served the in-shell Studio 404 -- the screen // was only reachable by client-side navigation from another Studio page. "^/studio/assets$", + "^/studio/taxonomy$", "^/studio/documents$", "^/studio/documents/new$", "^/studio/documents/[^/]+/(edit|validation|preview|publish)$", diff --git a/src/features/installed-contract-contributions.ts b/src/features/installed-contract-contributions.ts index 448d646..753abd9 100644 --- a/src/features/installed-contract-contributions.ts +++ b/src/features/installed-contract-contributions.ts @@ -6,6 +6,7 @@ import { import { REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION } from "./reference-feature/contracts/reference-feature-contract-contribution.ts"; import { REFERENCE_FEATURE_ID } from "./reference-feature/contracts/reference-feature-contract.ts"; import { INSTALLED_PRODUCT_FEATURE_IDS } from "./installed-product-manifest.ts"; +import { TECH_LOG_MANAGEMENT_CONTRIBUTION } from "./tech-log/contracts/tech-log-management-contract-contribution.ts"; import { TECH_LOG_PUBLIC_CONTRIBUTION } from "./tech-log/contracts/tech-log-public-contract-contribution.ts"; import { TECH_LOG_STUDIO_CONTRIBUTION } from "./tech-log/contracts/tech-log-studio-contract-contribution.ts"; @@ -23,8 +24,13 @@ export const INSTALLED_CONTRACT_CONTRIBUTIONS: readonly InstalledContractContrib REFERENCE_FEATURE_TEMPLATE_CONTRIBUTION, TECH_LOG_STUDIO_CONTRIBUTION, TECH_LOG_PUBLIC_CONTRIBUTION, + TECH_LOG_MANAGEMENT_CONTRIBUTION, ] - : [TECH_LOG_STUDIO_CONTRIBUTION, TECH_LOG_PUBLIC_CONTRIBUTION], + : [ + TECH_LOG_STUDIO_CONTRIBUTION, + TECH_LOG_PUBLIC_CONTRIBUTION, + TECH_LOG_MANAGEMENT_CONTRIBUTION, + ], ); export const COMPOSED_CONTRACT_CONTRIBUTIONS = composeContractContributions( diff --git a/src/features/tech-log/adapters/create-tech-log-feature-input.ts b/src/features/tech-log/adapters/create-tech-log-feature-input.ts index 416506f..194670e 100644 --- a/src/features/tech-log/adapters/create-tech-log-feature-input.ts +++ b/src/features/tech-log/adapters/create-tech-log-feature-input.ts @@ -12,6 +12,7 @@ import { import type { CsrfTokenProvider } from "./http/studio-session-csrf.ts"; import { createMockStudioAssetGateway } from "./mock/mock-studio-asset-gateway.ts"; import { createMockStudioGateway } from "./mock/mock-studio-gateway.ts"; +import { createHttpManagementGateway } from "./http/http-management-gateway.ts"; import { createHttpPublicContentGateway } from "./http/http-public-content-gateway.ts"; import { publicContentQueries } from "./static/public-query.ts"; @@ -76,8 +77,12 @@ export function createTechLogFeatureInstalledInput( ? publicContentQueries : createHttpPublicContentGateway({ operations: context.contractOperations }); + const createManagementGateway = () => + createHttpManagementGateway({ operations: context.contractOperations }); + const input: TechLogFeatureInput = Object.freeze({ publicContent, + createManagementGateway, createStudioGateway, createStudioAssetGateway, }); diff --git a/src/features/tech-log/adapters/http/http-management-gateway.ts b/src/features/tech-log/adapters/http/http-management-gateway.ts new file mode 100644 index 0000000..9f62f14 --- /dev/null +++ b/src/features/tech-log/adapters/http/http-management-gateway.ts @@ -0,0 +1,64 @@ +import type { + CreateDraftResponse, + ProjectEditResponse, + ProjectIndexPage, + ProjectUpdateRequest, + TopicEdit, +} from "../../contracts/management/contract.ts"; +import type { ManagementGateway } from "../../application/ports/management-gateway.ts"; +import type { StudioOperationExecutor } from "./http-studio-gateway.ts"; + +export type { ManagementGateway }; + +const ROUTE_ID = "TECH_LOG_STUDIO"; + +/** + * 주제·프로젝트 관리 게이트웨이. + * + *

Studio 게이트웨이와 같은 실패 규약을 쓴다 — 실패는 던지고, 화면은 `usePublicContent` 가 아니라 + * Studio 쪽 상태 처리를 그대로 쓴다. 여기서 Result 로 감싸면 이 표면만 다른 규약이 된다. + */ + +export class ManagementGatewayError extends Error { + readonly operationId: string; + readonly code: string; + + constructor(operationId: string, code: string) { + super(`${operationId}: ${code}`); + this.name = "ManagementGatewayError"; + this.operationId = operationId; + this.code = code; + } +} + +export function createHttpManagementGateway( + deps: Readonly<{ operations: StudioOperationExecutor }>, +): ManagementGateway { + async function run(operationId: string, input: unknown): Promise { + const outcome = await deps.operations.execute(operationId, input, { routeId: ROUTE_ID }); + if (outcome.kind === "SUCCESS") return outcome.value as T; + if (outcome.kind === "PROBLEM") { + const problem = outcome.problem as Readonly<{ code?: string }> | null; + throw new ManagementGatewayError(operationId, problem?.code ?? "PROBLEM"); + } + throw new ManagementGatewayError(operationId, outcome.kind); + } + + return Object.freeze({ + listTopics: () => run("listStudioTopics", {}), + createTopic: (input: TopicEdit) => run("createTopic", input), + updateTopic: (id: string, body: TopicEdit) => run("updateTopic", { id, body }), + deleteTopic: async (id: string, expectedVersion: number) => { + await run("deleteTopic", { id, expectedVersion }); + }, + listProjects: (page?: number, size?: number) => + run("listStudioProjects", { ...(page !== undefined ? { page } : {}), ...(size !== undefined ? { size } : {}) }), + getProject: (id: string) => run("getProjectForEdit", { id }), + createProject: (title: string) => run("createProject", { title }), + updateProject: (id: string, body: ProjectUpdateRequest) => + run("updateProject", { id, body }), + deleteProject: async (id: string, expectedVersion: number) => { + await run("deleteProject", { id, expectedVersion }); + }, + }); +} diff --git a/src/features/tech-log/application/ports/management-gateway.ts b/src/features/tech-log/application/ports/management-gateway.ts new file mode 100644 index 0000000..e0a7057 --- /dev/null +++ b/src/features/tech-log/application/ports/management-gateway.ts @@ -0,0 +1,26 @@ +import type { + CreateDraftResponse, + ProjectEditResponse, + ProjectIndexPage, + ProjectUpdateRequest, + TopicEdit, +} from "../../contracts/management/contract.ts"; + +/** + * 주제·프로젝트 관리 표면. + * + *

Studio 게이트웨이와 같은 실패 규약이다 — 실패는 던지고, 화면이 잡는다. MOCK 대응물을 두지 + * 않는 것도 의도다: 이 표면은 백엔드가 없으면 존재할 이유가 없고, 픽스처를 만들면 실제로는 만들 + * 수 없는 주제를 화면이 보여주게 된다. + */ +export type ManagementGateway = Readonly<{ + listTopics(): Promise; + createTopic(input: TopicEdit): Promise; + updateTopic(id: string, body: TopicEdit): Promise; + deleteTopic(id: string, expectedVersion: number): Promise; + listProjects(page?: number, size?: number): Promise; + getProject(id: string): Promise; + createProject(title: string): Promise; + updateProject(id: string, body: ProjectUpdateRequest): Promise; + deleteProject(id: string, expectedVersion: number): Promise; +}>; diff --git a/src/features/tech-log/application/tech-log-feature-input.ts b/src/features/tech-log/application/tech-log-feature-input.ts index 8f150f1..fadb72c 100644 --- a/src/features/tech-log/application/tech-log-feature-input.ts +++ b/src/features/tech-log/application/tech-log-feature-input.ts @@ -1,5 +1,6 @@ import type { PublicContentQueries } from "./ports/public-content-queries.ts"; import type { StudioAssetGateway } from "./ports/studio-asset-gateway.ts"; +import type { ManagementGateway } from "./ports/management-gateway.ts"; import type { StudioGateway } from "./ports/studio-gateway.ts"; export const TECH_LOG_FEATURE_ID = "tech-log" as const; @@ -8,6 +9,9 @@ export type TechLogFeatureInput = Readonly<{ publicContent: PublicContentQueries; createStudioGateway(): StudioGateway; createStudioAssetGateway(): StudioAssetGateway; + // 주제·프로젝트 관리. MOCK 대응물이 없다 — 이 표면은 백엔드가 없으면 존재할 이유가 없고, + // 픽스처를 만들면 실제로는 못 만드는 주제를 화면이 보여주게 된다. + createManagementGateway(): ManagementGateway; }>; declare module "../../../application/ports/in/application-api.ts" { diff --git a/src/features/tech-log/contracts/management/canonical-source.json b/src/features/tech-log/contracts/management/canonical-source.json new file mode 100644 index 0000000..bf05689 --- /dev/null +++ b/src/features/tech-log/contracts/management/canonical-source.json @@ -0,0 +1,87 @@ +{ + "packageId": "@tech-log/management-contract", + "version": "1.0.0", + "digest": "sha256:ec5e432215fb041abee980787366a6db29ff1ecdd78416aa9c61e09b9b91022f", + "sourceRevision": "6ef5c1c", + "operationIds": [ + "createCaseDraft", + "getCaseForEdit", + "updateCaseDraft", + "deleteCaseDraft", + "createReferenceDraft", + "getReferenceForEdit", + "updateReferenceDraft", + "deleteReferenceDraft", + "validateCase", + "submitReviewCase", + "returnToDraftCase", + "unpublishCase", + "archiveCase", + "restoreCase", + "publishCase", + "validateReference", + "submitReviewReference", + "returnToDraftReference", + "unpublishReference", + "archiveReference", + "restoreReference", + "publishReference", + "createQuestion", + "listStudioQuestions", + "getQuestionForEdit", + "updateQuestion", + "deleteQuestion", + "addQuestionUpdate", + "updateQuestionUpdate", + "deleteQuestionUpdate", + "resolveQuestion", + "startQuestionInvestigation", + "pauseQuestion", + "resumeQuestion", + "reopenQuestion", + "archiveQuestion", + "publishQuestion", + "unpublishQuestion", + "createProject", + "listStudioProjects", + "getProjectForEdit", + "updateProject", + "deleteProject", + "changeProjectPhase", + "publishProject", + "unpublishProject", + "createProjectDecision", + "listStudioProjectDecisions", + "getProjectDecision", + "updateProjectDecision", + "acceptProjectDecision", + "rejectProjectDecision", + "supersedeProjectDecision", + "createRelease", + "listStudioReleases", + "getReleaseForEdit", + "updateRelease", + "deleteRelease", + "publishRelease", + "archiveRelease", + "listStudioTopics", + "createTopic", + "updateTopic", + "deleteTopic", + "listStudioTags", + "createTag", + "updateTag", + "deleteTag", + "getStudioSite", + "updateStudioSite", + "getStudioProfile", + "updateStudioProfile", + "publishProfile", + "unpublishProfile", + "getHomeFocus", + "updateHomeFocus", + "listStudioProjectActivities", + "createProjectActivity", + "updateProjectActivity" + ] +} diff --git a/src/features/tech-log/contracts/management/contract.ts b/src/features/tech-log/contracts/management/contract.ts new file mode 100644 index 0000000..4984668 --- /dev/null +++ b/src/features/tech-log/contracts/management/contract.ts @@ -0,0 +1,12 @@ +import type { components } from "./generated.ts"; + +type Schemas = components["schemas"]; + +export type TopicEdit = Schemas["TopicEdit"]; +export type ProjectEditResponse = Schemas["ProjectEditResponse"]; +export type ProjectIndexItem = Schemas["ProjectIndexItem"]; +export type ProjectIndexPage = Schemas["ProjectIndexPage"]; +export type ProjectUpdateRequest = Schemas["ProjectUpdateRequest"]; +export type CreateDraftRequest = Schemas["CreateDraftRequest"]; +export type CreateDraftResponse = Schemas["CreateDraftResponse"]; +export type ExpectedVersionRequest = Schemas["ExpectedVersionRequest"]; diff --git a/src/features/tech-log/contracts/management/generated.ts b/src/features/tech-log/contracts/management/generated.ts new file mode 100644 index 0000000..a078080 --- /dev/null +++ b/src/features/tech-log/contracts/management/generated.ts @@ -0,0 +1,8387 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/api/v1/studio/cases": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["createCaseDraft"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/cases/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getCaseForEdit"]; + put: operations["updateCaseDraft"]; + post?: never; + delete: operations["deleteCaseDraft"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["createReferenceDraft"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getReferenceForEdit"]; + put: operations["updateReferenceDraft"]; + post?: never; + delete: operations["deleteReferenceDraft"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/cases/{id}/validate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["validateCase"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/cases/{id}/submit-review": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["submitReviewCase"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/cases/{id}/return-to-draft": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["returnToDraftCase"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/cases/{id}/unpublish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["unpublishCase"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/cases/{id}/archive": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["archiveCase"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/cases/{id}/restore": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["restoreCase"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/cases/{id}/publish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["publishCase"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references/{id}/validate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["validateReference"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references/{id}/submit-review": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["submitReviewReference"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references/{id}/return-to-draft": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["returnToDraftReference"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references/{id}/unpublish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["unpublishReference"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references/{id}/archive": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["archiveReference"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references/{id}/restore": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["restoreReference"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/references/{id}/publish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["publishReference"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listStudioQuestions"]; + put?: never; + post: operations["createQuestion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getQuestionForEdit"]; + put: operations["updateQuestion"]; + post?: never; + delete: operations["deleteQuestion"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/updates": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["addQuestionUpdate"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/updates/{updateId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put: operations["updateQuestionUpdate"]; + post?: never; + delete: operations["deleteQuestionUpdate"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/resolve": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["resolveQuestion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/start-investigation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["startQuestionInvestigation"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/pause": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["pauseQuestion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/resume": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["resumeQuestion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/reopen": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["reopenQuestion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/archive": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["archiveQuestion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/publish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["publishQuestion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/questions/{id}/unpublish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["unpublishQuestion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listStudioProjects"]; + put?: never; + post: operations["createProject"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getProjectForEdit"]; + put: operations["updateProject"]; + post?: never; + delete: operations["deleteProject"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/change-phase": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["changeProjectPhase"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/publish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["publishProject"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/unpublish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["unpublishProject"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/decisions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listStudioProjectDecisions"]; + put?: never; + post: operations["createProjectDecision"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/decisions/{decisionId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getProjectDecision"]; + put: operations["updateProjectDecision"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/decisions/{decisionId}/accept": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["acceptProjectDecision"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/decisions/{decisionId}/reject": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["rejectProjectDecision"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/decisions/{decisionId}/supersede": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["supersedeProjectDecision"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/releases": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listStudioReleases"]; + put?: never; + post: operations["createRelease"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/releases/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getReleaseForEdit"]; + put: operations["updateRelease"]; + post?: never; + delete: operations["deleteRelease"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/releases/{id}/publish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["publishRelease"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/releases/{id}/archive": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["archiveRelease"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/topics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listStudioTopics"]; + put?: never; + post: operations["createTopic"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/topics/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put: operations["updateTopic"]; + post?: never; + delete: operations["deleteTopic"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/tags": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listStudioTags"]; + put?: never; + post: operations["createTag"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/tags/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put: operations["updateTag"]; + post?: never; + delete: operations["deleteTag"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/site": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getStudioSite"]; + put: operations["updateStudioSite"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/profile": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getStudioProfile"]; + put: operations["updateStudioProfile"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/profile/publish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["publishProfile"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/profile/unpublish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["unpublishProfile"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/home-focus": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getHomeFocus"]; + put: operations["updateHomeFocus"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/activities": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listStudioProjectActivities"]; + put?: never; + post: operations["createProjectActivity"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/studio/projects/{id}/activities/{activityId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put: operations["updateProjectActivity"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + ResponseMeta: { + requestId: string; + traceId: string; + correlationId?: string | null; + /** @description 백엔드 템플릿의 ResponseMeta record 가 직렬화하는 자리다. 페이지 정보는 payload 의 `page` 가 소유하므로 이 관리 표면에서는 항상 null 이다. */ + page?: { + [key: string]: unknown; + } | null; + }; + ApiError: { + /** + * @description `INTERNAL_ERROR` 는 이 기능이 아니라 스켈레톤의 공통 처리기가 내는 코드다. 계약이 그것까지 열거해야 500 응답이 계약을 벗어나지 않는다. + * @enum {string} + */ + code: "AUTHENTICATION_REQUIRED" | "STUDIO_ACCESS_DENIED" | "REQUEST_VALIDATION_FAILED" | "VERSION_CONFLICT" | "TOPIC_NOT_FOUND" | "TOPIC_NAME_TAKEN" | "TOPIC_SLUG_TAKEN" | "TOPIC_IN_USE" | "PROJECT_NOT_FOUND" | "PROJECT_SLUG_TAKEN" | "PROJECT_IN_USE" | "INTERNAL_ERROR"; + /** @enum {string} */ + category: "VALIDATION" | "AUTH" | "AUTHZ" | "NOT_FOUND" | "CONFLICT" | "RATE_LIMIT" | "TRANSIENT_DEPENDENCY" | "PERMANENT_DEPENDENCY" | "DATA_INTEGRITY" | "INTERNAL"; + message: string; + retryable: boolean; + details?: components["schemas"]["ValidationErrorDetails"] | components["schemas"]["VersionConflictDetails"] | null; + }; + ValidationErrorDetails: { + fieldErrors: components["schemas"]["FieldError"][]; + }; + VersionConflictDetails: { + /** Format: int64 */ + currentVersion: number; + }; + ErrorEnvelope: { + /** @constant */ + success: false; + error: components["schemas"]["ApiError"]; + meta: components["schemas"]["ResponseMeta"]; + }; + TopicEditEnvelope: { + /** @constant */ + success: true; + data: components["schemas"]["TopicEdit"]; + meta: components["schemas"]["ResponseMeta"]; + }; + TopicEditListEnvelope: { + /** @constant */ + success: true; + data: components["schemas"]["TopicEdit"][]; + meta: components["schemas"]["ResponseMeta"]; + }; + CreateDraftResponseEnvelope: { + /** @constant */ + success: true; + data: components["schemas"]["CreateDraftResponse"]; + meta: components["schemas"]["ResponseMeta"]; + }; + ProjectIndexPageEnvelope: { + /** @constant */ + success: true; + data: components["schemas"]["ProjectIndexPage"]; + meta: components["schemas"]["ResponseMeta"]; + }; + ProjectEditResponseEnvelope: { + /** @constant */ + success: true; + data: components["schemas"]["ProjectEditResponse"]; + meta: components["schemas"]["ResponseMeta"]; + }; + FieldError: { + field: string; + code: string; + message: string; + }; + ProblemDetails: { + type: string; + title: string; + status: number; + code: string; + detail: string; + instance: string; + traceId: string; + fieldErrors?: components["schemas"]["FieldError"][]; + /** Format: int64 */ + currentVersion?: number; + }; + PageMetadata: { + number: number; + size: number; + /** Format: int64 */ + totalElements: number; + totalPages: number; + hasPrevious: boolean; + hasNext: boolean; + }; + TagSummary: { + name: string; + slug: string; + }; + ProjectSummary: { + name: string; + slug: string; + path: string; + }; + AssetReference: { + /** Format: uuid */ + assetId: string; + url: string; + altText?: string; + width?: number; + height?: number; + contentType?: string; + }; + RelatedEntry: { + /** @enum {string} */ + type: "CASE" | "REFERENCE" | "QUESTION" | "PROJECT" | "PROJECT_DECISION" | "RELEASE"; + title: string; + summary?: string; + path: string; + }; + PublicationStatus: { + /** @enum {string} */ + state: "NEVER_PUBLISHED" | "ACTIVE" | "WITHDRAWN"; + /** Format: int64 */ + publishedSourceVersion?: number; + hasUnpublishedChanges: boolean; + canonicalPath?: string; + /** Format: date-time */ + publishedAt?: string; + }; + ValidationIssue: { + /** @enum {string} */ + severity: "ERROR" | "WARNING"; + code: string; + field?: string; + message: string; + }; + ValidationResponse: { + validForSave: boolean; + validForPublish: boolean; + issues: components["schemas"]["ValidationIssue"][]; + }; + CreateDraftRequest: { + title: string; + }; + CreateDraftResponse: { + /** Format: uuid */ + id: string; + /** @enum {string} */ + status: "DRAFT"; + /** Format: int64 */ + version: number; + /** Format: date-time */ + createdAt: string; + }; + RelationEdit: { + /** Format: uuid */ + primaryProjectId?: string; + relatedProjectIds?: string[]; + /** Format: uuid */ + originQuestionId?: string; + relatedDocumentIds?: string[]; + derivedReferenceIds?: string[]; + }; + CaseUpdateRequest: { + /** Format: int64 */ + expectedVersion: number; + title: string; + slug?: string; + problemSummary: string; + conclusionSummary: string; + environmentSummary?: string[]; + content: string; + contentFormatVersion: number; + /** Format: uuid */ + primaryTopicId?: string; + tagIds: string[]; + relations: components["schemas"]["RelationEdit"]; + /** Format: uuid */ + coverAssetId?: string; + /** Format: date-time */ + lastVerifiedAt?: string; + /** @enum {string} */ + targetVisibility?: "PRIVATE" | "UNLISTED" | "PUBLIC"; + }; + CaseEditResponse: { + /** Format: uuid */ + id: string; + /** Format: int64 */ + version: number; + title: string; + slug?: string; + problemSummary: string; + conclusionSummary: string; + environmentSummary?: string[]; + content: string; + contentFormatVersion: number; + /** Format: uuid */ + primaryTopicId?: string; + tagIds: string[]; + relations: components["schemas"]["RelationEdit"]; + /** Format: uuid */ + coverAssetId?: string; + /** Format: date-time */ + lastVerifiedAt?: string; + workflowStatus: string; + targetVisibility: string; + publication: components["schemas"]["PublicationStatus"]; + /** Format: date-time */ + updatedAt: string; + }; + ReferenceUpdateRequest: { + /** Format: int64 */ + expectedVersion: number; + title: string; + slug?: string; + scopeSummary: string; + appliesTo: string[]; + excludedScope: string[]; + /** @enum {string} */ + freshnessStatus: "CURRENT" | "REVIEW_DUE" | "HISTORICAL"; + content: string; + contentFormatVersion: number; + /** Format: uuid */ + primaryTopicId?: string; + tagIds: string[]; + relations: components["schemas"]["RelationEdit"]; + /** Format: uuid */ + coverAssetId?: string; + /** Format: date-time */ + lastVerifiedAt?: string; + /** @enum {string} */ + targetVisibility?: "PRIVATE" | "UNLISTED" | "PUBLIC"; + }; + ReferenceEditResponse: { + /** Format: uuid */ + id: string; + /** Format: int64 */ + version: number; + title: string; + slug?: string; + scopeSummary: string; + appliesTo: string[]; + excludedScope: string[]; + freshnessStatus: string; + content: string; + contentFormatVersion: number; + /** Format: uuid */ + primaryTopicId?: string; + tagIds: string[]; + relations: components["schemas"]["RelationEdit"]; + /** Format: uuid */ + coverAssetId?: string; + /** Format: date-time */ + lastVerifiedAt?: string; + workflowStatus: string; + targetVisibility: string; + publication: components["schemas"]["PublicationStatus"]; + /** Format: date-time */ + updatedAt: string; + }; + ExpectedVersionRequest: { + /** Format: int64 */ + expectedVersion: number; + }; + PublishRequest: { + /** Format: int64 */ + expectedVersion: number; + /** @enum {string} */ + visibility: "PUBLIC" | "UNLISTED"; + }; + PublishResponse: { + /** Format: uuid */ + id: string; + /** @enum {string} */ + status: "PUBLISHED"; + /** @enum {string} */ + visibility: "PUBLIC" | "UNLISTED"; + canonicalPath: string; + /** Format: date-time */ + publishedAt: string; + /** Format: int64 */ + version: number; + }; + QuestionPointEdit: { + /** Format: uuid */ + id?: string; + /** @enum {string} */ + kind: "FACT" | "ASSUMPTION" | "UNKNOWN" | "CONSTRAINT"; + content: string; + displayOrder: number; + }; + QuestionUpdateRequest: { + /** @enum {string} */ + type: "OBSERVATION" | "EVIDENCE" | "SCOPE_CHANGE" | "BLOCKER" | "NEXT_STEP" | "RESOLUTION" | "RESOLUTION_REOPENED"; + title: string; + bodyMarkdown: string; + /** @enum {string} */ + visibility: "PRIVATE" | "PUBLIC"; + /** Format: date-time */ + occurredAt: string; + /** Format: int64 */ + expectedVersion: number; + }; + QuestionUpdateResponse: { + /** Format: uuid */ + id: string; + /** Format: int64 */ + questionVersion: number; + type: string; + title: string; + bodyMarkdown: string; + visibility: string; + /** Format: date-time */ + occurredAt: string; + }; + QuestionUpdateBody: { + /** Format: int64 */ + expectedVersion: number; + question: string; + slug?: string; + summary: string; + context: string; + importance: string; + nextVerification?: string; + /** @enum {string} */ + targetVisibility?: "PRIVATE" | "UNLISTED" | "PUBLIC"; + /** Format: uuid */ + primaryProjectId?: string; + /** Format: uuid */ + topicId?: string; + tagIds: string[]; + points: components["schemas"]["QuestionPointEdit"][]; + }; + QuestionEditResponse: { + /** Format: uuid */ + id: string; + /** Format: int64 */ + version: number; + question: string; + slug?: string; + summary?: string; + context?: string; + importance?: string; + nextVerification?: string; + status: string; + targetVisibility: string; + /** Format: uuid */ + primaryProjectId?: string; + /** Format: uuid */ + topicId?: string; + tagIds?: string[]; + points: components["schemas"]["QuestionPointEdit"][]; + updates: components["schemas"]["QuestionUpdateResponse"][]; + resolution?: { + type?: string; + summary?: string; + /** Format: date-time */ + resolvedAt?: string; + }; + publication: components["schemas"]["PublicationStatus"]; + /** Format: date-time */ + updatedAt: string; + }; + ResolveQuestionRequest: { + /** Format: int64 */ + expectedVersion: number; + /** @enum {string} */ + resolutionType: "DECISION_MADE" | "ASSUMPTION_REJECTED" | "QUESTION_REFRAMED" | "NO_LONGER_RELEVANT"; + resolutionSummary: string; + /** Format: date-time */ + resolvedAt: string; + createCaseDraft: boolean; + createProjectDecision: boolean; + }; + ResolveQuestionResponse: { + /** Format: uuid */ + questionId: string; + /** @enum {string} */ + status: "RESOLVED"; + /** Format: int64 */ + version: number; + /** Format: uuid */ + createdCaseDraftId?: string; + /** Format: uuid */ + createdProjectDecisionId?: string; + }; + ProjectUpdateRequest: { + /** Format: int64 */ + expectedVersion: number; + name: string; + slug?: string; + oneLinePurpose: string; + purposeMarkdown: string; + boundaryMarkdown: string; + systemOverviewMarkdown?: string; + phase: string; + currentObjective?: string; + nextStep?: string; + technologyLabels: string[]; + /** @enum {string} */ + targetVisibility: "PRIVATE" | "UNLISTED" | "PUBLIC"; + featuredOrder?: number; + topicIds?: string[]; + documentLinks?: components["schemas"]["ProjectResourceLinkEdit"][]; + questionLinks?: components["schemas"]["ProjectResourceLinkEdit"][]; + }; + ProjectEditResponse: { + /** Format: uuid */ + id: string; + /** Format: int64 */ + version: number; + name: string; + slug?: string; + oneLinePurpose?: string; + purposeMarkdown?: string; + boundaryMarkdown?: string; + systemOverviewMarkdown?: string; + phase: string; + currentObjective?: string; + nextStep?: string; + technologyLabels?: string[]; + workflowStatus: string; + targetVisibility: string; + featuredOrder?: number; + publication: components["schemas"]["PublicationStatus"]; + /** Format: date-time */ + updatedAt: string; + topicIds?: string[]; + documentLinks?: components["schemas"]["ProjectResourceLinkEdit"][]; + questionLinks?: components["schemas"]["ProjectResourceLinkEdit"][]; + }; + ChangeProjectPhaseRequest: { + /** Format: int64 */ + expectedVersion: number; + phase: string; + reason: string; + publishActivity: boolean; + }; + DecisionUpdateRequest: { + /** Format: int64 */ + expectedVersion: number; + statement: string; + rationaleMarkdown: string; + consequences: string[]; + alternativesMarkdown: string; + /** @enum {string} */ + targetVisibility: "PRIVATE" | "PUBLIC"; + /** Format: uuid */ + sourceQuestionId?: string; + /** Format: uuid */ + sourceCaseId?: string; + isFeatured?: boolean; + /** Format: date-time */ + decidedAt?: string; + }; + DecisionResponse: components["schemas"]["DecisionUpdateRequest"] & { + /** Format: uuid */ + id: string; + /** Format: uuid */ + projectId: string; + status: string; + /** Format: int64 */ + version: number; + /** Format: uuid */ + supersededById?: string; + }; + ReleaseUpdateRequest: { + /** Format: int64 */ + expectedVersion: number; + versionLabel: string; + title: string; + summary: string; + /** Format: date */ + releasedOn?: string; + changeTypes: string[]; + reasonMarkdown?: string; + changesMarkdown: string; + userImpactMarkdown?: string; + implementationImpactMarkdown?: string; + verificationMarkdown: string; + knownLimitationsMarkdown?: string; + relatedResources?: { + resourceType: string; + /** Format: uuid */ + resourceId: string; + }[]; + }; + ReleaseEditResponse: components["schemas"]["ReleaseUpdateRequest"] & { + /** Format: uuid */ + id: string; + workflowStatus: string; + /** Format: int64 */ + version: number; + publication: components["schemas"]["PublicationStatus"]; + }; + TopicEdit: { + /** + * Format: uuid + * @description 응답에만 실린다. 경로가 `/topics/{id}`이므로 이 값이 없으면 목록에서 어떤 주제도 + * 주소지정할 수 없다 — ProjectEditResponse / CaseEditResponse는 같은 이유로 이미 + * id를 싣고 있었고, TopicEdit만 빠져 있었다. 쓰기 요청에 와도 서버는 무시한다; + * 수정 대상은 경로가 소유한다. + */ + id?: string; + /** + * Format: int64 + * @description 응답에만 실린다. 다음 수정에서 보낼 `expectedVersion`의 출처다. 이것이 없으면 + * 클라이언트가 낙관적 잠금에 쓸 값을 얻을 방법이 없다. + */ + version?: number; + name: string; + slug: string; + description?: string; + scope?: string; + /** @enum {string} */ + status?: "ACTIVE" | "ARCHIVED"; + /** Format: int64 */ + expectedVersion?: number; + /** Format: uuid */ + featuredReferenceId?: string; + featuredCaseIds?: string[]; + }; + TagEdit: { + name: string; + slug: string; + /** Format: int64 */ + expectedVersion?: number; + }; + SiteConfigRequest: { + /** Format: int64 */ + expectedVersion: number; + brandTitle: string; + identityStatement: string; + operatorDisplayName: string; + shortIdentity?: string; + /** Format: uuid */ + avatarAssetId?: string; + contacts: Record[]; + }; + ProfileEditRequest: { + /** Format: int64 */ + expectedVersion: number; + headline: string; + introductionMarkdown?: string; + workingModel: Record[]; + territories: Record[]; + selectedEvidence: Record[]; + trajectory: Record[]; + contacts: Record[]; + /** @enum {string} */ + targetVisibility: "PRIVATE" | "PUBLIC"; + }; + HomeFocusRequest: { + /** Format: int64 */ + expectedVersion: number; + /** @enum {string} */ + defaultType?: "CURRENT_WORK" | "OPEN_QUESTION" | "RECENT_DECISION"; + /** Format: uuid */ + currentProjectId?: string; + /** Format: uuid */ + openQuestionId?: string; + /** Format: uuid */ + recentDecisionId?: string; + }; + QuestionIndexItem: { + /** Format: uuid */ + id: string; + question: string; + status: string; + targetVisibility: string; + primaryProject?: components["schemas"]["ProjectSummary"]; + nextVerification?: string; + /** Format: date-time */ + updatedAt: string; + /** Format: int64 */ + version: number; + publication: components["schemas"]["PublicationStatus"]; + }; + QuestionIndexPage: { + items: components["schemas"]["QuestionIndexItem"][]; + page: components["schemas"]["PageMetadata"]; + }; + ProjectIndexItem: { + /** Format: uuid */ + id: string; + name: string; + phase: string; + workflowStatus: string; + targetVisibility: string; + currentObjective?: string; + nextStep?: string; + /** Format: date-time */ + updatedAt: string; + /** Format: int64 */ + version: number; + publication: components["schemas"]["PublicationStatus"]; + }; + ProjectIndexPage: { + items: components["schemas"]["ProjectIndexItem"][]; + page: components["schemas"]["PageMetadata"]; + }; + ReleaseIndexItem: { + /** Format: uuid */ + id: string; + versionLabel: string; + title: string; + /** Format: date */ + releasedOn?: string; + workflowStatus: string; + /** Format: date-time */ + updatedAt: string; + /** Format: int64 */ + version: number; + publication: components["schemas"]["PublicationStatus"]; + }; + ReleaseIndexPage: { + items: components["schemas"]["ReleaseIndexItem"][]; + page: components["schemas"]["PageMetadata"]; + }; + SiteConfigResponse: { + /** Format: uuid */ + id: string; + /** Format: int64 */ + version: number; + brandTitle: string; + identityStatement: string; + operatorDisplayName: string; + shortIdentity?: string; + /** Format: uuid */ + avatarAssetId?: string; + contacts: Record[]; + }; + ProfileEditResponse: { + /** Format: uuid */ + id: string; + /** Format: int64 */ + version: number; + headline: string; + introductionMarkdown?: string; + workingModel: Record[]; + territories: Record[]; + selectedEvidence: Record[]; + trajectory: Record[]; + contacts: Record[]; + targetVisibility: string; + publication: components["schemas"]["PublicationStatus"]; + }; + HomeFocusResponse: { + /** Format: uuid */ + id: string; + /** Format: int64 */ + version: number; + defaultType?: string; + /** Format: uuid */ + currentProjectId?: string; + /** Format: uuid */ + openQuestionId?: string; + /** Format: uuid */ + recentDecisionId?: string; + }; + ProjectResourceLinkEdit: { + /** Format: uuid */ + resourceId: string; + /** @enum {string} */ + relationType: "PRIMARY" | "RELATED"; + featuredOrder?: number; + }; + CreateDecisionRequest: { + /** Format: int64 */ + expectedProjectVersion: number; + statement: string; + rationaleMarkdown: string; + consequences: string[]; + alternativesMarkdown: string; + /** @enum {string} */ + targetVisibility: "PRIVATE" | "PUBLIC"; + /** Format: uuid */ + sourceQuestionId?: string; + /** Format: uuid */ + sourceCaseId?: string; + isFeatured?: boolean; + /** Format: date-time */ + decidedAt?: string; + }; + SupersedeDecisionRequest: { + /** Format: int64 */ + expectedVersion: number; + /** Format: uuid */ + supersededById: string; + }; + ProjectActivityRequest: { + /** Format: int64 */ + expectedProjectVersion: number; + activityType: string; + title: string; + summary?: string; + /** @enum {string} */ + visibility: "PRIVATE" | "PUBLIC"; + relatedResourceType?: string; + /** Format: uuid */ + relatedResourceId?: string; + /** Format: date-time */ + occurredAt: string; + }; + ProjectActivityResponse: { + /** Format: uuid */ + id: string; + /** Format: uuid */ + projectId: string; + activityType: string; + title: string; + summary?: string; + visibility: string; + origin: string; + relatedResourceType?: string; + /** Format: uuid */ + relatedResourceId?: string; + /** Format: date-time */ + occurredAt: string; + /** Format: int64 */ + version: number; + }; + UpdateProjectActivityRequest: { + /** Format: int64 */ + expectedVersion: number; + title: string; + summary?: string; + /** @enum {string} */ + visibility: "PRIVATE" | "PUBLIC"; + /** Format: date-time */ + occurredAt: string; + }; + }; + responses: never; + parameters: { + CsrfToken: string; + }; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + createCaseDraft: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateDraftRequest"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreateDraftResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + getCaseForEdit: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateCaseDraft: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CaseUpdateRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + deleteCaseDraft: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + createReferenceDraft: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateDraftRequest"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreateDraftResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + getReferenceForEdit: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReferenceEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateReferenceDraft: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ReferenceUpdateRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReferenceEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + deleteReferenceDraft: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + validateCase: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ValidationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + submitReviewCase: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + returnToDraftCase: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + unpublishCase: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + archiveCase: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + restoreCase: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + publishCase: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PublishRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublishResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + validateReference: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ValidationResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + submitReviewReference: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReferenceEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + returnToDraftReference: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReferenceEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + unpublishReference: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReferenceEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + archiveReference: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReferenceEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + restoreReference: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReferenceEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + publishReference: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PublishRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublishResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + listStudioQuestions: { + parameters: { + query?: { + page?: number; + size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionIndexPage"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + createQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateDraftRequest"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreateDraftResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + getQuestionForEdit: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["QuestionUpdateBody"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + deleteQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + addQuestionUpdate: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["QuestionUpdateRequest"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionUpdateResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateQuestionUpdate: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + updateId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["QuestionUpdateRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionUpdateResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + deleteQuestionUpdate: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + updateId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + resolveQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ResolveQuestionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ResolveQuestionResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + startQuestionInvestigation: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + pauseQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + resumeQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + reopenQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + archiveQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + publishQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PublishRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublishResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + unpublishQuestion: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuestionEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + listStudioProjects: { + parameters: { + query?: { + page?: number; + size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectIndexPageEnvelope"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + createProject: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateDraftRequest"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreateDraftResponseEnvelope"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + getProjectForEdit: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectEditResponseEnvelope"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + updateProject: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ProjectUpdateRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectEditResponseEnvelope"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + deleteProject: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + changeProjectPhase: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ChangeProjectPhaseRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + publishProject: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PublishRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublishResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + unpublishProject: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + listStudioProjectDecisions: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DecisionResponse"][]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + createProjectDecision: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateDecisionRequest"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DecisionResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + getProjectDecision: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + decisionId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DecisionResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateProjectDecision: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + decisionId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DecisionUpdateRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DecisionResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + acceptProjectDecision: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + decisionId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DecisionResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + rejectProjectDecision: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + decisionId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DecisionResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + supersedeProjectDecision: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + decisionId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SupersedeDecisionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DecisionResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + listStudioReleases: { + parameters: { + query?: { + page?: number; + size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReleaseIndexPage"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + createRelease: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateDraftRequest"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreateDraftResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + getReleaseForEdit: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReleaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateRelease: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ReleaseUpdateRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReleaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + deleteRelease: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + publishRelease: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublishResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + archiveRelease: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReleaseEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + listStudioTopics: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TopicEditListEnvelope"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + createTopic: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TopicEdit"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TopicEditEnvelope"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + updateTopic: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TopicEdit"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TopicEditEnvelope"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + deleteTopic: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + }; + }; + listStudioTags: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TagEdit"][]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + createTag: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TagEdit"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TagEdit"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateTag: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TagEdit"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TagEdit"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + deleteTag: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + getStudioSite: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SiteConfigResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateStudioSite: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SiteConfigRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SiteConfigResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + getStudioProfile: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProfileEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateStudioProfile: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ProfileEditRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProfileEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + publishProfile: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublishResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + unpublishProfile: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExpectedVersionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProfileEditResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + getHomeFocus: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HomeFocusResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateHomeFocus: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["HomeFocusRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HomeFocusResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Unprocessable Content */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + listStudioProjectActivities: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectActivityResponse"][]; + }; + }; + /** @description 401 */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 403 */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 404 */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 500 */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + createProjectActivity: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ProjectActivityRequest"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectActivityResponse"]; + }; + }; + /** @description 400 */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 401 */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 403 */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 404 */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 409 */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 422 */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 500 */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + updateProjectActivity: { + parameters: { + query?: never; + header: { + "X-CSRF-TOKEN": components["parameters"]["CsrfToken"]; + }; + path: { + id: string; + activityId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateProjectActivityRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectActivityResponse"]; + }; + }; + /** @description 400 */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 401 */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 403 */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 404 */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 409 */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 422 */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + /** @description 500 */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; +} diff --git a/src/features/tech-log/contracts/management/management-api.openapi.yaml b/src/features/tech-log/contracts/management/management-api.openapi.yaml new file mode 100644 index 0000000..0a65f79 --- /dev/null +++ b/src/features/tech-log/contracts/management/management-api.openapi.yaml @@ -0,0 +1,7065 @@ +openapi: 3.1.0 +info: + title: Tech Log Studio Management API (Secondary) + version: 1.0.0 + description: |- + ⚠ 봉투 결정(ADR-006) 부분 반영 — 이 파일에는 두 모양이 공존한다. + + topics 4개와 projects 5개는 studio-v1.yaml과 같은 방식으로 변환했다 + (`application/json` + ErrorEnvelope / Envelope). 그 9개가 첫 구현 + 대상이자 첫 소비자이기 때문이다. + + 나머지 70개는 아직 bare payload + `application/problem+json` + ProblemDetails + 다. 구현에 착수할 때 같은 방식으로 따라온다 — 소비자가 없는 operation을 미리 + 변환해 두면 검증되지 않은 모양이 계약에 고정된다. + + Tech Log의 유형별 specialized management 계약이다. + + 이 계약은 primary Studio orchestration 계약(`studio-v1.yaml`)이 아니다. + 현재 Studio UI는 이 operation들을 직접 호출하지 않는다. + + 존재 이유는 설계 수정 가이드 14장과 23.7의 규칙이다. + + > 현재 UI에서 사용하지 않는다는 이유로 Backend capability를 삭제하지 않는다. + > 다만 capability 보존과 endpoint 보존을 동일시하지 않는다. + + 따라서 Question start/pause/resume/reopen/archive, Project phase change, + Project Activity, Release, Topic/Tag, Profile/Site/Home Focus, + Decision supersede/reject 등의 Domain/Application capability를 여기에 보존하고, + 현재 Frontend가 사용하는 통합 편집 흐름은 `studio-v1.yaml`이 소유한다. + + ## 이 계약을 사용하는 규칙 + + - primary 흐름(작업본 편집·검증·미리보기·게시)은 반드시 `studio-v1.yaml`을 사용한다. + - 여기의 publish/unpublish 계열 operation은 primary `publishStudioDocument`와 + 동일한 Publication Event/Snapshot 경로를 거쳐야 한다. + 서로 다른 두 개의 게시 경로를 허용하지 않는다. + - 새 UI가 특정 capability를 실제로 사용하기 시작하면 해당 operation을 + primary 계약으로 승격하고 여기에서 제거한다. + + ## 이 계약에서 제거된 것 + + - `GET /session` : primary `studio-v1.yaml`이 소유한다. + - `/documents` 계열 : primary 통합 Working Copy operation으로 대체되었다. + - `/assets` 계열 : primary `studio-v1.yaml`이 소유한다. + - `/previews`, `/previews/{token}` : Preview Token 계약은 폐기되었다. + 인증된 Preview Artifact(`studio-v1.yaml`)로 대체되었다. +servers: +- url: / +security: +- sessionCookie: [] +tags: +- name: Cases +- name: References +- name: Questions +- name: Projects +- name: Decisions +- name: Releases +- name: Taxonomy +- name: Identity +paths: + /api/v1/studio/cases: + post: + operationId: createCaseDraft + tags: + - Cases + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftRequest' + security: + - sessionCookie: [] + /api/v1/studio/cases/{id}: + get: + operationId: getCaseForEdit + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + put: + operationId: updateCaseDraft + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CaseUpdateRequest' + security: + - sessionCookie: [] + delete: + operationId: deleteCaseDraft + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/references: + post: + operationId: createReferenceDraft + tags: + - References + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftRequest' + security: + - sessionCookie: [] + /api/v1/studio/references/{id}: + get: + operationId: getReferenceForEdit + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReferenceEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + put: + operationId: updateReferenceDraft + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReferenceEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ReferenceUpdateRequest' + security: + - sessionCookie: [] + delete: + operationId: deleteReferenceDraft + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/cases/{id}/validate: + post: + operationId: validateCase + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/cases/{id}/submit-review: + post: + operationId: submitReviewCase + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/cases/{id}/return-to-draft: + post: + operationId: returnToDraftCase + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/cases/{id}/unpublish: + post: + operationId: unpublishCase + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/cases/{id}/archive: + post: + operationId: archiveCase + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/cases/{id}/restore: + post: + operationId: restoreCase + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/cases/{id}/publish: + post: + operationId: publishCase + tags: + - Cases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PublishResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PublishRequest' + security: + - sessionCookie: [] + /api/v1/studio/references/{id}/validate: + post: + operationId: validateReference + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/references/{id}/submit-review: + post: + operationId: submitReviewReference + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReferenceEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/references/{id}/return-to-draft: + post: + operationId: returnToDraftReference + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReferenceEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/references/{id}/unpublish: + post: + operationId: unpublishReference + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReferenceEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/references/{id}/archive: + post: + operationId: archiveReference + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReferenceEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/references/{id}/restore: + post: + operationId: restoreReference + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReferenceEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/references/{id}/publish: + post: + operationId: publishReference + tags: + - References + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PublishResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PublishRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions: + post: + operationId: createQuestion + tags: + - Questions + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftRequest' + security: + - sessionCookie: [] + get: + operationId: listStudioQuestions + tags: + - Questions + parameters: + - name: page + in: query + schema: + type: integer + minimum: 1 + default: 1 + - name: size + in: query + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionIndexPage' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}: + get: + operationId: getQuestionForEdit + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + put: + operationId: updateQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionUpdateBody' + security: + - sessionCookie: [] + delete: + operationId: deleteQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/updates: + post: + operationId: addQuestionUpdate + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionUpdateResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionUpdateRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/updates/{updateId}: + put: + operationId: updateQuestionUpdate + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: updateId + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionUpdateResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionUpdateRequest' + security: + - sessionCookie: [] + delete: + operationId: deleteQuestionUpdate + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: updateId + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/resolve: + post: + operationId: resolveQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ResolveQuestionResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ResolveQuestionRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/start-investigation: + post: + operationId: startQuestionInvestigation + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/pause: + post: + operationId: pauseQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/resume: + post: + operationId: resumeQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/reopen: + post: + operationId: reopenQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/archive: + post: + operationId: archiveQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/publish: + post: + operationId: publishQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PublishResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PublishRequest' + security: + - sessionCookie: [] + /api/v1/studio/questions/{id}/unpublish: + post: + operationId: unpublishQuestion + tags: + - Questions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects: + post: + operationId: createProject + tags: + - Projects + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftResponseEnvelope' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '422': + description: Unprocessable Content + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftRequest' + security: + - sessionCookie: [] + get: + operationId: listStudioProjects + tags: + - Projects + parameters: + - name: page + in: query + schema: + type: integer + minimum: 1 + default: 1 + - name: size + in: query + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectIndexPageEnvelope' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}: + get: + operationId: getProjectForEdit + tags: + - Projects + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectEditResponseEnvelope' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + security: + - sessionCookie: [] + put: + operationId: updateProject + tags: + - Projects + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectEditResponseEnvelope' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '422': + description: Unprocessable Content + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectUpdateRequest' + security: + - sessionCookie: [] + delete: + operationId: deleteProject + tags: + - Projects + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '422': + description: Unprocessable Content + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/change-phase: + post: + operationId: changeProjectPhase + tags: + - Projects + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeProjectPhaseRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/publish: + post: + operationId: publishProject + tags: + - Projects + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PublishResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PublishRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/unpublish: + post: + operationId: unpublishProject + tags: + - Projects + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/decisions: + post: + operationId: createProjectDecision + tags: + - Decisions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DecisionResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDecisionRequest' + security: + - sessionCookie: [] + get: + operationId: listStudioProjectDecisions + tags: + - Decisions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/DecisionResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/decisions/{decisionId}: + get: + operationId: getProjectDecision + tags: + - Decisions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: decisionId + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DecisionResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + put: + operationId: updateProjectDecision + tags: + - Decisions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: decisionId + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DecisionResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DecisionUpdateRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/decisions/{decisionId}/accept: + post: + operationId: acceptProjectDecision + tags: + - Decisions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: decisionId + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DecisionResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/decisions/{decisionId}/reject: + post: + operationId: rejectProjectDecision + tags: + - Decisions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: decisionId + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DecisionResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/decisions/{decisionId}/supersede: + post: + operationId: supersedeProjectDecision + tags: + - Decisions + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: decisionId + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DecisionResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SupersedeDecisionRequest' + security: + - sessionCookie: [] + /api/v1/studio/releases: + post: + operationId: createRelease + tags: + - Releases + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateDraftRequest' + security: + - sessionCookie: [] + get: + operationId: listStudioReleases + tags: + - Releases + parameters: + - name: page + in: query + schema: + type: integer + minimum: 1 + default: 1 + - name: size + in: query + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReleaseIndexPage' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + /api/v1/studio/releases/{id}: + get: + operationId: getReleaseForEdit + tags: + - Releases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReleaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + put: + operationId: updateRelease + tags: + - Releases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReleaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ReleaseUpdateRequest' + security: + - sessionCookie: [] + delete: + operationId: deleteRelease + tags: + - Releases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/releases/{id}/publish: + post: + operationId: publishRelease + tags: + - Releases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PublishResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/releases/{id}/archive: + post: + operationId: archiveRelease + tags: + - Releases + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReleaseEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/topics: + get: + operationId: listStudioTopics + tags: + - Taxonomy + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TopicEditListEnvelope' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + security: + - sessionCookie: [] + post: + operationId: createTopic + tags: + - Taxonomy + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TopicEditEnvelope' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '422': + description: Unprocessable Content + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TopicEdit' + security: + - sessionCookie: [] + /api/v1/studio/topics/{id}: + put: + operationId: updateTopic + tags: + - Taxonomy + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TopicEditEnvelope' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '422': + description: Unprocessable Content + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TopicEdit' + security: + - sessionCookie: [] + delete: + operationId: deleteTopic + tags: + - Taxonomy + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '422': + description: Unprocessable Content + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorEnvelope' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/tags: + get: + operationId: listStudioTags + tags: + - Taxonomy + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TagEdit' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + post: + operationId: createTag + tags: + - Taxonomy + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '201': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TagEdit' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TagEdit' + security: + - sessionCookie: [] + /api/v1/studio/tags/{id}: + put: + operationId: updateTag + tags: + - Taxonomy + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TagEdit' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TagEdit' + security: + - sessionCookie: [] + delete: + operationId: deleteTag + tags: + - Taxonomy + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + responses: + '204': + description: No Content + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/site: + get: + operationId: getStudioSite + tags: + - Identity + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SiteConfigResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + put: + operationId: updateStudioSite + tags: + - Identity + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SiteConfigResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SiteConfigRequest' + security: + - sessionCookie: [] + /api/v1/studio/profile: + get: + operationId: getStudioProfile + tags: + - Identity + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProfileEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + put: + operationId: updateStudioProfile + tags: + - Identity + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProfileEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ProfileEditRequest' + security: + - sessionCookie: [] + /api/v1/studio/profile/publish: + post: + operationId: publishProfile + tags: + - Identity + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PublishResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/profile/unpublish: + post: + operationId: unpublishProfile + tags: + - Identity + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProfileEditResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExpectedVersionRequest' + security: + - sessionCookie: [] + /api/v1/studio/home-focus: + get: + operationId: getHomeFocus + tags: + - Identity + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/HomeFocusResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + security: + - sessionCookie: [] + put: + operationId: updateHomeFocus + tags: + - Identity + parameters: + - $ref: '#/components/parameters/CsrfToken' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/HomeFocusResponse' + '400': + description: Bad Request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: Unauthorized + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: Not Found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: Conflict + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: Unprocessable Content + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: Internal Server Error + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/HomeFocusRequest' + security: + - sessionCookie: [] + /api/v1/studio/projects/{id}/activities: + get: + operationId: listStudioProjectActivities + tags: + - Projects + security: + - sessionCookie: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ProjectActivityResponse' + '401': + description: '401' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: '403' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: '404' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: '500' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + post: + operationId: createProjectActivity + tags: + - Projects + security: + - sessionCookie: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectActivityRequest' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectActivityResponse' + '400': + description: '400' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: '401' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: '403' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: '404' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: '409' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: '422' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: '500' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + /api/v1/studio/projects/{id}/activities/{activityId}: + put: + operationId: updateProjectActivity + tags: + - Projects + security: + - sessionCookie: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: activityId + in: path + required: true + schema: + type: string + format: uuid + - $ref: '#/components/parameters/CsrfToken' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateProjectActivityRequest' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectActivityResponse' + '400': + description: '400' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '401': + description: '401' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '403': + description: '403' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '404': + description: '404' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '409': + description: '409' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '422': + description: '422' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + '500': + description: '500' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' +components: + securitySchemes: + sessionCookie: + type: apiKey + in: cookie + name: TECHLOG_SESSION + parameters: + CsrfToken: + name: X-CSRF-TOKEN + in: header + required: true + schema: + type: string + schemas: + # ------------------------------------------------------------------------- + # 응답 봉투 (ADR-006) + # + # 이 파일 상단이 예고한 변환이다. 지금은 topics/projects 9개 operation 만 + # 옮겼다 — 그 둘이 첫 소비자이기 때문이고, 나머지는 구현에 착수할 때 같은 + # 방식으로 따라온다. 그동안 이 파일에는 두 모양이 공존한다: 변환된 + # operation 은 `application/json` + Envelope, 나머지는 예전 + # `application/problem+json` + ProblemDetails 다. + # ------------------------------------------------------------------------- + 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: + - object + - 'null' + additionalProperties: true + description: 백엔드 템플릿의 ResponseMeta record 가 직렬화하는 자리다. 페이지 정보는 payload 의 `page` 가 소유하므로 이 관리 표면에서는 항상 null 이다. + ApiError: + type: object + additionalProperties: false + required: + - code + - category + - message + - retryable + properties: + code: + type: string + enum: + - AUTHENTICATION_REQUIRED + - STUDIO_ACCESS_DENIED + - REQUEST_VALIDATION_FAILED + - VERSION_CONFLICT + - TOPIC_NOT_FOUND + - TOPIC_NAME_TAKEN + - TOPIC_SLUG_TAKEN + - TOPIC_IN_USE + - PROJECT_NOT_FOUND + - PROJECT_SLUG_TAKEN + - PROJECT_IN_USE + - INTERNAL_ERROR + description: '`INTERNAL_ERROR` 는 이 기능이 아니라 스켈레톤의 공통 처리기가 내는 코드다. 계약이 그것까지 열거해야 500 응답이 계약을 벗어나지 않는다.' + 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' + - type: 'null' + ValidationErrorDetails: + type: object + additionalProperties: false + required: + - fieldErrors + properties: + fieldErrors: + type: array + maxItems: 200 + items: + $ref: '#/components/schemas/FieldError' + VersionConflictDetails: + type: object + additionalProperties: false + required: + - currentVersion + properties: + currentVersion: + type: integer + format: int64 + minimum: 0 + 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' + TopicEditEnvelope: + type: object + additionalProperties: false + required: + - success + - data + - meta + properties: + success: + type: boolean + const: true + data: + $ref: '#/components/schemas/TopicEdit' + meta: + $ref: '#/components/schemas/ResponseMeta' + TopicEditListEnvelope: + type: object + additionalProperties: false + required: + - success + - data + - meta + properties: + success: + type: boolean + const: true + data: + type: array + maxItems: 500 + items: + $ref: '#/components/schemas/TopicEdit' + meta: + $ref: '#/components/schemas/ResponseMeta' + CreateDraftResponseEnvelope: + type: object + additionalProperties: false + required: + - success + - data + - meta + properties: + success: + type: boolean + const: true + data: + $ref: '#/components/schemas/CreateDraftResponse' + meta: + $ref: '#/components/schemas/ResponseMeta' + ProjectIndexPageEnvelope: + type: object + additionalProperties: false + required: + - success + - data + - meta + properties: + success: + type: boolean + const: true + data: + $ref: '#/components/schemas/ProjectIndexPage' + meta: + $ref: '#/components/schemas/ResponseMeta' + ProjectEditResponseEnvelope: + type: object + additionalProperties: false + required: + - success + - data + - meta + properties: + success: + type: boolean + const: true + data: + $ref: '#/components/schemas/ProjectEditResponse' + meta: + $ref: '#/components/schemas/ResponseMeta' + FieldError: + type: object + required: + - field + - code + - message + properties: + field: + type: string + code: + type: string + message: + type: string + ProblemDetails: + type: object + required: + - type + - title + - status + - code + - detail + - instance + - traceId + properties: + type: + type: string + title: + type: string + status: + type: integer + code: + type: string + detail: + type: string + instance: + type: string + traceId: + type: string + fieldErrors: + type: array + items: + $ref: '#/components/schemas/FieldError' + currentVersion: + type: integer + format: int64 + PageMetadata: + type: object + required: + - number + - size + - totalElements + - totalPages + - hasPrevious + - hasNext + properties: + number: + type: integer + minimum: 1 + size: + type: integer + minimum: 1 + maximum: 100 + totalElements: + type: integer + format: int64 + minimum: 0 + totalPages: + type: integer + minimum: 0 + hasPrevious: + type: boolean + hasNext: + type: boolean + TagSummary: + type: object + required: + - name + - slug + properties: + name: + type: string + slug: + type: string + ProjectSummary: + type: object + required: + - name + - slug + - path + properties: + name: + type: string + slug: + type: string + path: + type: string + AssetReference: + type: object + required: + - assetId + - url + properties: + assetId: + type: string + format: uuid + url: + type: string + altText: + type: string + width: + type: integer + height: + type: integer + contentType: + type: string + RelatedEntry: + type: object + required: + - type + - title + - path + properties: + type: + type: string + enum: + - CASE + - REFERENCE + - QUESTION + - PROJECT + - PROJECT_DECISION + - RELEASE + title: + type: string + summary: + type: string + path: + type: string + PublicationStatus: + type: object + required: + - state + - hasUnpublishedChanges + properties: + state: + type: string + enum: + - NEVER_PUBLISHED + - ACTIVE + - WITHDRAWN + publishedSourceVersion: + type: integer + format: int64 + hasUnpublishedChanges: + type: boolean + canonicalPath: + type: string + publishedAt: + type: string + format: date-time + ValidationIssue: + type: object + required: + - severity + - code + - message + properties: + severity: + type: string + enum: + - ERROR + - WARNING + code: + type: string + field: + type: string + message: + type: string + ValidationResponse: + type: object + required: + - validForSave + - validForPublish + - issues + properties: + validForSave: + type: boolean + validForPublish: + type: boolean + issues: + type: array + items: + $ref: '#/components/schemas/ValidationIssue' + CreateDraftRequest: + type: object + required: + - title + properties: + title: + type: string + maxLength: 180 + CreateDraftResponse: + type: object + required: + - id + - status + - version + - createdAt + properties: + id: + type: string + format: uuid + status: + type: string + enum: + - DRAFT + version: + type: integer + format: int64 + createdAt: + type: string + format: date-time + RelationEdit: + type: object + properties: + primaryProjectId: + type: string + format: uuid + relatedProjectIds: + type: array + items: + type: string + format: uuid + originQuestionId: + type: string + format: uuid + relatedDocumentIds: + type: array + items: + type: string + format: uuid + derivedReferenceIds: + type: array + items: + type: string + format: uuid + CaseUpdateRequest: + type: object + required: + - expectedVersion + - title + - problemSummary + - conclusionSummary + - content + - contentFormatVersion + - tagIds + - relations + properties: + expectedVersion: + type: integer + format: int64 + title: + type: string + maxLength: 180 + slug: + type: string + maxLength: 180 + problemSummary: + type: string + maxLength: 600 + conclusionSummary: + type: string + maxLength: 600 + environmentSummary: + type: array + items: + type: string + content: + type: string + maxLength: 2000000 + contentFormatVersion: + type: integer + minimum: 1 + primaryTopicId: + type: string + format: uuid + tagIds: + type: array + maxItems: 12 + items: + type: string + format: uuid + relations: + $ref: '#/components/schemas/RelationEdit' + coverAssetId: + type: string + format: uuid + lastVerifiedAt: + type: string + format: date-time + targetVisibility: + type: string + enum: + - PRIVATE + - UNLISTED + - PUBLIC + CaseEditResponse: + type: object + required: + - id + - version + - title + - problemSummary + - conclusionSummary + - content + - contentFormatVersion + - tagIds + - relations + - workflowStatus + - targetVisibility + - publication + - updatedAt + properties: + id: + type: string + format: uuid + version: + type: integer + format: int64 + title: + type: string + slug: + type: string + problemSummary: + type: string + conclusionSummary: + type: string + environmentSummary: + type: array + items: + type: string + content: + type: string + contentFormatVersion: + type: integer + primaryTopicId: + type: string + format: uuid + tagIds: + type: array + items: + type: string + format: uuid + relations: + $ref: '#/components/schemas/RelationEdit' + coverAssetId: + type: string + format: uuid + lastVerifiedAt: + type: string + format: date-time + workflowStatus: + type: string + targetVisibility: + type: string + publication: + $ref: '#/components/schemas/PublicationStatus' + updatedAt: + type: string + format: date-time + ReferenceUpdateRequest: + type: object + required: + - expectedVersion + - title + - scopeSummary + - appliesTo + - excludedScope + - freshnessStatus + - content + - contentFormatVersion + - tagIds + - relations + properties: + expectedVersion: + type: integer + format: int64 + title: + type: string + maxLength: 180 + slug: + type: string + maxLength: 180 + scopeSummary: + type: string + maxLength: 600 + appliesTo: + type: array + items: + type: string + excludedScope: + type: array + items: + type: string + freshnessStatus: + type: string + enum: + - CURRENT + - REVIEW_DUE + - HISTORICAL + content: + type: string + maxLength: 2000000 + contentFormatVersion: + type: integer + minimum: 1 + primaryTopicId: + type: string + format: uuid + tagIds: + type: array + maxItems: 12 + items: + type: string + format: uuid + relations: + $ref: '#/components/schemas/RelationEdit' + coverAssetId: + type: string + format: uuid + lastVerifiedAt: + type: string + format: date-time + targetVisibility: + type: string + enum: + - PRIVATE + - UNLISTED + - PUBLIC + ReferenceEditResponse: + type: object + required: + - id + - version + - title + - scopeSummary + - appliesTo + - excludedScope + - freshnessStatus + - content + - contentFormatVersion + - tagIds + - relations + - workflowStatus + - targetVisibility + - publication + - updatedAt + properties: + id: + type: string + format: uuid + version: + type: integer + format: int64 + title: + type: string + slug: + type: string + scopeSummary: + type: string + appliesTo: + type: array + items: + type: string + excludedScope: + type: array + items: + type: string + freshnessStatus: + type: string + content: + type: string + contentFormatVersion: + type: integer + primaryTopicId: + type: string + format: uuid + tagIds: + type: array + items: + type: string + format: uuid + relations: + $ref: '#/components/schemas/RelationEdit' + coverAssetId: + type: string + format: uuid + lastVerifiedAt: + type: string + format: date-time + workflowStatus: + type: string + targetVisibility: + type: string + publication: + $ref: '#/components/schemas/PublicationStatus' + updatedAt: + type: string + format: date-time + ExpectedVersionRequest: + type: object + required: + - expectedVersion + properties: + expectedVersion: + type: integer + format: int64 + PublishRequest: + type: object + required: + - expectedVersion + - visibility + properties: + expectedVersion: + type: integer + format: int64 + visibility: + type: string + enum: + - PUBLIC + - UNLISTED + PublishResponse: + type: object + required: + - id + - status + - visibility + - canonicalPath + - publishedAt + - version + properties: + id: + type: string + format: uuid + status: + type: string + enum: + - PUBLISHED + visibility: + type: string + enum: + - PUBLIC + - UNLISTED + canonicalPath: + type: string + publishedAt: + type: string + format: date-time + version: + type: integer + format: int64 + QuestionPointEdit: + type: object + required: + - kind + - content + - displayOrder + properties: + id: + type: string + format: uuid + kind: + type: string + enum: + - FACT + - ASSUMPTION + - UNKNOWN + - CONSTRAINT + content: + type: string + displayOrder: + type: integer + minimum: 0 + QuestionUpdateRequest: + type: object + required: + - expectedVersion + - type + - title + - bodyMarkdown + - visibility + - occurredAt + properties: + type: + type: string + enum: + - OBSERVATION + - EVIDENCE + - SCOPE_CHANGE + - BLOCKER + - NEXT_STEP + - RESOLUTION + - RESOLUTION_REOPENED + title: + type: string + maxLength: 180 + bodyMarkdown: + type: string + visibility: + type: string + enum: + - PRIVATE + - PUBLIC + occurredAt: + type: string + format: date-time + expectedVersion: + type: integer + format: int64 + QuestionUpdateResponse: + type: object + required: + - id + - questionVersion + - type + - title + - bodyMarkdown + - visibility + - occurredAt + properties: + id: + type: string + format: uuid + questionVersion: + type: integer + format: int64 + type: + type: string + title: + type: string + bodyMarkdown: + type: string + visibility: + type: string + occurredAt: + type: string + format: date-time + QuestionUpdateBody: + type: object + required: + - expectedVersion + - question + - summary + - context + - importance + - points + - tagIds + properties: + expectedVersion: + type: integer + format: int64 + question: + type: string + maxLength: 300 + slug: + type: string + maxLength: 180 + summary: + type: string + maxLength: 600 + context: + type: string + importance: + type: string + nextVerification: + type: string + targetVisibility: + type: string + enum: + - PRIVATE + - UNLISTED + - PUBLIC + primaryProjectId: + type: string + format: uuid + topicId: + type: string + format: uuid + tagIds: + type: array + maxItems: 12 + items: + type: string + format: uuid + points: + type: array + items: + $ref: '#/components/schemas/QuestionPointEdit' + QuestionEditResponse: + type: object + required: + - id + - version + - question + - status + - targetVisibility + - points + - updates + - publication + - updatedAt + properties: + id: + type: string + format: uuid + version: + type: integer + format: int64 + question: + type: string + slug: + type: string + summary: + type: string + context: + type: string + importance: + type: string + nextVerification: + type: string + status: + type: string + targetVisibility: + type: string + primaryProjectId: + type: string + format: uuid + topicId: + type: string + format: uuid + tagIds: + type: array + items: + type: string + format: uuid + points: + type: array + items: + $ref: '#/components/schemas/QuestionPointEdit' + updates: + type: array + items: + $ref: '#/components/schemas/QuestionUpdateResponse' + resolution: + type: object + properties: + type: + type: string + summary: + type: string + resolvedAt: + type: string + format: date-time + publication: + $ref: '#/components/schemas/PublicationStatus' + updatedAt: + type: string + format: date-time + ResolveQuestionRequest: + type: object + required: + - expectedVersion + - resolutionType + - resolutionSummary + - resolvedAt + - createCaseDraft + - createProjectDecision + properties: + expectedVersion: + type: integer + format: int64 + resolutionType: + type: string + enum: + - DECISION_MADE + - ASSUMPTION_REJECTED + - QUESTION_REFRAMED + - NO_LONGER_RELEVANT + resolutionSummary: + type: string + resolvedAt: + type: string + format: date-time + createCaseDraft: + type: boolean + createProjectDecision: + type: boolean + ResolveQuestionResponse: + type: object + required: + - questionId + - status + - version + properties: + questionId: + type: string + format: uuid + status: + type: string + enum: + - RESOLVED + version: + type: integer + format: int64 + createdCaseDraftId: + type: string + format: uuid + createdProjectDecisionId: + type: string + format: uuid + ProjectUpdateRequest: + type: object + required: + - expectedVersion + - name + - oneLinePurpose + - purposeMarkdown + - boundaryMarkdown + - phase + - technologyLabels + - targetVisibility + properties: + expectedVersion: + type: integer + format: int64 + name: + type: string + maxLength: 180 + slug: + type: string + maxLength: 180 + oneLinePurpose: + type: string + maxLength: 600 + purposeMarkdown: + type: string + boundaryMarkdown: + type: string + systemOverviewMarkdown: + type: string + phase: + type: string + currentObjective: + type: string + nextStep: + type: string + technologyLabels: + type: array + items: + type: string + targetVisibility: + type: string + enum: + - PRIVATE + - UNLISTED + - PUBLIC + featuredOrder: + type: integer + topicIds: + type: array + items: + type: string + format: uuid + documentLinks: + type: array + items: + $ref: '#/components/schemas/ProjectResourceLinkEdit' + questionLinks: + type: array + items: + $ref: '#/components/schemas/ProjectResourceLinkEdit' + ProjectEditResponse: + type: object + required: + - id + - version + - name + - phase + - workflowStatus + - targetVisibility + - publication + - updatedAt + properties: + id: + type: string + format: uuid + version: + type: integer + format: int64 + name: + type: string + slug: + type: string + oneLinePurpose: + type: string + purposeMarkdown: + type: string + boundaryMarkdown: + type: string + systemOverviewMarkdown: + type: string + phase: + type: string + currentObjective: + type: string + nextStep: + type: string + technologyLabels: + type: array + items: + type: string + workflowStatus: + type: string + targetVisibility: + type: string + featuredOrder: + type: integer + publication: + $ref: '#/components/schemas/PublicationStatus' + updatedAt: + type: string + format: date-time + topicIds: + type: array + items: + type: string + format: uuid + documentLinks: + type: array + items: + $ref: '#/components/schemas/ProjectResourceLinkEdit' + questionLinks: + type: array + items: + $ref: '#/components/schemas/ProjectResourceLinkEdit' + ChangeProjectPhaseRequest: + type: object + required: + - expectedVersion + - phase + - reason + - publishActivity + properties: + expectedVersion: + type: integer + format: int64 + phase: + type: string + reason: + type: string + publishActivity: + type: boolean + DecisionUpdateRequest: + type: object + required: + - expectedVersion + - statement + - rationaleMarkdown + - consequences + - alternativesMarkdown + - targetVisibility + properties: + expectedVersion: + type: integer + format: int64 + statement: + type: string + maxLength: 1000 + rationaleMarkdown: + type: string + consequences: + type: array + items: + type: string + alternativesMarkdown: + type: string + targetVisibility: + type: string + enum: + - PRIVATE + - PUBLIC + sourceQuestionId: + type: string + format: uuid + sourceCaseId: + type: string + format: uuid + isFeatured: + type: boolean + decidedAt: + type: string + format: date-time + DecisionResponse: + allOf: + - $ref: '#/components/schemas/DecisionUpdateRequest' + - type: object + required: + - id + - projectId + - status + - version + properties: + id: + type: string + format: uuid + projectId: + type: string + format: uuid + status: + type: string + version: + type: integer + format: int64 + supersededById: + type: string + format: uuid + ReleaseUpdateRequest: + type: object + required: + - expectedVersion + - versionLabel + - title + - summary + - changeTypes + - changesMarkdown + - verificationMarkdown + properties: + expectedVersion: + type: integer + format: int64 + versionLabel: + type: string + maxLength: 32 + title: + type: string + maxLength: 180 + summary: + type: string + maxLength: 600 + releasedOn: + type: string + format: date + changeTypes: + type: array + items: + type: string + reasonMarkdown: + type: string + changesMarkdown: + type: string + userImpactMarkdown: + type: string + implementationImpactMarkdown: + type: string + verificationMarkdown: + type: string + knownLimitationsMarkdown: + type: string + relatedResources: + type: array + items: + type: object + required: + - resourceType + - resourceId + properties: + resourceType: + type: string + resourceId: + type: string + format: uuid + ReleaseEditResponse: + allOf: + - $ref: '#/components/schemas/ReleaseUpdateRequest' + - type: object + required: + - id + - workflowStatus + - version + - publication + properties: + id: + type: string + format: uuid + workflowStatus: + type: string + version: + type: integer + format: int64 + publication: + $ref: '#/components/schemas/PublicationStatus' + TopicEdit: + type: object + required: + - name + - slug + properties: + id: + type: string + format: uuid + description: |- + 응답에만 실린다. 경로가 `/topics/{id}`이므로 이 값이 없으면 목록에서 어떤 주제도 + 주소지정할 수 없다 — ProjectEditResponse / CaseEditResponse는 같은 이유로 이미 + id를 싣고 있었고, TopicEdit만 빠져 있었다. 쓰기 요청에 와도 서버는 무시한다; + 수정 대상은 경로가 소유한다. + version: + type: integer + format: int64 + description: |- + 응답에만 실린다. 다음 수정에서 보낼 `expectedVersion`의 출처다. 이것이 없으면 + 클라이언트가 낙관적 잠금에 쓸 값을 얻을 방법이 없다. + name: + type: string + maxLength: 80 + slug: + type: string + maxLength: 100 + description: + type: string + maxLength: 600 + scope: + type: string + status: + type: string + enum: + - ACTIVE + - ARCHIVED + expectedVersion: + type: integer + format: int64 + featuredReferenceId: + type: string + format: uuid + featuredCaseIds: + type: array + maxItems: 3 + items: + type: string + format: uuid + TagEdit: + type: object + required: + - name + - slug + properties: + name: + type: string + maxLength: 40 + slug: + type: string + maxLength: 60 + expectedVersion: + type: integer + format: int64 + SiteConfigRequest: + type: object + required: + - expectedVersion + - brandTitle + - identityStatement + - operatorDisplayName + - contacts + properties: + expectedVersion: + type: integer + format: int64 + brandTitle: + type: string + identityStatement: + type: string + operatorDisplayName: + type: string + shortIdentity: + type: string + avatarAssetId: + type: string + format: uuid + contacts: + type: array + items: + type: object + ProfileEditRequest: + type: object + required: + - expectedVersion + - headline + - workingModel + - territories + - selectedEvidence + - trajectory + - contacts + - targetVisibility + properties: + expectedVersion: + type: integer + format: int64 + headline: + type: string + introductionMarkdown: + type: string + workingModel: + type: array + items: + type: object + territories: + type: array + items: + type: object + selectedEvidence: + type: array + items: + type: object + trajectory: + type: array + items: + type: object + contacts: + type: array + items: + type: object + targetVisibility: + type: string + enum: + - PRIVATE + - PUBLIC + HomeFocusRequest: + type: object + required: + - expectedVersion + properties: + expectedVersion: + type: integer + format: int64 + defaultType: + type: string + enum: + - CURRENT_WORK + - OPEN_QUESTION + - RECENT_DECISION + currentProjectId: + type: string + format: uuid + openQuestionId: + type: string + format: uuid + recentDecisionId: + type: string + format: uuid + QuestionIndexItem: + type: object + required: + - id + - question + - status + - targetVisibility + - updatedAt + - version + - publication + properties: + id: + type: string + format: uuid + question: + type: string + status: + type: string + targetVisibility: + type: string + primaryProject: + $ref: '#/components/schemas/ProjectSummary' + nextVerification: + type: string + updatedAt: + type: string + format: date-time + version: + type: integer + format: int64 + publication: + $ref: '#/components/schemas/PublicationStatus' + QuestionIndexPage: + type: object + required: + - items + - page + properties: + items: + type: array + items: + $ref: '#/components/schemas/QuestionIndexItem' + page: + $ref: '#/components/schemas/PageMetadata' + ProjectIndexItem: + type: object + required: + - id + - name + - phase + - workflowStatus + - targetVisibility + - updatedAt + - version + - publication + properties: + id: + type: string + format: uuid + name: + type: string + phase: + type: string + workflowStatus: + type: string + targetVisibility: + type: string + currentObjective: + type: string + nextStep: + type: string + updatedAt: + type: string + format: date-time + version: + type: integer + format: int64 + publication: + $ref: '#/components/schemas/PublicationStatus' + ProjectIndexPage: + type: object + required: + - items + - page + properties: + items: + type: array + items: + $ref: '#/components/schemas/ProjectIndexItem' + page: + $ref: '#/components/schemas/PageMetadata' + ReleaseIndexItem: + type: object + required: + - id + - versionLabel + - title + - workflowStatus + - updatedAt + - version + - publication + properties: + id: + type: string + format: uuid + versionLabel: + type: string + title: + type: string + releasedOn: + type: string + format: date + workflowStatus: + type: string + updatedAt: + type: string + format: date-time + version: + type: integer + format: int64 + publication: + $ref: '#/components/schemas/PublicationStatus' + ReleaseIndexPage: + type: object + required: + - items + - page + properties: + items: + type: array + items: + $ref: '#/components/schemas/ReleaseIndexItem' + page: + $ref: '#/components/schemas/PageMetadata' + SiteConfigResponse: + type: object + required: + - id + - version + - brandTitle + - identityStatement + - operatorDisplayName + - contacts + properties: + id: + type: string + format: uuid + version: + type: integer + format: int64 + brandTitle: + type: string + identityStatement: + type: string + operatorDisplayName: + type: string + shortIdentity: + type: string + avatarAssetId: + type: string + format: uuid + contacts: + type: array + items: + type: object + ProfileEditResponse: + type: object + required: + - id + - version + - headline + - workingModel + - territories + - selectedEvidence + - trajectory + - contacts + - targetVisibility + - publication + properties: + id: + type: string + format: uuid + version: + type: integer + format: int64 + headline: + type: string + introductionMarkdown: + type: string + workingModel: + type: array + items: + type: object + territories: + type: array + items: + type: object + selectedEvidence: + type: array + items: + type: object + trajectory: + type: array + items: + type: object + contacts: + type: array + items: + type: object + targetVisibility: + type: string + publication: + $ref: '#/components/schemas/PublicationStatus' + HomeFocusResponse: + type: object + required: + - id + - version + properties: + id: + type: string + format: uuid + version: + type: integer + format: int64 + defaultType: + type: string + currentProjectId: + type: string + format: uuid + openQuestionId: + type: string + format: uuid + recentDecisionId: + type: string + format: uuid + ProjectResourceLinkEdit: + type: object + required: + - resourceId + - relationType + properties: + resourceId: + type: string + format: uuid + relationType: + type: string + enum: + - PRIMARY + - RELATED + featuredOrder: + type: integer + minimum: 0 + CreateDecisionRequest: + type: object + required: + - expectedProjectVersion + - statement + - rationaleMarkdown + - consequences + - alternativesMarkdown + - targetVisibility + properties: + expectedProjectVersion: + type: integer + format: int64 + statement: + type: string + maxLength: 1000 + rationaleMarkdown: + type: string + consequences: + type: array + items: + type: string + alternativesMarkdown: + type: string + targetVisibility: + type: string + enum: + - PRIVATE + - PUBLIC + sourceQuestionId: + type: string + format: uuid + sourceCaseId: + type: string + format: uuid + isFeatured: + type: boolean + decidedAt: + type: string + format: date-time + SupersedeDecisionRequest: + type: object + required: + - expectedVersion + - supersededById + properties: + expectedVersion: + type: integer + format: int64 + supersededById: + type: string + format: uuid + ProjectActivityRequest: + type: object + required: + - expectedProjectVersion + - activityType + - title + - visibility + - occurredAt + properties: + expectedProjectVersion: + type: integer + format: int64 + activityType: + type: string + title: + type: string + maxLength: 180 + summary: + type: string + maxLength: 600 + visibility: + type: string + enum: + - PRIVATE + - PUBLIC + relatedResourceType: + type: string + relatedResourceId: + type: string + format: uuid + occurredAt: + type: string + format: date-time + ProjectActivityResponse: + type: object + required: + - id + - projectId + - activityType + - title + - visibility + - origin + - occurredAt + - version + properties: + id: + type: string + format: uuid + projectId: + type: string + format: uuid + activityType: + type: string + title: + type: string + summary: + type: string + visibility: + type: string + origin: + type: string + relatedResourceType: + type: string + relatedResourceId: + type: string + format: uuid + occurredAt: + type: string + format: date-time + version: + type: integer + format: int64 + UpdateProjectActivityRequest: + type: object + required: + - expectedVersion + - title + - visibility + - occurredAt + properties: + expectedVersion: + type: integer + format: int64 + title: + type: string + summary: + type: string + visibility: + type: string + enum: + - PRIVATE + - PUBLIC + occurredAt: + type: string + format: date-time diff --git a/src/features/tech-log/contracts/public/canonical-source.json b/src/features/tech-log/contracts/public/canonical-source.json index f39638e..1b36765 100644 --- a/src/features/tech-log/contracts/public/canonical-source.json +++ b/src/features/tech-log/contracts/public/canonical-source.json @@ -2,7 +2,7 @@ "packageId": "@tech-log/public-contract", "version": "2.0.0", "digest": "sha256:8ac71425b38658f34641102b4c2e6e21288c811efebdb92c0a46fb9d4790e23e", - "sourceRevision": "b98eaf9", + "sourceRevision": "6ef5c1c", "operationIds": [ "getPublicSite", "getPublicHome", diff --git a/src/features/tech-log/contracts/studio/canonical-source.json b/src/features/tech-log/contracts/studio/canonical-source.json index 259f6df..cf93a8e 100644 --- a/src/features/tech-log/contracts/studio/canonical-source.json +++ b/src/features/tech-log/contracts/studio/canonical-source.json @@ -2,7 +2,7 @@ "packageId": "@tech-log/studio-contract", "version": "3.0.0", "digest": "sha256:6cae9924403d0761f401643a022980b8e04183eea0d890c143c9fbbbbc7431e4", - "sourceRevision": "b98eaf9", + "sourceRevision": "6ef5c1c", "operationIds": [ "getStudioSession", "getStudioDashboard", diff --git a/src/features/tech-log/contracts/tech-log-management-contract-contribution.ts b/src/features/tech-log/contracts/tech-log-management-contract-contribution.ts new file mode 100644 index 0000000..2dc5c2a --- /dev/null +++ b/src/features/tech-log/contracts/tech-log-management-contract-contribution.ts @@ -0,0 +1,256 @@ +import type { + CommandEffectDescriptor, + InstalledContractContribution, + InstalledHttpContract, +} from "../../../contracts/external-contract-runtime.ts"; +import type { ProblemDetails } from "./studio/contract.ts"; +import { TECH_LOG_FEATURE_ID } from "../application/tech-log-feature-input.ts"; +import canonicalSource from "./management/canonical-source.json" with { type: "json" }; +import { + envelopeData, + envelopeError, + passthroughInput, +} from "./tech-log-studio-contract-contribution.ts"; +import { TECH_LOG_STUDIO_SESSION_AUTH_PROFILE_ID } from "../adapters/http/studio-session-credentials.ts"; + +type PathValues = Readonly>; +type QueryEntries = readonly (readonly [string, string])[]; + +const NO_PATH: PathValues = Object.freeze({}); +const NO_QUERY = Object.freeze([]) as QueryEntries; +const PROBLEM = envelopeError(); + +/** Studio 쪽과 같은 판정이다: 4xx 도메인 거절은 적용되지 않았음이 확정, 5xx·네트워크는 불확정. */ +const COMMAND_EFFECT: CommandEffectDescriptor = Object.freeze({ + successEffect: "APPLIED_CONFIRMED" as const, + classifyProblem({ status }: Readonly<{ status: number; problem: unknown }>) { + return status >= 400 && status < 500 ? "NOT_APPLIED" : "MAYBE_APPLIED"; + }, +}); + +/** + * 관리 표면은 Studio 와 같은 세션·CSRF 를 쓴다. 계약이 `sessionCookie` 보안과 `CsrfToken` + * 파라미터를 선언하고 있고, 실제로 같은 백엔드의 같은 필터 체인을 지난다 — 그래서 인증 프로필도 + * 공유한다. 부트스트랩 프로필은 쓰지 않는다: CSRF 토큰을 발급하는 것은 `getStudioSession` + * 하나뿐이고, 이 표면은 그 뒤에만 호출된다. + */ +function readOperation( + operationId: string, + pathTemplate: string, + responseByteLimit: number, + project: (input: never) => Readonly<{ pathValues: PathValues; queryEntries: QueryEntries }> = () => + Object.freeze({ pathValues: NO_PATH, queryEntries: NO_QUERY }), +): InstalledHttpContract { + return Object.freeze({ + contract: Object.freeze({ + operationId, + method: "GET" as const, + pathTemplate, + inputValidator: passthroughInput(`${operationId}Input`), + outputValidator: envelopeData(`${operationId}Output`), + problemValidator: PROBLEM, + acceptedStatuses: Object.freeze([200]), + emptyBodyStatuses: Object.freeze([]), + retrySemantics: "SAFE" as const, + requestBody: "NONE" as const, + responseBody: "REQUIRED_JSON" as const, + commandRecovery: null, + commandEffect: null, + projectRequest(input: never) { + return Object.freeze({ ...project(input), body: null }); + }, + }), + frontend: Object.freeze({ + policyId: `${operationId}_V1`, + requestByteLimit: 0, + responseByteLimit, + totalDeadlineMs: 10_000, + retryBudget: 2 as const, + authProfileId: TECH_LOG_STUDIO_SESSION_AUTH_PROFILE_ID, + diagnosticsOperation: `techLog.management.${operationId}`, + }), + }) as InstalledHttpContract; +} + +/** + * 쓰기는 `Idempotency-Key` 를 쓰지 않는다 — 계약이 요구하지 않고, 재생 보호는 `expectedVersion` + * 이 맡는다. 그래서 `NOT_IDEMPOTENT` 가 아니라 재시도 예산 0 으로 둔다: 응답을 못 본 재시도가 + * 두 번째 생성을 만들 수 있는 표면이다. + */ +function writeOperation( + operationId: string, + method: "POST" | "PUT" | "DELETE", + pathTemplate: string, + options: Readonly<{ + acceptedStatuses: readonly number[]; + emptyBodyStatuses?: readonly number[]; + requestByteLimit: number; + responseByteLimit: number; + }>, + project: (input: never) => Readonly<{ + pathValues: PathValues; + queryEntries: QueryEntries; + body: unknown; + }>, +): InstalledHttpContract { + return Object.freeze({ + contract: Object.freeze({ + operationId, + method, + pathTemplate, + inputValidator: passthroughInput(`${operationId}Input`), + outputValidator: envelopeData(`${operationId}Output`), + problemValidator: PROBLEM, + acceptedStatuses: Object.freeze([...options.acceptedStatuses]), + emptyBodyStatuses: Object.freeze([...(options.emptyBodyStatuses ?? [])]), + // 생성은 재생 보호가 없으므로 NEVER 다. 수정·삭제는 expectedVersion 이 두 번째 + // 적용을 409 로 막으므로 IDEMPOTENT 로 둘 수 있지만, 세 경우를 한 헬퍼가 만들고 + // 있어 가장 보수적인 값으로 통일한다 — 재시도 예산도 0 이라 실제 차이는 없다. + retrySemantics: "NEVER" as const, + requestBody: "JSON" as const, + responseBody: + (options.emptyBodyStatuses ?? []).length > 0 + ? ("OPTIONAL_JSON" as const) + : ("REQUIRED_JSON" as const), + commandRecovery: null, + commandEffect: COMMAND_EFFECT, + projectRequest(input: never) { + return Object.freeze(project(input)); + }, + }), + frontend: Object.freeze({ + policyId: `${operationId}_V1`, + requestByteLimit: options.requestByteLimit, + responseByteLimit: options.responseByteLimit, + totalDeadlineMs: 15_000, + retryBudget: 0 as const, + authProfileId: TECH_LOG_STUDIO_SESSION_AUTH_PROFILE_ID, + diagnosticsOperation: `techLog.management.${operationId}`, + }), + // read 쪽과 달리 여기서만 unknown 을 거친다: `emptyBodyStatuses` 유무로 responseBody 가 + // 갈리는 삼항이 union 타입을 만들어, 컴파일러가 리터럴을 대상 타입과 겹친다고 보지 않는다. + }) as unknown as InstalledHttpContract; +} + +const byId = (input: never) => { + const value = input as unknown as Readonly<{ id: string }>; + return Object.freeze({ pathValues: Object.freeze({ id: value.id }), queryEntries: NO_QUERY }); +}; + +const T = "/api/v1/studio/topics"; +const P = "/api/v1/studio/projects"; + +const HTTP_CONTRACTS = Object.freeze([ + readOperation("listStudioTopics", T, 131_072), + writeOperation( + "createTopic", + "POST", + T, + { acceptedStatuses: [201], requestByteLimit: 16_384, responseByteLimit: 16_384 }, + (input: never) => + Object.freeze({ pathValues: NO_PATH, queryEntries: NO_QUERY, body: input }), + ), + writeOperation( + "updateTopic", + "PUT", + `${T}/{id}`, + { acceptedStatuses: [200], requestByteLimit: 16_384, responseByteLimit: 16_384 }, + (input: never) => { + const value = input as unknown as Readonly<{ id: string; body: unknown }>; + return Object.freeze({ + pathValues: Object.freeze({ id: value.id }), + queryEntries: NO_QUERY, + body: value.body, + }); + }, + ), + writeOperation( + "deleteTopic", + "DELETE", + `${T}/{id}`, + { + acceptedStatuses: [204], + emptyBodyStatuses: [204], + requestByteLimit: 1_024, + responseByteLimit: 1_024, + }, + (input: never) => { + const value = input as unknown as Readonly<{ id: string; expectedVersion: number }>; + return Object.freeze({ + pathValues: Object.freeze({ id: value.id }), + queryEntries: NO_QUERY, + body: { expectedVersion: value.expectedVersion }, + }); + }, + ), + readOperation("listStudioProjects", P, 262_144, (input: never) => { + const value = input as unknown as Readonly<{ page?: number; size?: number }> | undefined; + const entries: (readonly [string, string])[] = []; + if (value?.page !== undefined) entries.push(["page", String(value.page)]); + if (value?.size !== undefined) entries.push(["size", String(value.size)]); + return Object.freeze({ pathValues: NO_PATH, queryEntries: Object.freeze(entries) }); + }), + readOperation("getProjectForEdit", `${P}/{id}`, 262_144, byId), + writeOperation( + "createProject", + "POST", + P, + { acceptedStatuses: [201], requestByteLimit: 4_096, responseByteLimit: 8_192 }, + (input: never) => + Object.freeze({ pathValues: NO_PATH, queryEntries: NO_QUERY, body: input }), + ), + writeOperation( + "updateProject", + "PUT", + `${P}/{id}`, + { acceptedStatuses: [200], requestByteLimit: 131_072, responseByteLimit: 262_144 }, + (input: never) => { + const value = input as unknown as Readonly<{ id: string; body: unknown }>; + return Object.freeze({ + pathValues: Object.freeze({ id: value.id }), + queryEntries: NO_QUERY, + body: value.body, + }); + }, + ), + writeOperation( + "deleteProject", + "DELETE", + `${P}/{id}`, + { + acceptedStatuses: [204], + emptyBodyStatuses: [204], + requestByteLimit: 1_024, + responseByteLimit: 1_024, + }, + (input: never) => { + const value = input as unknown as Readonly<{ id: string; expectedVersion: number }>; + return Object.freeze({ + pathValues: Object.freeze({ id: value.id }), + queryEntries: NO_QUERY, + body: { expectedVersion: value.expectedVersion }, + }); + }, + ), +]); + +export const TECH_LOG_MANAGEMENT_OPERATION_IDS = Object.freeze( + HTTP_CONTRACTS.map((entry) => entry.contract.operationId), +); + +export const TECH_LOG_MANAGEMENT_CONTRIBUTION: InstalledContractContribution = Object.freeze({ + contributionId: "tech-log-management-http-v1", + featureId: TECH_LOG_FEATURE_ID, + source: Object.freeze({ + kind: "EXTERNAL_PACKAGE" as const, + package: Object.freeze({ + packageId: canonicalSource.packageId, + version: canonicalSource.version, + digest: canonicalSource.digest as `sha256:${string}`, + runtimeProtocolVersion: 1 as const, + sourceRevision: canonicalSource.sourceRevision, + }), + }), + http: HTTP_CONTRACTS, + events: Object.freeze([]), +}); diff --git a/src/features/tech-log/contracts/tech-log-route-contract.ts b/src/features/tech-log/contracts/tech-log-route-contract.ts index a4b8ec7..6c5292e 100644 --- a/src/features/tech-log/contracts/tech-log-route-contract.ts +++ b/src/features/tech-log/contracts/tech-log-route-contract.ts @@ -48,6 +48,7 @@ const TECH_LOG_ROUTE_SPECS = [ defineSpec({ routeId: "TECH_LOG_STUDIO_PUBLICATIONS", path: "/studio/publications", layoutGroup: "STUDIO", paramsSchema: null, searchSchema: null, title: "게시 기록", navigationLabel: "게시 기록", navigationOrder: 20 }), defineSpec({ routeId: "TECH_LOG_STUDIO_PUBLICATION_PREVIEW", path: "/studio/publications/:publicationEventId/preview", layoutGroup: "STUDIO", paramsSchema: "TechLogPublicationEventIdParams", searchSchema: null, title: "게시 Snapshot", navigationLabel: null, navigationOrder: null }), defineSpec({ routeId: "TECH_LOG_STUDIO_ASSETS", path: "/studio/assets", layoutGroup: "STUDIO", paramsSchema: null, searchSchema: null, title: "Asset", navigationLabel: null, navigationOrder: null }), + defineSpec({ routeId: "TECH_LOG_STUDIO_TAXONOMY", path: "/studio/taxonomy", layoutGroup: "STUDIO", paramsSchema: null, searchSchema: null, title: "주제와 프로젝트", navigationLabel: "주제·프로젝트", navigationOrder: 40 }), defineSpec({ routeId: "TECH_LOG_STUDIO_NOT_FOUND", path: "/studio/*", layoutGroup: "STUDIO", paramsSchema: "TechLogStudioSplat", searchSchema: null, title: "Studio 화면을 찾을 수 없습니다", navigationLabel: null, navigationOrder: null }), defineSpec({ routeId: "NOT_FOUND", path: "*", layoutGroup: "PUBLIC", paramsSchema: "NotFoundSplat", searchSchema: null, title: "페이지를 찾을 수 없습니다.", navigationLabel: null, navigationOrder: null }), ] as const; diff --git a/src/features/tech-log/presentation/public/components/explore-filter-form.tsx b/src/features/tech-log/presentation/public/components/explore-filter-form.tsx index 52fe466..36931c4 100644 --- a/src/features/tech-log/presentation/public/components/explore-filter-form.tsx +++ b/src/features/tech-log/presentation/public/components/explore-filter-form.tsx @@ -56,7 +56,11 @@ export function ExploreFilterForm({ item.title.toLocaleLowerCase("ko-KR") === normalizedProject, )?.slug; const hasActiveFilter = Boolean((showType && kind) || topic || project); - const formKey = [kind, topic, project, showType].join(":"); + // 선택지는 나중에 도착하는데 아래 select 들은 `defaultValue` 를 쓰는 비제어 요소다 — + // 첫 렌더에는 맞출 option 이 없어 값이 비어버린다. 도착 여부를 키에 넣어 그때 폼을 + // 다시 마운트시키면 defaultValue 가 적용된다. 제어 요소로 바꾸지 않는 이유는 이 폼이 + // submit 으로 URL 을 만드는 구조라 값의 주인이 URL 이기 때문이다. + const formKey = [kind, topic, project, showType, view.ready].join(":"); function submit(event: React.FormEvent) { event.preventDefault(); diff --git a/src/features/tech-log/presentation/public/components/search-dialog.tsx b/src/features/tech-log/presentation/public/components/search-dialog.tsx index ee01220..c16439d 100644 --- a/src/features/tech-log/presentation/public/components/search-dialog.tsx +++ b/src/features/tech-log/presentation/public/components/search-dialog.tsx @@ -23,7 +23,7 @@ export function SearchDialog({ // result list with a loading skeleton on every key, which is a worse dialog // than a stale-free local filter. The predicate is the same one the catalog // applies for a non-empty query, so the visible result set is unchanged. - const view = usePublicContent(["tech-log", "search", ""], async (queries) => ({ + const view = usePublicContent(["tech-log", "search", "dialog"], async (queries) => ({ entities: await queries.searchPublicContent(""), })); const results = (view.data?.entities ?? []).filter((entity) => diff --git a/src/features/tech-log/presentation/public/pages/search-page.tsx b/src/features/tech-log/presentation/public/pages/search-page.tsx index cf2bd54..9daa1bb 100644 --- a/src/features/tech-log/presentation/public/pages/search-page.tsx +++ b/src/features/tech-log/presentation/public/pages/search-page.tsx @@ -13,7 +13,11 @@ export function SearchPage() { const navigate = useNavigate(); const { search } = useRouteInput<"TECH_LOG_SEARCH">(); const query = optionalString(search.q)?.trim() ?? ""; - const view = usePublicContent(["tech-log", "search", query], async (queries) => ({ + // 키의 마지막 조각이 화면을 구분한다. 헤더의 검색 다이얼로그도 같은 카탈로그를 읽고 + // 빈 검색어일 때 앞 세 조각이 완전히 겹치는데, 두 화면이 담아 오는 모양이 다르다 + // (여기는 `results`, 다이얼로그는 `entities`). 키가 같으면 react-query 가 한쪽 캐시를 + // 다른 쪽에 돌려주고, 받는 쪽은 없는 필드를 읽다 렌더에서 죽는다. + const view = usePublicContent(["tech-log", "search", "page", query], async (queries) => ({ results: await queries.searchPublicContent(query), })); diff --git a/src/features/tech-log/presentation/studio/components/taxonomy-manager.tsx b/src/features/tech-log/presentation/studio/components/taxonomy-manager.tsx new file mode 100644 index 0000000..884b86b --- /dev/null +++ b/src/features/tech-log/presentation/studio/components/taxonomy-manager.tsx @@ -0,0 +1,276 @@ +import { useCallback, useEffect, useState, type FormEvent } from "react"; + +import type { ProjectIndexItem, TopicEdit } from "../../../contracts/management/contract.ts"; +import { useStudio } from "../use-studio.ts"; + +/** + * 주제와 프로젝트 관리. + * + *

이 화면이 존재하는 이유는 문서 발행이 주제를 요구하는데 주제를 만들 곳이 없었기 때문이다. + * 그래서 범위를 목록·생성·삭제로 끊었다 — 편집(이름 변경, 단계 전환, 공개 범위)은 계약에 있고 + * 백엔드도 구현돼 있으나, 그 화면은 별도 설계가 필요하다. + * + *

새 CSS 를 만들지 않는다. 작업본 목록이 쓰는 클래스만 재사용하므로 이 화면은 Studio 의 + * 나머지와 같은 간격·타이포·색을 그대로 따른다. + */ +export function TaxonomyManager() { + const { managementGateway, setRequestAnnouncement } = useStudio(); + const [topics, setTopics] = useState(null); + const [projects, setProjects] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(""); + const [pending, setPending] = useState(false); + const [generation, setGeneration] = useState(0); + + const [topicName, setTopicName] = useState(""); + const [topicSlug, setTopicSlug] = useState(""); + const [projectTitle, setProjectTitle] = useState(""); + + const reload = useCallback(() => setGeneration((value) => value + 1), []); + + useEffect(() => { + let cancelled = false; + setLoading(true); + setError(""); + void Promise.all([managementGateway.listTopics(), managementGateway.listProjects(0, 50)]).then( + ([topicList, projectPage]) => { + if (cancelled) return; + setTopics(topicList); + setProjects(projectPage.items ?? []); + setLoading(false); + }, + () => { + if (cancelled) return; + setError("주제와 프로젝트를 불러오지 못했습니다."); + setLoading(false); + }, + ); + return () => { + cancelled = true; + }; + }, [managementGateway, generation]); + + /** + * slug 를 비워 두면 이름에서 만든다. 한글 이름이 흔한데 slug 는 ASCII 만 받으므로, 비운 채로 + * 저장하면 서버가 422 로 거절한다 — 사용자가 규칙을 몰라도 되도록 여기서 채운다. + */ + const slugify = (value: string) => + value + .trim() + .toLowerCase() + .replace(/[^a-z0-9]+/gu, "-") + .replace(/^-+|-+$/gu, ""); + + const submitTopic = async (event: FormEvent) => { + event.preventDefault(); + if (pending) return; + const name = topicName.trim(); + const slug = slugify(topicSlug || topicName); + if (!name || !slug) { + setError("주제 이름과 slug 를 입력해 주세요. slug 는 영문·숫자·하이픈만 가능합니다."); + return; + } + setPending(true); + setError(""); + try { + await managementGateway.createTopic({ name, slug } as TopicEdit); + setTopicName(""); + setTopicSlug(""); + setRequestAnnouncement(`주제 ${name} 을(를) 만들었습니다.`); + reload(); + } catch { + setError("주제를 만들지 못했습니다. 같은 이름이나 slug 가 이미 있을 수 있습니다."); + } finally { + setPending(false); + } + }; + + const submitProject = async (event: FormEvent) => { + event.preventDefault(); + if (pending) return; + const title = projectTitle.trim(); + if (!title) { + setError("프로젝트 이름을 입력해 주세요."); + return; + } + setPending(true); + setError(""); + try { + await managementGateway.createProject(title); + setProjectTitle(""); + setRequestAnnouncement(`프로젝트 ${title} 을(를) 만들었습니다.`); + reload(); + } catch { + setError("프로젝트를 만들지 못했습니다."); + } finally { + setPending(false); + } + }; + + const removeTopic = async (topic: TopicEdit) => { + if (pending || topic.id === undefined || topic.version === undefined) return; + setPending(true); + setError(""); + try { + await managementGateway.deleteTopic(topic.id, topic.version); + setRequestAnnouncement(`주제 ${topic.name} 을(를) 삭제했습니다.`); + reload(); + } catch { + setError("주제를 삭제하지 못했습니다. 이 주제를 쓰는 기록이 있을 수 있습니다."); + } finally { + setPending(false); + } + }; + + const removeProject = async (project: ProjectIndexItem) => { + if (pending) return; + setPending(true); + setError(""); + try { + await managementGateway.deleteProject(project.id, project.version); + setRequestAnnouncement(`프로젝트 ${project.name} 을(를) 삭제했습니다.`); + reload(); + } catch { + setError("프로젝트를 삭제하지 못했습니다. 연결된 기록이 있을 수 있습니다."); + } finally { + setPending(false); + } + }; + + return ( +

+
+
+

TAXONOMY

+

주제와 프로젝트

+

문서를 게시하려면 주제가 필요합니다. 여기서 만들고 정리합니다.

+
+
+ +
+
+ +
+ setTopicName(event.target.value)} + /> + setTopicSlug(event.target.value)} + /> + +
+
+
+ +
+ setProjectTitle(event.target.value)} + /> + +
+
+
+ + {loading ? ( +

+ 주제와 프로젝트를 불러오는 중입니다. +

+ ) : null} + {error ? ( +

+ {error} +

+ ) : null} + + {!loading && topics ? ( + <> +

{topics.length}개의 주제

+ {topics.length ? ( +
+ {topics.map((topic) => ( +
+

TOPIC

+
+

{topic.name}

+

{topic.slug}

+
+
+
+
상태
+
{topic.status === "ARCHIVED" ? "보관" : "사용 중"}
+
+
+ +
+ ))} +
+ ) : ( +

아직 주제가 없습니다. 위에서 하나 만들어 주세요.

+ )} + + ) : null} + + {!loading && projects ? ( + <> +

{projects.length}개의 프로젝트

+ {projects.length ? ( +
+ {projects.map((project) => ( +
+

PROJECT

+
+

{project.name}

+

{project.currentObjective ?? "목표 미지정"}

+
+
+
+
단계
+
{project.phase}
+
+
+
공개
+
{project.targetVisibility}
+
+
+ +
+ ))} +
+ ) : ( +

아직 프로젝트가 없습니다.

+ )} + + ) : null} +
+ ); +} diff --git a/src/features/tech-log/presentation/studio/pages/taxonomy-page.tsx b/src/features/tech-log/presentation/studio/pages/taxonomy-page.tsx new file mode 100644 index 0000000..8a65f39 --- /dev/null +++ b/src/features/tech-log/presentation/studio/pages/taxonomy-page.tsx @@ -0,0 +1,5 @@ +import { TaxonomyManager } from "../components/taxonomy-manager.tsx"; + +export function TaxonomyPage() { + return ; +} diff --git a/src/features/tech-log/presentation/studio/studio-provider.tsx b/src/features/tech-log/presentation/studio/studio-provider.tsx index 21580a7..36355a8 100644 --- a/src/features/tech-log/presentation/studio/studio-provider.tsx +++ b/src/features/tech-log/presentation/studio/studio-provider.tsx @@ -9,6 +9,7 @@ import { import { isStudioGatewayError } from "../../application/ports/studio-gateway-error.ts"; import type { StudioAssetGateway } from "../../application/ports/studio-asset-gateway.ts"; import type { StudioGateway } from "../../application/ports/studio-gateway.ts"; +import type { ManagementGateway } from "../../application/ports/management-gateway.ts"; import type { ResolvePublishedLabel } from "../../domain/public-render-content.ts"; import type { WorkingCopy, @@ -29,6 +30,7 @@ type StudioProviderProps = Readonly<{ // Optional so test harnesses that only exercise the document gateway keep // working unchanged. `StudioShell` always supplies one in the running app. createAssetGateway?: () => StudioAssetGateway; + createManagementGateway: () => ManagementGateway; resolvePublishedLabel?: ResolvePublishedLabel; now?: () => Date; navigate?: (href: string) => void; @@ -53,6 +55,7 @@ export function StudioProvider({ children, createGateway, createAssetGateway, + createManagementGateway, resolvePublishedLabel = missingPublishedLabel, now = () => new Date("2026-08-14T01:00:00.000Z"), navigate = defaultNavigate, @@ -64,6 +67,7 @@ export function StudioProvider({ const [assetGateway] = useState( () => createAssetGateway?.() ?? null, ); + const [managementGateway] = useState(() => createManagementGateway()); const [editor, setEditor] = useState(null); const [pendingHref, setPendingHref] = useState(null); const [requestAnnouncement, setRequestAnnouncement] = useState(""); @@ -159,6 +163,7 @@ export function StudioProvider({ const value = useMemo( () => ({ gateway, + managementGateway, assetGateway, resolvePublishedLabel, now, @@ -177,6 +182,7 @@ export function StudioProvider({ clearEditor, editor, gateway, + managementGateway, navigateInternal, now, requestAnnouncement, diff --git a/src/features/tech-log/presentation/studio/studio-shell.tsx b/src/features/tech-log/presentation/studio/studio-shell.tsx index 99d49c7..34d6e4d 100644 --- a/src/features/tech-log/presentation/studio/studio-shell.tsx +++ b/src/features/tech-log/presentation/studio/studio-shell.tsx @@ -58,6 +58,10 @@ export function StudioShell({ children }: StudioShellProps) { () => application.features.get(TECH_LOG_FEATURE_ID).createStudioGateway(), [application], ); + const createManagementGateway = useCallback( + () => application.features.get(TECH_LOG_FEATURE_ID).createManagementGateway(), + [application], + ); const createAssetGateway = useCallback( () => application.features.get(TECH_LOG_FEATURE_ID).createStudioAssetGateway(), [application], @@ -92,6 +96,7 @@ export function StudioShell({ children }: StudioShellProps) { key={generation} createGateway={createGateway} createAssetGateway={createAssetGateway} + createManagementGateway={createManagementGateway} resolvePublishedLabel={resolvePublishedLabel} navigate={navigateInternal} > diff --git a/src/features/tech-log/presentation/studio/use-studio.ts b/src/features/tech-log/presentation/studio/use-studio.ts index aea95e4..5be80a4 100644 --- a/src/features/tech-log/presentation/studio/use-studio.ts +++ b/src/features/tech-log/presentation/studio/use-studio.ts @@ -2,6 +2,7 @@ import { createContext, useContext, useMemo } from "react"; import type { StudioAssetGateway } from "../../application/ports/studio-asset-gateway.ts"; import type { StudioGateway } from "../../application/ports/studio-gateway.ts"; +import type { ManagementGateway } from "../../application/ports/management-gateway.ts"; import type { ResolvePublishedLabel } from "../../domain/public-render-content.ts"; import type { WorkingCopy, @@ -23,6 +24,9 @@ export type StudioContextValue = Readonly<{ // `createAssetGateway` prop. `StudioShell` — the real app path — always // supplies one, so production code sees this populated. assetGateway: StudioAssetGateway | null; + // 주제·프로젝트 관리. `assetGateway` 와 같은 이유로 nullable 이 아니다 — 이 표면은 + // MOCK 소스가 없어 항상 HTTP 이고, 없는 경우가 존재하지 않는다. + managementGateway: ManagementGateway; resolvePublishedLabel: ResolvePublishedLabel; now(): Date; editor: StudioEditorState | null; diff --git a/src/features/tech-log/presentation/tech-log-route-runtime.tsx b/src/features/tech-log/presentation/tech-log-route-runtime.tsx index 66133a2..57bd97b 100644 --- a/src/features/tech-log/presentation/tech-log-route-runtime.tsx +++ b/src/features/tech-log/presentation/tech-log-route-runtime.tsx @@ -209,6 +209,13 @@ export const TECH_LOG_ROUTE_RUNTIME = Object.freeze({ "AssetsPage", ), ), + TECH_LOG_STUDIO_TAXONOMY: runtime( + "TECH_LOG_STUDIO_TAXONOMY", + routeModule( + () => import("./studio/pages/taxonomy-page.tsx"), + "TaxonomyPage", + ), + ), TECH_LOG_STUDIO_NOT_FOUND: runtime( "TECH_LOG_STUDIO_NOT_FOUND", routeModule( diff --git a/tests/features/tech-log/asset-picker.test.tsx b/tests/features/tech-log/asset-picker.test.tsx index 71f7e18..cf40d18 100644 --- a/tests/features/tech-log/asset-picker.test.tsx +++ b/tests/features/tech-log/asset-picker.test.tsx @@ -28,6 +28,7 @@ import { MOCK_STUDIO_INSTALL_CONTEXT } from "../../helpers/studio-install-contex import type { StudioAssetGateway } from "../../../src/features/tech-log/application/ports/studio-asset-gateway.ts"; import { StudioGatewayError } from "../../../src/features/tech-log/application/ports/studio-gateway-error.ts"; import type { Asset } from "../../../src/features/tech-log/contracts/studio/contract.ts"; +import { createManagementGatewayStub } from "../../helpers/management-gateway-stub.ts"; // jsdom does not implement `` -- same polyfill `studio-save-navigation.test.tsx` // and `studio-decision-authoring.test.tsx` already use for the other Studio dialogs. @@ -433,7 +434,8 @@ test("inserts the directive at the saved cursor position and the live preview re render( - gateway} createAssetGateway={() => assetGateway}> + gateway} createAssetGateway={() => assetGateway}> , @@ -538,7 +540,8 @@ test("an asset uploaded through the mock composition previews live and passes mo render( - gateway} createAssetGateway={() => assetGateway}> + gateway} createAssetGateway={() => assetGateway}> , @@ -1259,7 +1262,8 @@ function caseDraft(bodyMarkdown: string) { function renderInstantPreview(bodyMarkdown: string, assets: readonly Asset[]) { render( - createMockStudioGateway()}> + createMockStudioGateway()}> , @@ -1479,7 +1483,8 @@ test("an asset uploaded with alt text inserts that alt and the document validate render( - gateway} createAssetGateway={() => assetGateway}> + gateway} createAssetGateway={() => assetGateway}> , @@ -1524,7 +1529,8 @@ test("a decorative upload inserts an empty alt and the document is still publish render( - gateway} createAssetGateway={() => assetGateway}> + gateway} createAssetGateway={() => assetGateway}> , @@ -1797,7 +1803,8 @@ test("a Picker search never drops an already-inserted asset out of Instant Previ render( - gateway} createAssetGateway={() => assetGateway}> + gateway} createAssetGateway={() => assetGateway}> , diff --git a/tests/features/tech-log/feature-input.test.ts b/tests/features/tech-log/feature-input.test.ts index d379aae..1d4bb64 100644 --- a/tests/features/tech-log/feature-input.test.ts +++ b/tests/features/tech-log/feature-input.test.ts @@ -16,7 +16,10 @@ type Assert = Condition; type TechLogFeatureInputExposesNoMissingOrAdditionalKeys = Assert< Equal< keyof ApplicationFeatureInputs["tech-log"], - "publicContent" | "createStudioGateway" | "createStudioAssetGateway" + | "publicContent" + | "createStudioGateway" + | "createStudioAssetGateway" + | "createManagementGateway" > >; type TechLogFeatureInputRegistryValueMatchesFeatureContract = Assert< diff --git a/tests/features/tech-log/navigation-derivation.test.ts b/tests/features/tech-log/navigation-derivation.test.ts index a1dab08..bc1a2e0 100644 --- a/tests/features/tech-log/navigation-derivation.test.ts +++ b/tests/features/tech-log/navigation-derivation.test.ts @@ -28,6 +28,7 @@ describe("TechLog navigation derivation", () => { ["작업본", "/studio/documents"], ["게시 기록", "/studio/publications"], ["새 문서", "/studio/documents/new"], + ["주제·프로젝트", "/studio/taxonomy"], ]); }); diff --git a/tests/features/tech-log/public-discovery-screens.test.tsx b/tests/features/tech-log/public-discovery-screens.test.tsx index d757185..49506c7 100644 --- a/tests/features/tech-log/public-discovery-screens.test.tsx +++ b/tests/features/tech-log/public-discovery-screens.test.tsx @@ -34,6 +34,7 @@ import { ApplicationProvider } from "../../../src/presentation/providers/applica import { createGroupedRouteObjects } from "../../../src/presentation/routes/app-router.tsx"; import { PLATFORM_ROUTE_CODECS } from "../../../src/presentation/routes/platform-route-codecs.ts"; import { createTestApplication } from "../../helpers/create-test-application.ts"; +import { renderWithQueryProviders } from "../../helpers/query-providers.tsx"; const routeCodecs = Object.freeze({ ...PLATFORM_ROUTE_CODECS, @@ -54,7 +55,7 @@ type DiscoveryRenderOptions = Readonly<{ application?: ReturnType; }>; -function renderDiscoveryRoute( +async function renderDiscoveryRoute( routeId: RouteId, initialEntry: string, options: DiscoveryRenderOptions = {}, @@ -93,6 +94,7 @@ function renderDiscoveryRoute( ); const techLog = createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input; const view = render( + renderWithQueryProviders( ( > , + )); + // 포트가 async 가 되면서 첫 페인트에는 데이터가 없다. 화면이 정착한 뒤 + // 단언하도록 여기서 한 번 기다린다 — 각 테스트에 흩어 놓으면 빠뜨린 곳이 생긴다. + // `main` 을 기다리지 않는다 — 이 파일에는 의도적으로 렌더에 실패하는 라우트도 있고, + // 그때는 main 이 아예 없다. 로딩 표면이 사라지는 것이 두 경우 모두에 맞는 기준이다. + await waitFor(() => + expect(document.querySelector(".state-surface--loading")).toBeNull(), ); return { ...view, router } satisfies ReturnType & { router: RouterProviderProps["router"]; @@ -122,7 +131,7 @@ afterEach(() => { describe("TechLog home discovery", () => { it("renders the exact identity, focus, latest ordering, and explore choices", async () => { - const { router } = renderDiscoveryRoute( + const { router } = await renderDiscoveryRoute( "TECH_LOG_HOME", "/?focus=invalid&focus=question", ); @@ -174,7 +183,7 @@ describe("TechLog home discovery", () => { it("moves linked tabs with arrows, Home, and End while synchronizing the URL", async () => { const user = userEvent.setup(); - const { router } = renderDiscoveryRoute("TECH_LOG_HOME", "/?focus=question"); + const { router } = await renderDiscoveryRoute("TECH_LOG_HOME", "/?focus=question"); const question = screen.getByRole("tab", { name: "열린 질문" }); question.focus(); @@ -195,7 +204,7 @@ describe("TechLog home discovery", () => { }); it("treats external focus URL changes as authoritative without stealing focus", async () => { - const { router } = renderDiscoveryRoute( + const { router } = await renderDiscoveryRoute( "TECH_LOG_HOME", "/?focus=current&state=latest-empty#latest", ); @@ -250,14 +259,14 @@ describe("TechLog home discovery", () => { expect(screen.getAllByRole("tabpanel")).toHaveLength(1); }); - it("preserves the source home empty and error states", () => { - const emptyView = renderDiscoveryRoute("TECH_LOG_HOME", "/?state=latest-empty"); + it("preserves the source home empty and error states", async () => { + const emptyView = await renderDiscoveryRoute("TECH_LOG_HOME", "/?state=latest-empty"); expect(screen.getByText("아직 공개된 기록이 없습니다.")).toHaveClass( "latest-state--empty", ); emptyView.unmount(); - const latestErrorView = renderDiscoveryRoute( + const latestErrorView = await renderDiscoveryRoute( "TECH_LOG_HOME", "/?state=latest-error", ); @@ -270,14 +279,14 @@ describe("TechLog home discovery", () => { ); latestErrorView.unmount(); - const errorView = renderDiscoveryRoute("TECH_LOG_HOME", "/?state=site-error"); + const errorView = await renderDiscoveryRoute("TECH_LOG_HOME", "/?state=site-error"); expect( screen.getByRole("heading", { name: "페이지를 불러오지 못했습니다." }), ).toBeVisible(); expect(screen.getByText("PREVIEW-HOME-500")).toBeVisible(); errorView.unmount(); - renderDiscoveryRoute("TECH_LOG_HOME", "/?state=focus-empty"); + await renderDiscoveryRoute("TECH_LOG_HOME", "/?state=focus-empty"); expect(screen.queryByText("지금 집중하는 것")).not.toBeInTheDocument(); }); }); @@ -285,7 +294,7 @@ describe("TechLog home discovery", () => { describe("TechLog explore discovery", () => { it("filters by kind, topic, and project and keeps source result structure", async () => { const user = userEvent.setup(); - const { router } = renderDiscoveryRoute( + const { router } = await renderDiscoveryRoute( "TECH_LOG_EXPLORE", "/explore?type=CASE&topic=JPA&project=backend-skeleton", ); @@ -294,9 +303,12 @@ describe("TechLog explore discovery", () => { expect( screen.getByText("유형과 기술 주제, 프로젝트를 조합해 공개 기록을 찾습니다."), ).toBeVisible(); - expect(screen.getByLabelText("유형")).toHaveValue("CASE"); - expect(screen.getByLabelText("주제")).toHaveValue("JPA"); - expect(screen.getByLabelText("프로젝트")).toHaveValue("backend-skeleton"); + // 필터의 선택지는 카탈로그가 도착한 뒤 채워지고, select 의 값도 그때 설정된다. + await waitFor(() => { + expect(screen.getByLabelText("유형")).toHaveValue("CASE"); + expect(screen.getByLabelText("주제")).toHaveValue("JPA"); + expect(screen.getByLabelText("프로젝트")).toHaveValue("backend-skeleton"); + }); expect(screen.getByText("1개의 공개 기록")).toBeVisible(); expect( screen.getByRole("link", { name: /컬렉션 Fetch Join과 페이징은 왜 충돌하는가/ }), @@ -337,8 +349,8 @@ describe("TechLog explore discovery", () => { ]); }); - it("renders the distinct no-result state for an unmatched query filter", () => { - renderDiscoveryRoute("TECH_LOG_EXPLORE", "/explore?topic=missing"); + it("renders the distinct no-result state for an unmatched query filter", async () => { + await renderDiscoveryRoute("TECH_LOG_EXPLORE", "/explore?topic=missing"); expect(screen.getByText("0개의 공개 기록")).toBeVisible(); expect(screen.getByText("조건에 맞는 공개 기록이 없습니다.")).toHaveClass( @@ -351,8 +363,8 @@ describe("TechLog explore discovery", () => { ); }); - it("keeps kind-specific copy, filtering, count, ordering, and back navigation", () => { - renderDiscoveryRoute( + it("keeps kind-specific copy, filtering, count, ordering, and back navigation", async () => { + await renderDiscoveryRoute( "TECH_LOG_EXPLORE_KIND", "/explore/questions?topic=JPA&project=backend-skeleton", ); @@ -374,7 +386,7 @@ describe("TechLog explore discovery", () => { }); it("keeps the unreachable unknown-kind client fallback accessible", async () => { - const { router, container } = renderDiscoveryRoute( + const { router, container } = await renderDiscoveryRoute( "TECH_LOG_EXPLORE_KIND", "/explore/unknown", ); @@ -425,7 +437,7 @@ describe("TechLog explore discovery", () => { }, featureInputs: { "tech-log": techLog }, }); - const { router, container } = renderDiscoveryRoute( + const { router, container } = await renderDiscoveryRoute( "TECH_LOG_EXPLORE_KIND", "/explore/unknown", { NotFoundComponent: LazyNotFound, application }, @@ -473,7 +485,7 @@ describe("TechLog explore discovery", () => { describe("TechLog search discovery", () => { it("normalizes repeated queries, preserves result ordering, and navigates canonically", async () => { const user = userEvent.setup(); - const { router } = renderDiscoveryRoute( + const { router } = await renderDiscoveryRoute( "TECH_LOG_SEARCH", "/search?q=%20JPA%20&q=Redis&unknown=drop", ); @@ -503,8 +515,8 @@ describe("TechLog search discovery", () => { it("submits from the keyboard and synchronizes the searchbox with URL changes", async () => { const user = userEvent.setup(); - const { router } = renderDiscoveryRoute("TECH_LOG_SEARCH", "/search"); - const input = screen.getByRole("searchbox", { name: "검색어" }); + const { router } = await renderDiscoveryRoute("TECH_LOG_SEARCH", "/search"); + const input = await screen.findByRole("searchbox", { name: "검색어" }); await user.type(input, "Redis{Enter}"); await waitFor(() => { @@ -523,7 +535,7 @@ describe("TechLog search discovery", () => { }); it("normalizes an explicitly empty first query and renders all canonical results", async () => { - const { router } = renderDiscoveryRoute( + const { router } = await renderDiscoveryRoute( "TECH_LOG_SEARCH", "/search?q=%20%20&q=JPA", ); @@ -531,13 +543,13 @@ describe("TechLog search discovery", () => { await waitFor(() => { expect(router.state.location.search).toBe(""); }); - expect(screen.getByRole("heading", { name: "전체 검색 결과" })).toBeVisible(); + expect(await screen.findByRole("heading", { name: "전체 검색 결과" })).toBeVisible(); expect(screen.getByText("9개의 검색 결과")).toBeVisible(); expect(screen.getByRole("searchbox", { name: "검색어" })).toHaveValue(""); }); - it("renders the exact zero-result state without a result list", () => { - renderDiscoveryRoute("TECH_LOG_SEARCH", "/search?q=존재하지않음"); + it("renders the exact zero-result state without a result list", async () => { + await renderDiscoveryRoute("TECH_LOG_SEARCH", "/search?q=존재하지않음"); expect(screen.getByText("0개의 검색 결과")).toBeVisible(); expect(screen.getByText("일치하는 공개 기록이 없습니다.")).toHaveClass( @@ -548,7 +560,7 @@ describe("TechLog search discovery", () => { }); describe("home focus domain", () => { - it("normalizes selection and wraps all supported keyboard movements", () => { + it("normalizes selection and wraps all supported keyboard movements", async () => { const keys = ["current", "question", "decision"] as const; expect(normalizeFocus(undefined, keys)).toBe("current"); diff --git a/tests/features/tech-log/public-document-screens.test.tsx b/tests/features/tech-log/public-document-screens.test.tsx index eeebdeb..3f01a11 100644 --- a/tests/features/tech-log/public-document-screens.test.tsx +++ b/tests/features/tech-log/public-document-screens.test.tsx @@ -26,6 +26,7 @@ import { ApplicationProvider } from "../../../src/presentation/providers/applica import { createGroupedRouteObjects } from "../../../src/presentation/routes/app-router.tsx"; import { PLATFORM_ROUTE_CODECS } from "../../../src/presentation/routes/platform-route-codecs.ts"; import { createTestApplication } from "../../helpers/create-test-application.ts"; +import { renderWithQueryProviders } from "../../helpers/query-providers.tsx"; const routeCodecs = Object.freeze({ ...PLATFORM_ROUTE_CODECS, @@ -63,7 +64,7 @@ afterEach(() => { vi.unstubAllGlobals(); }); -function renderDocumentRoute(routeId: DocumentRouteId, initialEntry: string) { +async function renderDocumentRoute(routeId: DocumentRouteId, initialEntry: string) { const definition = TECH_LOG_ROUTE_REGISTRY[routeId]; const runtime = TECH_LOG_ROUTE_RUNTIME_CONTRACT[routeId]; const Component = routeComponents[routeId]; @@ -95,6 +96,7 @@ function renderDocumentRoute(routeId: DocumentRouteId, initialEntry: string) { ); const techLog = createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input; const view = render( + renderWithQueryProviders( , - ); + )); + // 포트가 async 가 되면서 첫 페인트에는 데이터가 없다. 화면이 정착한 뒤 + // 단언하도록 여기서 한 번 기다린다 — 각 테스트에 흩어 놓으면 빠뜨린 곳이 생긴다. + await screen.findByRole("main"); return { ...view, router }; } @@ -195,8 +200,8 @@ describe("TechLog canonical Public documents", () => { }, ])( "renders $path with exact metadata, evidence, and ordered relations", - ({ routeId, path, title, kind, topic, project, published, evidence, relations }) => { - const { container } = renderDocumentRoute(routeId, path); + async ({ routeId, path, title, kind, topic, project, published, evidence, relations }) => { + const { container } = await renderDocumentRoute(routeId, path); const main = screen.getByRole("main"); expect(within(main).getByRole("heading", { level: 1, name: title })).toBeVisible(); @@ -222,8 +227,8 @@ describe("TechLog canonical Public documents", () => { }, ); - it("preserves the specialized Fetch Join layout, TOC, rendered blocks, anchors, and evidence media", () => { - const { container } = renderDocumentRoute( + it("preserves the specialized Fetch Join layout, TOC, rendered blocks, anchors, and evidence media", async () => { + const { container } = await renderDocumentRoute( "TECH_LOG_CASE", "/cases/collection-fetch-join-pagination", ); @@ -253,8 +258,8 @@ describe("TechLog canonical Public documents", () => { expect(image).toHaveAttribute("loading", "lazy"); }); - it("uses the generic Case markup and omits source relations for the canonical empty state", () => { - const generic = renderDocumentRoute( + it("uses the generic Case markup and omits source relations for the canonical empty state", async () => { + const generic = await renderDocumentRoute( "TECH_LOG_CASE", "/cases/redis-adapter-ttl-boundary", ); @@ -264,7 +269,7 @@ describe("TechLog canonical Public documents", () => { ); generic.unmount(); - const empty = renderDocumentRoute( + const empty = await renderDocumentRoute( "TECH_LOG_CASE", "/cases/collection-fetch-join-pagination?state=relations-empty", ); @@ -273,8 +278,8 @@ describe("TechLog canonical Public documents", () => { expect(screen.queryByRole("heading", { name: "이 기록의 연결" })).not.toBeInTheDocument(); }); - it("preserves Reference rule order and resolved Question evidence and empty copy", () => { - const reference = renderDocumentRoute( + it("preserves Reference rule order and resolved Question evidence and empty copy", async () => { + const reference = await renderDocumentRoute( "TECH_LOG_REFERENCE", "/references/state-and-nonce-boundary", ); @@ -290,7 +295,7 @@ describe("TechLog canonical Public documents", () => { expect(screen.getByText("마지막 검증 2026.08.09")).toBeVisible(); reference.unmount(); - renderDocumentRoute( + await renderDocumentRoute( "TECH_LOG_QUESTION", "/questions/collection-fetch-join-with-pagination", ); @@ -311,16 +316,16 @@ describe("TechLog topics and Public not-found routing", () => { ["jpa", "JPA", "목록 조회, 연관 로딩과 페이지 경계를 함께 검증한 기록입니다.", "3개의 관련 기록"], ["authentication", "Authentication", "브라우저와 Edge, Resource Server 사이의 인증 책임과 신뢰 경계를 검증한 기록입니다.", "2개의 관련 기록"], ["redis", "Redis", "애플리케이션 정책과 Redis 저장 명령의 책임 경계를 검증한 기록입니다.", "1개의 관련 기록"], - ])("aggregates /topics/%s with exact copy and count", (slug, title, description, count) => { - renderDocumentRoute("TECH_LOG_TOPIC", `/topics/${slug}`); + ])("aggregates /topics/%s with exact copy and count", async (slug, title, description, count) => { + await renderDocumentRoute("TECH_LOG_TOPIC", `/topics/${slug}`); expect(screen.getByRole("heading", { level: 1, name: title })).toBeVisible(); expect(screen.getByText(description)).toBeVisible(); expect(screen.getByText(count)).toBeVisible(); }); - it("keeps JPA topic records in canonical published order", () => { - const { container } = renderDocumentRoute("TECH_LOG_TOPIC", "/topics/jpa"); + it("keeps JPA topic records in canonical published order", async () => { + const { container } = await renderDocumentRoute("TECH_LOG_TOPIC", "/topics/jpa"); expect( Array.from(container.querySelectorAll(".public-record-list > li > a"), (link) => @@ -339,7 +344,7 @@ describe("TechLog topics and Public not-found routing", () => { ["TECH_LOG_QUESTION" as const, "/questions/not-registered"], ["TECH_LOG_TOPIC" as const, "/topics/not-registered"], ])("keeps the unreachable client fallback accessible for %s", async (routeId, path) => { - const { container, router } = renderDocumentRoute(routeId, path); + const { container, router } = await renderDocumentRoute(routeId, path); expect(router.state.location.pathname).toBe(path); expect( diff --git a/tests/features/tech-log/public-index-screens.test.tsx b/tests/features/tech-log/public-index-screens.test.tsx index 835cc24..2d07fc7 100644 --- a/tests/features/tech-log/public-index-screens.test.tsx +++ b/tests/features/tech-log/public-index-screens.test.tsx @@ -31,6 +31,7 @@ import { ApplicationProvider } from "../../../src/presentation/providers/applica import { createGroupedRouteObjects } from "../../../src/presentation/routes/app-router.tsx"; import { PLATFORM_ROUTE_CODECS } from "../../../src/presentation/routes/platform-route-codecs.ts"; import { createTestApplication } from "../../helpers/create-test-application.ts"; +import { renderWithQueryProviders } from "../../helpers/query-providers.tsx"; const routeCodecs = Object.freeze({ ...PLATFORM_ROUTE_CODECS, @@ -52,7 +53,7 @@ const routeComponents = { type PublicIndexRouteId = keyof typeof routeComponents; -function renderPublicRoute(routeId: PublicIndexRouteId, initialEntry: string) { +async function renderPublicRoute(routeId: PublicIndexRouteId, initialEntry: string) { const routeIds = routeId === "NOT_FOUND" ? [routeId] : [routeId, "NOT_FOUND" as const]; const registry = Object.fromEntries( routeIds.map((id) => [id, TECH_LOG_ROUTE_REGISTRY[id]]), @@ -85,6 +86,7 @@ function renderPublicRoute(routeId: PublicIndexRouteId, initialEntry: string) { ); const techLog = createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input; const view = render( + renderWithQueryProviders( , - ); + )); + // 포트가 async 가 되면서 첫 페인트에는 데이터가 없다. 화면이 정착한 뒤 + // 단언하도록 여기서 한 번 기다린다 — 각 테스트에 흩어 놓으면 빠뜨린 곳이 생긴다. + await screen.findByRole("main"); return { ...view, router }; } @@ -101,8 +106,8 @@ function projectNavigation() { } describe("TechLog project screens", () => { - it("renders the exact ordered project index and project links", () => { - const { container } = renderPublicRoute("TECH_LOG_PROJECTS", "/projects"); + it("renders the exact ordered project index and project links", async () => { + const { container } = await renderPublicRoute("TECH_LOG_PROJECTS", "/projects"); const main = screen.getByRole("main"); expect(within(main).getByRole("heading", { level: 1, name: "프로젝트" })).toBeVisible(); @@ -145,8 +150,8 @@ describe("TechLog project screens", () => { stats: ["2공개 기록", "1설계 결정", "2활동 기록"], topics: ["Authentication", "OAuth 2.0", "OIDC", "Keycloak"], }, - ])("renders $path overview with source counts and topics", ({ path, title, current, thesis, stats, topics }) => { - const { container } = renderPublicRoute("TECH_LOG_PROJECT", path); + ])("renders $path overview with source counts and topics", async ({ path, title, current, thesis, stats, topics }) => { + const { container } = await renderPublicRoute("TECH_LOG_PROJECT", path); const main = screen.getByRole("main"); expect(within(main).getByRole("heading", { level: 1, name: title })).toBeVisible(); @@ -167,8 +172,8 @@ describe("TechLog project screens", () => { ["TECH_LOG_PROJECT_RECORDS" as const, "/projects/backend-skeleton/records", "기록"], ["TECH_LOG_PROJECT_DECISIONS" as const, "/projects/backend-skeleton/decisions", "결정"], ["TECH_LOG_PROJECT_ACTIVITY" as const, "/projects/backend-skeleton/activity", "활동"], - ])("derives the active project tab from %s location", (routeId, path, activeLabel) => { - renderPublicRoute(routeId, path); + ])("derives the active project tab from %s location", async (routeId, path, activeLabel) => { + await renderPublicRoute(routeId, path); expect( within(projectNavigation()) @@ -199,8 +204,8 @@ describe("TechLog project screens", () => { "/questions/validate-edge-token-again", ], }, - ])("keeps $slug records filtered and published in source order", ({ slug, expected }) => { - const { container } = renderPublicRoute( + ])("keeps $slug records filtered and published in source order", async ({ slug, expected }) => { + const { container } = await renderPublicRoute( "TECH_LOG_PROJECT_RECORDS", `/projects/${slug}/records`, ); @@ -213,8 +218,8 @@ describe("TechLog project screens", () => { expect(screen.getByText(`${expected.length}개의 공개 기록`)).toBeVisible(); }); - it("keeps project decisions ordered with stable IDs and evidence links", () => { - const { container } = renderPublicRoute( + it("keeps project decisions ordered with stable IDs and evidence links", async () => { + const { container } = await renderPublicRoute( "TECH_LOG_PROJECT_DECISIONS", "/projects/backend-skeleton/decisions", ); @@ -240,8 +245,8 @@ describe("TechLog project screens", () => { ]); }); - it("keeps project activity ordered and preserves self-fragment and record links", () => { - const { container } = renderPublicRoute( + it("keeps project activity ordered and preserves self-fragment and record links", async () => { + const { container } = await renderPublicRoute( "TECH_LOG_PROJECT_ACTIVITY", "/projects/backend-skeleton/activity", ); @@ -273,8 +278,8 @@ describe("TechLog project screens", () => { }); describe("TechLog release and profile screens", () => { - it("renders the ordered release index and complete version link", () => { - const { container } = renderPublicRoute("TECH_LOG_RELEASES", "/releases"); + it("renders the ordered release index and complete version link", async () => { + const { container } = await renderPublicRoute("TECH_LOG_RELEASES", "/releases"); expect(screen.getByRole("heading", { level: 1, name: "변경 기록" })).toBeVisible(); expect( @@ -297,8 +302,8 @@ describe("TechLog release and profile screens", () => { ]); }); - it("renders exact release sections and ordered cross-links", () => { - const { container } = renderPublicRoute("TECH_LOG_RELEASE", "/releases/0.1.0"); + it("renders exact release sections and ordered cross-links", async () => { + const { container } = await renderPublicRoute("TECH_LOG_RELEASE", "/releases/0.1.0"); const main = screen.getByRole("main"); expect( @@ -325,8 +330,8 @@ describe("TechLog release and profile screens", () => { ).toEqual(["/", "/explore", "/cases/collection-fetch-join-pagination"]); }); - it("renders the grounded profile, principles, project links, and topics", () => { - const { container } = renderPublicRoute("TECH_LOG_PROFILE", "/profile"); + it("renders the grounded profile, principles, project links, and topics", async () => { + const { container } = await renderPublicRoute("TECH_LOG_PROFILE", "/profile"); const main = screen.getByRole("main"); expect(within(main).getByRole("heading", { level: 1, name: "동현" })).toBeVisible(); @@ -362,7 +367,7 @@ describe("TechLog Public not-found runtime", () => { ["TECH_LOG_RELEASE" as const, "/releases/9.9.9"], ["TECH_LOG_CASE" as const, "/cases/not-registered"], ])("keeps the client-only fallback accessible for %s", async (routeId, path) => { - const { container, router } = renderPublicRoute(routeId, path); + const { container, router } = await renderPublicRoute(routeId, path); expect(router.state.location.pathname).toBe(path); expect( @@ -374,8 +379,8 @@ describe("TechLog Public not-found runtime", () => { expect(container.querySelector(".site-frame")).not.toBeNull(); }); - it("keeps the client-only Public catch-all accessible", () => { - const { container, router } = renderPublicRoute( + it("keeps the client-only Public catch-all accessible", async () => { + const { container, router } = await renderPublicRoute( "NOT_FOUND", "/definitely-not-a-product-route", ); diff --git a/tests/features/tech-log/public-shell.test.tsx b/tests/features/tech-log/public-shell.test.tsx index 2dab991..fef1ce7 100644 --- a/tests/features/tech-log/public-shell.test.tsx +++ b/tests/features/tech-log/public-shell.test.tsx @@ -11,6 +11,7 @@ import { FatalErrorState } from "../../../src/features/tech-log/presentation/pub import { PublicShell } from "../../../src/features/tech-log/presentation/public/public-shell.tsx"; import { ApplicationProvider } from "../../../src/presentation/providers/application-provider.tsx"; import { createTestApplication } from "../../helpers/create-test-application.ts"; +import { renderWithQueryProviders } from "../../helpers/query-providers.tsx"; const originalShowModal = HTMLDialogElement.prototype.showModal; const originalClose = HTMLDialogElement.prototype.close; @@ -47,7 +48,7 @@ afterEach(() => { }); }); -function renderShell(initialEntry = "/projects") { +async function renderShell(initialEntry = "/projects") { const techLog = createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input; const router = createMemoryRouter( [ @@ -65,6 +66,7 @@ function renderShell(initialEntry = "/projects") { { initialEntries: [initialEntry] }, ); const view = render( + renderWithQueryProviders( , - ); + )); + // 포트가 async 가 되면서 첫 페인트에는 데이터가 없다. 화면이 정착한 뒤 + // 단언하도록 여기서 한 번 기다린다 — 각 테스트에 흩어 놓으면 빠뜨린 곳이 생긴다. + await screen.findByRole("main"); return { ...view, router }; } describe("TechLog Public shell", () => { - it("preserves source landmark order, navigation copy, current state, and footer", () => { - const view = renderShell(); + it("preserves source landmark order, navigation copy, current state, and footer", async () => { + const view = await renderShell(); const frame = view.container.querySelector(".site-frame"); expect( @@ -127,7 +132,7 @@ describe("TechLog Public shell", () => { it("uses the canonical root route for brand navigation", async () => { const user = userEvent.setup(); - const { router } = renderShell("/projects/backend-skeleton"); + const { router } = await renderShell("/projects/backend-skeleton"); await user.click(screen.getByRole("link", { name: "TechLog 홈" })); @@ -136,7 +141,7 @@ describe("TechLog Public shell", () => { it("opens search by click, closes on Escape, and restores trigger focus", async () => { const user = userEvent.setup(); - renderShell(); + await renderShell(); const trigger = screen.getByRole("button", { name: "TechLog 검색 열기" }); await user.click(trigger); @@ -155,7 +160,7 @@ describe("TechLog Public shell", () => { it("opens the native search trigger from the keyboard", async () => { const user = userEvent.setup(); - renderShell(); + await renderShell(); const trigger = screen.getByRole("button", { name: "TechLog 검색 열기" }); trigger.focus(); @@ -169,7 +174,7 @@ describe("TechLog Public shell", () => { it("returns source-equivalent results and navigates to their canonical route", async () => { const user = userEvent.setup(); - const { router } = renderShell(); + const { router } = await renderShell(); await user.click( screen.getByRole("button", { name: "TechLog 검색 열기" }), ); @@ -191,7 +196,7 @@ describe("TechLog Public shell", () => { it("navigates the full-search action with its canonical query intact", async () => { const user = userEvent.setup(); - const { router } = renderShell(); + const { router } = await renderShell(); await user.click( screen.getByRole("button", { name: "TechLog 검색 열기" }), ); @@ -210,7 +215,7 @@ describe("TechLog Public shell", () => { it("uses native mobile disclosure activation and closes it after selection", async () => { const user = userEvent.setup(); - const view = renderShell(); + const view = await renderShell(); const details = view.container.querySelector( "details.mobile-nav", ); @@ -236,7 +241,7 @@ describe("TechLog Public shell", () => { it("closes a natively opened mobile disclosure before opening one search dialog", async () => { const user = userEvent.setup(); - const view = renderShell(); + const view = await renderShell(); const details = view.container.querySelector( "details.mobile-nav", ); diff --git a/tests/features/tech-log/route-contract.test.ts b/tests/features/tech-log/route-contract.test.ts index 47f3237..e471e64 100644 --- a/tests/features/tech-log/route-contract.test.ts +++ b/tests/features/tech-log/route-contract.test.ts @@ -38,6 +38,7 @@ const expectedRoutes = [ ["TECH_LOG_STUDIO_PUBLICATIONS", "/studio/publications", "STUDIO", null, null], ["TECH_LOG_STUDIO_PUBLICATION_PREVIEW", "/studio/publications/:publicationEventId/preview", "STUDIO", "TechLogPublicationEventIdParams", null], ["TECH_LOG_STUDIO_ASSETS", "/studio/assets", "STUDIO", null, null], + ["TECH_LOG_STUDIO_TAXONOMY", "/studio/taxonomy", "STUDIO", null, null], ["TECH_LOG_STUDIO_NOT_FOUND", "/studio/*", "STUDIO", "TechLogStudioSplat", null], ["NOT_FOUND", "*", "PUBLIC", "NotFoundSplat", null], ] as const; @@ -69,6 +70,7 @@ const expectedTitles = { TECH_LOG_STUDIO_PUBLICATIONS: "게시 기록", TECH_LOG_STUDIO_PUBLICATION_PREVIEW: "게시 Snapshot", TECH_LOG_STUDIO_ASSETS: "Asset", + TECH_LOG_STUDIO_TAXONOMY: "주제와 프로젝트", TECH_LOG_STUDIO_NOT_FOUND: "Studio 화면을 찾을 수 없습니다", NOT_FOUND: "페이지를 찾을 수 없습니다.", } as const; @@ -142,12 +144,13 @@ describe("TechLog route boundary contract", () => { ["TECH_LOG_STUDIO_DOCUMENTS", "STUDIO", "작업본", 10], ["TECH_LOG_STUDIO_DOCUMENT_NEW", "STUDIO", "새 문서", 30], ["TECH_LOG_STUDIO_PUBLICATIONS", "STUDIO", "게시 기록", 20], + ["TECH_LOG_STUDIO_TAXONOMY", "STUDIO", "주제·프로젝트", 40], ]); for (const locale of ["ko-KR", "en-US"] as const) { const catalog: Readonly> = TECH_LOG_MESSAGE_CATALOGS[locale]; - expect(Object.keys(catalog)).toHaveLength(56); + expect(Object.keys(catalog)).toHaveLength(58); for (const [routeId, title] of Object.entries(expectedTitles)) { expect(catalog[`route.${routeId}.title`]).toBe(title); expect(catalog[`route.${routeId}.navigation`]).toBe(title); diff --git a/tests/features/tech-log/runtime-composition.test.ts b/tests/features/tech-log/runtime-composition.test.ts index 039441a..61d1b89 100644 --- a/tests/features/tech-log/runtime-composition.test.ts +++ b/tests/features/tech-log/runtime-composition.test.ts @@ -60,6 +60,7 @@ test("installs TechLog beside the retained reference feature through application assert.deepEqual(Object.keys(installed), ["reference-feature", "tech-log"]); assert.deepEqual(Object.keys(installed["tech-log"]).sort(), [ + "createManagementGateway", "createStudioAssetGateway", "createStudioGateway", "publicContent", diff --git a/tests/features/tech-log/studio-decision-authoring.test.tsx b/tests/features/tech-log/studio-decision-authoring.test.tsx index cfe1364..9484ad6 100644 --- a/tests/features/tech-log/studio-decision-authoring.test.tsx +++ b/tests/features/tech-log/studio-decision-authoring.test.tsx @@ -14,6 +14,7 @@ import type { WorkingCopyInput } from "../../../src/features/tech-log/contracts/ import { DocumentEditorScreen } from "../../../src/features/tech-log/presentation/studio/components/document-editor-screen.tsx"; import { NewDocumentForm } from "../../../src/features/tech-log/presentation/studio/components/new-document-form.tsx"; import { StudioProvider } from "../../../src/features/tech-log/presentation/studio/studio-provider.tsx"; +import { createManagementGatewayStub } from "../../helpers/management-gateway-stub.ts"; type DecisionInput = Extract; @@ -54,6 +55,7 @@ function RouterStudioProvider({ const navigate = useNavigate(); return ( gateway} navigate={(href) => { void navigate(href); }} > @@ -140,7 +142,8 @@ describe("TechLog Studio project decision authoring", () => { }); render( - gateway}> + gateway}> , diff --git a/tests/features/tech-log/studio-editor-smoke.test.tsx b/tests/features/tech-log/studio-editor-smoke.test.tsx index dc55ebd..1fe1806 100644 --- a/tests/features/tech-log/studio-editor-smoke.test.tsx +++ b/tests/features/tech-log/studio-editor-smoke.test.tsx @@ -12,6 +12,7 @@ import { FIXTURE_IDS } from "../../../src/features/tech-log/adapters/mock/fixtur import type { StudioGateway } from "../../../src/features/tech-log/application/ports/studio-gateway.ts"; import { DocumentEditorScreen } from "../../../src/features/tech-log/presentation/studio/components/document-editor-screen.tsx"; import { StudioProvider } from "../../../src/features/tech-log/presentation/studio/studio-provider.tsx"; +import { createManagementGatewayStub } from "../../helpers/management-gateway-stub.ts"; afterEach(() => vi.restoreAllMocks()); @@ -26,7 +27,8 @@ function renderEditor( .input.createStudioAssetGateway(); return render( - gateway} createAssetGateway={() => assetGateway}> + gateway} createAssetGateway={() => assetGateway}> , diff --git a/tests/features/tech-log/studio-publication-flow.test.tsx b/tests/features/tech-log/studio-publication-flow.test.tsx index 630685e..4d4f8fa 100644 --- a/tests/features/tech-log/studio-publication-flow.test.tsx +++ b/tests/features/tech-log/studio-publication-flow.test.tsx @@ -24,6 +24,7 @@ import { PublicationEventPreviewScreen } from "../../../src/features/tech-log/pr import { PublicationList } from "../../../src/features/tech-log/presentation/studio/components/publication-list.tsx"; import { PublishScreen } from "../../../src/features/tech-log/presentation/studio/components/publish-screen.tsx"; import { StudioProvider } from "../../../src/features/tech-log/presentation/studio/studio-provider.tsx"; +import { createManagementGatewayStub } from "../../helpers/management-gateway-stub.ts"; const originalShowModal = HTMLDialogElement.prototype.showModal; const originalClose = HTMLDialogElement.prototype.close; @@ -85,7 +86,8 @@ function renderInStudio( ) { return render( - gateway} navigate={navigate}> + gateway} navigate={navigate}> {node} , diff --git a/tests/features/tech-log/studio-save-navigation.test.tsx b/tests/features/tech-log/studio-save-navigation.test.tsx index 602141c..919fa28 100644 --- a/tests/features/tech-log/studio-save-navigation.test.tsx +++ b/tests/features/tech-log/studio-save-navigation.test.tsx @@ -23,6 +23,7 @@ import { useStudio, useStudioEditorSession, } from "../../../src/features/tech-log/presentation/studio/use-studio.ts"; +import { createManagementGatewayStub } from "../../helpers/management-gateway-stub.ts"; const originalShowModal = HTMLDialogElement.prototype.showModal; const originalClose = HTMLDialogElement.prototype.close; @@ -86,7 +87,8 @@ function renderEditor(documentId: string, gateway: StudioGateway) { .input.createStudioAssetGateway(); return render( - gateway} createAssetGateway={() => assetGateway}> + gateway} createAssetGateway={() => assetGateway}> @@ -214,7 +216,8 @@ describe("TechLog Studio dirty navigation", () => { const saved = await getSavedDocument(gateway); render( - gateway} navigate={(href) => destinations.push(href)}> + gateway} navigate={(href) => destinations.push(href)}> , @@ -251,7 +254,8 @@ describe("TechLog Studio dirty navigation", () => { const saved = await getSavedDocument(gateway); render( - gateway} navigate={(href) => destinations.push(href)}> + gateway} navigate={(href) => destinations.push(href)}> , @@ -283,7 +287,8 @@ describe("TechLog Studio dirty navigation", () => { const saved = await getSavedDocument(base); render( - gateway} navigate={(href) => destinations.push(href)}> + gateway} navigate={(href) => destinations.push(href)}> , diff --git a/tests/features/tech-log/studio-screens-smoke.test.tsx b/tests/features/tech-log/studio-screens-smoke.test.tsx index 6cace79..3e3a6b3 100644 --- a/tests/features/tech-log/studio-screens-smoke.test.tsx +++ b/tests/features/tech-log/studio-screens-smoke.test.tsx @@ -16,6 +16,7 @@ import { StudioDashboard } from "../../../src/features/tech-log/presentation/stu import { StudioProvider } from "../../../src/features/tech-log/presentation/studio/studio-provider.tsx"; import { ApplicationProvider } from "../../../src/presentation/providers/application-provider.tsx"; import { createTestApplication } from "../../helpers/create-test-application.ts"; +import { createManagementGatewayStub } from "../../helpers/management-gateway-stub.ts"; afterEach(() => vi.restoreAllMocks()); @@ -30,6 +31,7 @@ function RouterStudioProvider({ const navigate = useNavigate(); return ( gateway} navigate={(href) => { void navigate(href); diff --git a/tests/features/tech-log/studio-shell-smoke.test.tsx b/tests/features/tech-log/studio-shell-smoke.test.tsx index 8af8833..87f8131 100644 --- a/tests/features/tech-log/studio-shell-smoke.test.tsx +++ b/tests/features/tech-log/studio-shell-smoke.test.tsx @@ -15,10 +15,11 @@ import { createExternalAuthSessionAdapter } from "../../../src/adapters/auth/ext import { ApplicationProvider } from "../../../src/presentation/providers/application-provider.tsx"; import { SessionProvider } from "../../../src/presentation/providers/session-provider.tsx"; import { createTestApplication } from "../../helpers/create-test-application.ts"; +import { renderWithQueryProviders } from "../../helpers/query-providers.tsx"; afterEach(() => vi.restoreAllMocks()); -function renderStudio( +async function renderStudio( initialEntry: string, createStudioGateway: () => StudioGateway, ) { @@ -62,12 +63,16 @@ function renderStudio( }, }); const view = render( + renderWithQueryProviders( , - ); + )); + // 포트가 async 가 되면서 첫 페인트에는 데이터가 없다. 화면이 정착한 뒤 + // 단언하도록 여기서 한 번 기다린다 — 각 테스트에 흩어 놓으면 빠뜨린 곳이 생긴다. + await screen.findByRole("main"); return { ...view, router }; } @@ -77,7 +82,7 @@ describe("TechLog Studio shell", () => { const gateway = createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input.createStudioGateway(); const createGateway = vi.fn(() => gateway); - const { container, router } = renderStudio("/studio", createGateway); + const { container, router } = await renderStudio("/studio", createGateway); expect(await screen.findByRole("heading", { level: 1, name: "작업 흐름" })).toBeVisible(); expect(createGateway).toHaveBeenCalledTimes(1); @@ -90,6 +95,7 @@ describe("TechLog Studio shell", () => { ["작업본", "/studio/documents"], ["게시 기록", "/studio/publications"], ["새 문서", "/studio/documents/new"], + ["주제·프로젝트", "/studio/taxonomy"], ["공개 사이트 보기", "/"], ]); expect(screen.getByRole("link", { name: "공개 사이트 보기" })).toHaveAttribute("href", "/"); @@ -117,7 +123,7 @@ describe("TechLog Studio shell", () => { .mockReturnValueOnce(first) .mockReturnValueOnce(second); - renderStudio("/studio", createGateway); + await renderStudio("/studio", createGateway); await waitFor(() => expect(firstSignal).toBeDefined()); await user.click(screen.getByRole("button", { name: "Studio 메뉴 열기" })); expect(screen.getByRole("button", { name: "Studio 메뉴 닫기" })).toBeVisible(); @@ -131,12 +137,12 @@ describe("TechLog Studio shell", () => { expect(secondDashboard).toHaveBeenCalledTimes(1); }); - it("keeps unknown Studio routes inside the Studio shell without authentication UI", () => { + it("keeps unknown Studio routes inside the Studio shell without authentication UI", async () => { const createGateway = vi.fn( () => createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input.createStudioGateway(), ); - renderStudio("/studio/does-not-exist", createGateway); + await renderStudio("/studio/does-not-exist", createGateway); expect(screen.getByRole("banner")).toHaveClass("studio-header"); expect(screen.getByRole("heading", { level: 1, name: "Studio 화면을 찾을 수 없습니다" })).toBeVisible(); diff --git a/tests/features/tech-log/studio-validation-preview.test.tsx b/tests/features/tech-log/studio-validation-preview.test.tsx index 733b85e..d588d97 100644 --- a/tests/features/tech-log/studio-validation-preview.test.tsx +++ b/tests/features/tech-log/studio-validation-preview.test.tsx @@ -13,6 +13,7 @@ import { DocumentEditorScreen } from "../../../src/features/tech-log/presentatio import { PublicPreviewScreen } from "../../../src/features/tech-log/presentation/studio/components/public-preview-screen.tsx"; import { ValidationScreen } from "../../../src/features/tech-log/presentation/studio/components/validation-screen.tsx"; import { StudioProvider } from "../../../src/features/tech-log/presentation/studio/studio-provider.tsx"; +import { createManagementGatewayStub } from "../../helpers/management-gateway-stub.ts"; class NoopIntersectionObserver implements IntersectionObserver { readonly root = null; @@ -44,7 +45,8 @@ function renderStudio( ) { return render( - gateway}>{child} + gateway}>{child} , ); } @@ -130,7 +132,8 @@ describe("TechLog Studio validation workflow", () => { view.rerender( - gateway}> + gateway}> , diff --git a/tests/features/tech-log/style-contract.test.ts b/tests/features/tech-log/style-contract.test.ts index e5588b6..962f27d 100644 --- a/tests/features/tech-log/style-contract.test.ts +++ b/tests/features/tech-log/style-contract.test.ts @@ -1,6 +1,6 @@ // @vitest-environment jsdom -import { render } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; @@ -14,6 +14,7 @@ import { FatalErrorState } from "../../../src/features/tech-log/presentation/pub import { PublicShell } from "../../../src/features/tech-log/presentation/public/public-shell.tsx"; import { ApplicationProvider } from "../../../src/presentation/providers/application-provider.tsx"; import { createTestApplication } from "../../helpers/create-test-application.ts"; +import { renderWithQueryProviders } from "../../helpers/query-providers.tsx"; let styleElement: HTMLStyleElement; @@ -33,7 +34,7 @@ afterAll(() => { styleElement.remove(); }); -function renderPublicSurface(node: React.ReactNode) { +async function renderPublicSurface(node: React.ReactNode) { const techLog = createTechLogFeatureInstalledInput(MOCK_STUDIO_INSTALL_CONTEXT).input; const shell = createElement(PublicShell, { children: node }); const router = createElement( @@ -41,19 +42,24 @@ function renderPublicSurface(node: React.ReactNode) { { initialEntries: ["/projects"] }, shell, ); - return render( - createElement(ApplicationProvider, { - application: createTestApplication({ - featureInputs: { "tech-log": techLog }, + const view = render( + renderWithQueryProviders( + createElement(ApplicationProvider, { + application: createTestApplication({ + featureInputs: { "tech-log": techLog }, + }), + children: router, }), - children: router, - }), + ), ); + // 포트가 async 가 되면서 첫 페인트에는 데이터가 없다. 화면이 정착한 뒤 단언한다. + await screen.findByRole("main"); + return view; } describe("TechLog consumer-visible style contract", () => { - it("applies the source typography, palette, shell width, and header spacing", () => { - const view = renderPublicSurface( + it("applies the source typography, palette, shell width, and header spacing", async () => { + const view = await renderPublicSurface( createElement( "main", { id: "main-content", className: "shell" }, @@ -97,8 +103,8 @@ describe("TechLog consumer-visible style contract", () => { expect(getComputedStyle(wordmark).fontWeight).toBe("680"); }); - it("aligns code, table, and evidence widths with the article body", () => { - const view = renderPublicSurface( + it("aligns code, table, and evidence widths with the article body", async () => { + const view = await renderPublicSurface( createElement( "main", { id: "main-content", className: "shell" }, @@ -152,7 +158,7 @@ describe("TechLog consumer-visible style contract", () => { it("preserves keyboard focus and the real search-field focus style", async () => { const user = userEvent.setup(); - const view = renderPublicSurface( + const view = await renderPublicSurface( createElement( "main", { id: "main-content", className: "shell" }, @@ -181,8 +187,8 @@ describe("TechLog consumer-visible style contract", () => { ); }); - it("keeps every header and dialog control at the source minimum target size", () => { - const view = renderPublicSurface( + it("keeps every header and dialog control at the source minimum target size", async () => { + const view = await renderPublicSurface( createElement( "main", { id: "main-content", className: "shell" }, @@ -218,8 +224,8 @@ describe("TechLog consumer-visible style contract", () => { expect(getComputedStyle(dialogInner!).padding).toBe("27px 28px 22px"); }); - it("preserves footer spacing and fatal-action sizing", () => { - const view = renderPublicSurface( + it("preserves footer spacing and fatal-action sizing", async () => { + const view = await renderPublicSurface( createElement(FatalErrorState, { traceId: "PUBLIC-500", retryHref: "/", diff --git a/tests/features/tech-log/use-studio-asset-gateway.test.tsx b/tests/features/tech-log/use-studio-asset-gateway.test.tsx index b8a41f9..ace91d3 100644 --- a/tests/features/tech-log/use-studio-asset-gateway.test.tsx +++ b/tests/features/tech-log/use-studio-asset-gateway.test.tsx @@ -26,6 +26,8 @@ function contextValue( return { gateway: {} as never, assetGateway, + // 이 테스트는 Asset 접근자만 고정한다 — 관리 게이트웨이는 쓰이지 않으므로 자리만 채운다. + managementGateway: {} as never, resolvePublishedLabel: () => undefined, now: () => new Date("2026-08-14T01:00:00.000Z"), editor: null, diff --git a/tests/helpers/management-gateway-stub.ts b/tests/helpers/management-gateway-stub.ts new file mode 100644 index 0000000..7aa954a --- /dev/null +++ b/tests/helpers/management-gateway-stub.ts @@ -0,0 +1,28 @@ +import type { ManagementGateway } from "../../src/features/tech-log/application/ports/management-gateway.ts"; + +/** + * Studio 화면 테스트가 `StudioProvider` 를 렌더할 때 필요한 최소 관리 게이트웨이. + * + *

기본값은 "아무것도 없음" 이다. 이 하네스들은 문서·Asset 흐름을 검증하지 아니 주제·프로젝트 + * 관리를 검증하지 않으므로, 호출되면 빈 목록을 주고 쓰기는 거부한다 — 조용히 성공시키면 관리 + * 표면을 실수로 건드리는 테스트가 통과해 버린다. + */ +export function createManagementGatewayStub( + overrides: Partial = {}, +): ManagementGateway { + const unused = (name: string) => async (): Promise => { + throw new Error(`management gateway ${name} was not stubbed for this test`); + }; + return Object.freeze({ + listTopics: async () => [], + listProjects: async () => ({ items: [], page: {} }) as never, + createTopic: unused("createTopic"), + updateTopic: unused("updateTopic"), + deleteTopic: unused("deleteTopic"), + getProject: unused("getProject"), + createProject: unused("createProject"), + updateProject: unused("updateProject"), + deleteProject: unused("deleteProject"), + ...overrides, + }) as ManagementGateway; +} diff --git a/tests/unit/ci-workflow-generation.test.ts b/tests/unit/ci-workflow-generation.test.ts index 88e41c5..7e25b00 100644 --- a/tests/unit/ci-workflow-generation.test.ts +++ b/tests/unit/ci-workflow-generation.test.ts @@ -200,8 +200,8 @@ describe("CI gate contract", () => { // than taking either side's number. // Task 11 added TECH_LOG_STUDIO_ASSETS's manual accessibility evidence. // Alignment follow-up item 2 added the TechLog junit report. - expect(contract.gates.reduce((total, gate) => total + gate.evidenceArtifactIds.length, 0)).toBe(109); - expect(contract.artifacts).toHaveLength(130); + expect(contract.gates.reduce((total, gate) => total + gate.evidenceArtifactIds.length, 0)).toBe(110); + expect(contract.artifacts).toHaveLength(131); expect(contract.stages).toHaveLength(5); expect(contract.retention.classes).toHaveLength(5); expect(index.gates.get("FE-GATE-015")?.commandIds).toHaveLength(2); diff --git a/tests/unit/task3-selective-integration.test.ts b/tests/unit/task3-selective-integration.test.ts index fb11579..14c5543 100644 --- a/tests/unit/task3-selective-integration.test.ts +++ b/tests/unit/task3-selective-integration.test.ts @@ -192,7 +192,7 @@ describe("selective Task 3 contract closure", () => { expect(canonical.gates).toHaveLength(27); expect(canonical.commands).toHaveLength(85); expect(canonical.gates.reduce((sum, gate) => sum + gate.commandIds.length, 0)).toBe(97); - expect(canonical.artifacts).toHaveLength(130); + expect(canonical.artifacts).toHaveLength(131); expect(canonical.stages).toHaveLength(5); expect(canonical.retention.classes).toHaveLength(5); diff --git a/tests/unit/tech-log-serving-contract.test.ts b/tests/unit/tech-log-serving-contract.test.ts index 70d7304..216de55 100644 --- a/tests/unit/tech-log-serving-contract.test.ts +++ b/tests/unit/tech-log-serving-contract.test.ts @@ -34,6 +34,7 @@ describe("TechLog production serving contract", () => { expect(contract.studioSpaPathPatterns).toEqual([ "^/studio$", "^/studio/assets$", + "^/studio/taxonomy$", "^/studio/documents$", "^/studio/documents/new$", "^/studio/documents/[^/]+/(edit|validation|preview|publish)$", diff --git a/vite.config.ts b/vite.config.ts index 9486e38..1142a83 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -31,6 +31,7 @@ const techLogRouteChunks = Object.freeze({ "/studio/pages/publications-page.tsx": "route-tech-log-studio-publications", "/studio/pages/publication-preview-page.tsx": "route-tech-log-studio-publication-preview", "/studio/pages/assets-page.tsx": "route-tech-log-studio-assets", + "/studio/pages/taxonomy-page.tsx": "route-tech-log-studio-taxonomy", "/studio/pages/studio-not-found-page.tsx": "route-tech-log-studio-not-found", "/public/pages/public-not-found-page.tsx": "route-not-found", } as const);