feat: add TechLog feature contracts

This commit is contained in:
DongHyeonka
2026-08-15 18:07:12 +09:00
parent 5479101c8c
commit 01ed1e9300
9 changed files with 2787 additions and 0 deletions
@@ -0,0 +1,27 @@
import type { components } from "./generated.ts";
type Schemas = components["schemas"];
export type RecordKind = Schemas["RecordKind"];
export type WorkingCopy = Schemas["WorkingCopy"];
export type WorkingCopyInput = Schemas["WorkingCopyInput"];
export type WorkingCopyDetail = Schemas["WorkingCopyDetail"];
export type StudioDashboard = Schemas["StudioDashboard"];
export type DocumentPage = Schemas["DocumentPage"];
export type CreateDocumentInput = Schemas["CreateDocumentInput"];
export type SaveDocumentCommand = Schemas["SaveDocumentCommand"];
export type ValidateDocumentCommand = Schemas["ValidateDocumentCommand"];
export type ValidationReport = Schemas["ValidationReport"];
export type CreatePreviewCommand = Schemas["CreatePreviewCommand"];
export type PublicPreview = Schemas["PublicPreview"];
export type PreviewDetail = Schemas["PreviewDetail"];
export type PublishDocumentCommand = Schemas["PublishDocumentCommand"];
export type PublishResult = Schemas["PublishResult"];
export type UnpublishCommand = Schemas["UnpublishCommand"];
export type PublicationAggregate = Schemas["PublicationAggregate"];
export type PublicationEvent = Schemas["PublicationEvent"];
export type PublicationListItem = Schemas["PublicationListItem"];
export type PublicationPage = Schemas["PublicationPage"];
export type PublicationSnapshot = Schemas["PublicationSnapshot"];
export type CatalogPage = Schemas["CatalogPage"];
export type ProblemDetails = Schemas["ProblemDetails"];
export type PublicRenderModel = Schemas["PublicRenderModel"];
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,984 @@
openapi: 3.1.0
info:
title: TechLog Studio API
version: 1.0.0
description: Transport contract for the TechLog Studio authoring frontend.
license: { name: Proprietary, identifier: LicenseRef-Proprietary }
servers:
- url: /
security: []
paths:
/api/v1/studio/dashboard:
get:
operationId: getStudioDashboard
summary: Get the Studio dashboard
responses:
"200": { description: Dashboard lists and totals, content: { application/json: { schema: { $ref: "#/components/schemas/StudioDashboard" } } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"503": { $ref: "#/components/responses/StudioUnavailable" }
/api/v1/studio/documents:
get:
operationId: listStudioDocuments
summary: List working copies
parameters:
- { $ref: "#/components/parameters/Query" }
- { $ref: "#/components/parameters/DocumentKind" }
- { $ref: "#/components/parameters/PublicationStatus" }
- { $ref: "#/components/parameters/NextAction" }
- { $ref: "#/components/parameters/ProjectId" }
- { $ref: "#/components/parameters/DocumentSort" }
- { $ref: "#/components/parameters/Cursor" }
- { $ref: "#/components/parameters/Limit" }
responses:
"200": { description: Working-copy cursor page, content: { application/json: { schema: { $ref: "#/components/schemas/DocumentPage" } } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"422": { $ref: "#/components/responses/RequestValidationFailed" }
"503": { $ref: "#/components/responses/StudioUnavailable" }
post:
operationId: createStudioDocument
summary: Create a working copy
parameters: [{ $ref: "#/components/parameters/IdempotencyKey" }]
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/CreateDocumentInput" } } } }
responses:
"201":
description: Created working copy
headers: { Idempotency-Replayed: { $ref: "#/components/headers/IdempotencyReplayed" } }
content: { application/json: { schema: { $ref: "#/components/schemas/WorkingCopy" } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"409": { $ref: "#/components/responses/CommandConflict" }
"422": { $ref: "#/components/responses/RequestValidationFailed" }
"503": { $ref: "#/components/responses/StudioUnavailable" }
/api/v1/studio/documents/{documentId}:
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
get:
operationId: getStudioDocument
summary: Get a working copy and its current state
responses:
"200": { description: Working-copy detail, content: { application/json: { schema: { $ref: "#/components/schemas/WorkingCopyDetail" } } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"404": { $ref: "#/components/responses/DocumentNotFound" }
"503": { $ref: "#/components/responses/StudioUnavailable" }
put:
operationId: saveStudioDocument
summary: Save a full working copy
parameters: [{ $ref: "#/components/parameters/IdempotencyKey" }]
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/SaveDocumentCommand" } } } }
responses:
"200":
description: Saved working-copy detail
headers: { Idempotency-Replayed: { $ref: "#/components/headers/IdempotencyReplayed" } }
content: { application/json: { schema: { $ref: "#/components/schemas/WorkingCopyDetail" } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"404": { $ref: "#/components/responses/DocumentNotFound" }
"409": { $ref: "#/components/responses/CommandConflict" }
"422": { $ref: "#/components/responses/RequestValidationFailed" }
"503": { $ref: "#/components/responses/StudioUnavailable" }
/api/v1/studio/documents/{documentId}/validate:
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
post:
operationId: validateStudioDocument
summary: Validate a saved working copy
parameters: [{ $ref: "#/components/parameters/IdempotencyKey" }]
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/ValidateDocumentCommand" } } } }
responses:
"200":
description: Validation report
headers: { Idempotency-Replayed: { $ref: "#/components/headers/IdempotencyReplayed" } }
content: { application/json: { schema: { $ref: "#/components/schemas/ValidationReport" } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"404": { $ref: "#/components/responses/DocumentNotFound" }
"409": { $ref: "#/components/responses/CommandConflict" }
"422": { $ref: "#/components/responses/RequestValidationFailed" }
"503": { $ref: "#/components/responses/StudioUnavailable" }
/api/v1/studio/documents/{documentId}/preview:
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
get:
operationId: getCurrentStudioPreview
summary: Get the latest preview and its computed state
responses:
"200": { description: Preview detail, content: { application/json: { schema: { $ref: "#/components/schemas/PreviewDetail" } } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"404": { $ref: "#/components/responses/PreviewNotFound" }
"503": { $ref: "#/components/responses/StudioUnavailable" }
post:
operationId: createStudioPreview
summary: Create a public-layout preview
parameters: [{ $ref: "#/components/parameters/IdempotencyKey" }]
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/CreatePreviewCommand" } } } }
responses:
"201":
description: Created preview
headers: { Idempotency-Replayed: { $ref: "#/components/headers/IdempotencyReplayed" } }
content: { application/json: { schema: { $ref: "#/components/schemas/PublicPreview" } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"404": { $ref: "#/components/responses/DocumentNotFound" }
"409": { $ref: "#/components/responses/CommandConflict" }
"422": { $ref: "#/components/responses/RequestValidationFailed" }
"503": { $ref: "#/components/responses/StudioUnavailable" }
/api/v1/studio/documents/{documentId}/publish:
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
post:
operationId: publishStudioDocument
summary: Publish or republish a validated preview
parameters: [{ $ref: "#/components/parameters/IdempotencyKey" }]
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/PublishDocumentCommand" } } } }
responses:
"200":
description: Publication aggregate and immutable event
headers: { Idempotency-Replayed: { $ref: "#/components/headers/IdempotencyReplayed" } }
content: { application/json: { schema: { $ref: "#/components/schemas/PublishResult" } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"404": { $ref: "#/components/responses/DocumentNotFound" }
"409": { $ref: "#/components/responses/CommandConflict" }
"422": { $ref: "#/components/responses/RequestValidationFailed" }
"503": { $ref: "#/components/responses/StudioUnavailable" }
/api/v1/studio/publications/{publicationId}/unpublish:
parameters: [{ $ref: "#/components/parameters/PublicationId" }]
post:
operationId: unpublishStudioPublication
summary: Unpublish the current publication
parameters: [{ $ref: "#/components/parameters/IdempotencyKey" }]
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/UnpublishCommand" } } } }
responses:
"200":
description: Updated publication aggregate and event
headers: { Idempotency-Replayed: { $ref: "#/components/headers/IdempotencyReplayed" } }
content: { application/json: { schema: { $ref: "#/components/schemas/PublishResult" } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"404": { $ref: "#/components/responses/PublicationNotFound" }
"409": { $ref: "#/components/responses/CommandConflict" }
"422": { $ref: "#/components/responses/RequestValidationFailed" }
"503": { $ref: "#/components/responses/StudioUnavailable" }
/api/v1/studio/publications:
get:
operationId: listStudioPublications
summary: List immutable publication events
description: Events are ordered by occurredAt DESC, then publicationEventId.
parameters:
- { $ref: "#/components/parameters/Query" }
- { $ref: "#/components/parameters/PublicationEventType" }
- { $ref: "#/components/parameters/Cursor" }
- { $ref: "#/components/parameters/Limit" }
responses:
"200": { description: Publication cursor page, content: { application/json: { schema: { $ref: "#/components/schemas/PublicationPage" } } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"422": { $ref: "#/components/responses/RequestValidationFailed" }
"503": { $ref: "#/components/responses/StudioUnavailable" }
/api/v1/studio/publications/{publicationEventId}/preview:
parameters: [{ $ref: "#/components/parameters/PublicationEventId" }]
get:
operationId: getStudioPublicationSnapshot
summary: Get an immutable publication snapshot
responses:
"200": { description: Publication snapshot, content: { application/json: { schema: { $ref: "#/components/schemas/PublicationSnapshot" } } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"404": { $ref: "#/components/responses/PublicationSnapshotNotFound" }
"503": { $ref: "#/components/responses/StudioUnavailable" }
/api/v1/studio/catalog:
get:
operationId: listStudioCatalog
summary: List catalog entries for editor pickers
parameters:
- { $ref: "#/components/parameters/CatalogType" }
- { $ref: "#/components/parameters/Query" }
- { $ref: "#/components/parameters/Cursor" }
- { $ref: "#/components/parameters/Limit" }
responses:
"200": { description: Catalog cursor page, content: { application/json: { schema: { $ref: "#/components/schemas/CatalogPage" } } } }
"401": { $ref: "#/components/responses/AuthenticationRequired" }
"403": { $ref: "#/components/responses/AccessDenied" }
"422": { $ref: "#/components/responses/RequestValidationFailed" }
"503": { $ref: "#/components/responses/StudioUnavailable" }
components:
parameters:
DocumentId: { name: documentId, in: path, required: true, schema: { type: string, format: uuid } }
PublicationId: { name: publicationId, in: path, required: true, schema: { type: string, format: uuid } }
PublicationEventId: { name: publicationEventId, in: path, required: true, schema: { type: string, format: uuid } }
IdempotencyKey: { name: Idempotency-Key, in: header, required: true, schema: { type: string, minLength: 1, maxLength: 200 } }
Query: { name: q, in: query, description: Free-text query normalized as part of the opaque cursor, schema: { type: string, maxLength: 100 } }
Cursor: { name: cursor, in: query, description: Opaque cursor bound to normalized filters and sort, schema: { type: string, minLength: 1, maxLength: 2000 } }
Limit: { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 20 } }
DocumentKind: { name: kind, in: query, schema: { $ref: "#/components/schemas/RecordKind" } }
PublicationStatus: { name: publicationStatus, in: query, schema: { $ref: "#/components/schemas/PublicationStatus" } }
NextAction: { name: nextAction, in: query, schema: { $ref: "#/components/schemas/NextAction" } }
ProjectId: { name: projectId, in: query, schema: { type: string, format: uuid } }
DocumentSort: { name: sort, in: query, schema: { type: string, enum: [UPDATED_DESC, UPDATED_ASC, TITLE_ASC], default: UPDATED_DESC } }
PublicationEventType: { name: type, in: query, schema: { $ref: "#/components/schemas/PublicationEventType" } }
CatalogType: { name: type, in: query, required: true, schema: { $ref: "#/components/schemas/CatalogEntryType" } }
headers:
IdempotencyReplayed: { description: True when the original result was replayed, schema: { type: boolean } }
responses:
AuthenticationRequired: { description: Authentication required, x-error-codes: [AUTHENTICATION_REQUIRED], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
AccessDenied: { description: Studio access denied, x-error-codes: [STUDIO_ACCESS_DENIED], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
DocumentNotFound: { description: Document not found, x-error-codes: [DOCUMENT_NOT_FOUND], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
PreviewNotFound: { description: Document or preview not found, x-error-codes: [DOCUMENT_NOT_FOUND, PREVIEW_NOT_FOUND], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
PublicationNotFound: { description: Publication not found, x-error-codes: [PUBLICATION_NOT_FOUND], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
PublicationSnapshotNotFound: { description: Publication event or snapshot not found, x-error-codes: [PUBLICATION_EVENT_NOT_FOUND, PUBLICATION_SNAPSHOT_NOT_FOUND], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
CommandConflict:
description: Command conflicts with current state, freshness, or idempotency
x-error-codes: [VERSION_CONFLICT, PUBLICATION_CONFLICT, VALIDATION_STALE, PREVIEW_STALE, PREVIEW_EXPIRED, IDEMPOTENCY_KEY_REUSED]
content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } }
RequestValidationFailed: { description: Request validation failed, x-error-codes: [REQUEST_VALIDATION_FAILED], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
StudioUnavailable: { description: Studio unavailable, x-error-codes: [STUDIO_UNAVAILABLE], content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
schemas:
RecordKind: { type: string, enum: [CASE, REFERENCE, QUESTION] }
PublicationStatus: { type: string, enum: [NEVER_PUBLISHED, PUBLISHED, UNPUBLISHED] }
NextAction: { type: string, enum: [CONTINUE_EDITING, VALIDATE, FIX_VALIDATION, CREATE_PREVIEW, PUBLISH, NONE] }
RelationInput:
type: object
additionalProperties: false
required: [id, targetId, reason, order]
properties:
id: { type: [string, "null"], format: uuid }
targetId: { type: [string, "null"], format: uuid }
reason: { type: string, maxLength: 100000 }
order: { type: integer, minimum: 0 }
Relation:
type: object
additionalProperties: false
required: [id, targetId, reason, order]
properties:
id: { type: string, format: uuid }
targetId: { type: string, format: uuid }
reason: { type: string, maxLength: 100000 }
order: { type: integer, minimum: 0 }
OrderedText:
type: object
additionalProperties: false
required: [id, text, order]
properties:
id: { type: string, format: uuid }
text: { type: string, minLength: 1, maxLength: 100000 }
order: { type: integer, minimum: 0 }
ReferenceRule:
type: object
additionalProperties: false
required: [id, title, body, order]
properties:
id: { type: string, format: uuid }
title: { type: string, minLength: 1, maxLength: 120 }
body: { type: string, minLength: 1, maxLength: 100000 }
order: { type: integer, minimum: 0 }
QuestionOption:
type: object
additionalProperties: false
required: [id, title, description, order]
properties:
id: { type: string, format: uuid }
title: { type: string, minLength: 1, maxLength: 120 }
description: { type: string, maxLength: 100000 }
order: { type: integer, minimum: 0 }
QuestionResolution:
type: object
additionalProperties: false
required: [summary, evidenceTargetId, linkLabel]
properties:
summary: { type: string, maxLength: 100000 }
evidenceTargetId: { type: [string, "null"], format: uuid }
linkLabel: { type: string, maxLength: 120 }
WorkingCopyInputBase:
type: object
required: [kind, title, slug, summary, topicId, projectId, relations]
properties:
kind: { $ref: "#/components/schemas/RecordKind" }
title: { type: string, maxLength: 120 }
slug:
oneOf:
- { type: string, const: "" }
- { type: string, minLength: 3, maxLength: 100, pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$" }
summary: { type: string, maxLength: 300 }
topicId: { type: [string, "null"], format: uuid }
projectId: { type: [string, "null"], format: uuid }
relations: { type: array, maxItems: 20, items: { $ref: "#/components/schemas/RelationInput" } }
CaseInput:
unevaluatedProperties: false
allOf:
- { $ref: "#/components/schemas/WorkingCopyInputBase" }
- type: object
required: [kind, problem, conclusion, environment, reproduction, lastVerifiedOn, bodyMarkdown]
properties:
kind: { type: string, const: CASE }
problem: { type: string, maxLength: 100000 }
conclusion: { type: string, maxLength: 100000 }
environment: { type: string, maxLength: 100000 }
reproduction: { type: string, maxLength: 100000 }
lastVerifiedOn: { type: [string, "null"], format: date }
bodyMarkdown: { type: string, maxLength: 100000 }
ReferenceInput:
unevaluatedProperties: false
allOf:
- { $ref: "#/components/schemas/WorkingCopyInputBase" }
- type: object
required: [kind, purpose, rules, applyWhen, exceptions, examples, verifiedOn]
properties:
kind: { type: string, const: REFERENCE }
purpose: { type: string, maxLength: 100000 }
rules: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/ReferenceRule" } }
applyWhen: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
exceptions: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
examples: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
verifiedOn: { type: [string, "null"], format: date }
QuestionInput:
unevaluatedProperties: false
allOf:
- { $ref: "#/components/schemas/WorkingCopyInputBase" }
- type: object
required: [kind, questionStatus, facts, assumptions, unknowns, constraints, options, nextValidation, resolution]
properties:
kind: { type: string, const: QUESTION }
questionStatus: { type: [string, "null"], enum: [OPEN, RESOLVED, null] }
facts: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
assumptions: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
unknowns: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
constraints: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
options: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/QuestionOption" } }
nextValidation: { type: string, maxLength: 100000 }
resolution:
oneOf:
- { $ref: "#/components/schemas/QuestionResolution" }
- { type: "null" }
WorkingCopyInput:
oneOf:
- { $ref: "#/components/schemas/CaseInput" }
- { $ref: "#/components/schemas/ReferenceInput" }
- { $ref: "#/components/schemas/QuestionInput" }
discriminator:
propertyName: kind
mapping:
CASE: "#/components/schemas/CaseInput"
REFERENCE: "#/components/schemas/ReferenceInput"
QUESTION: "#/components/schemas/QuestionInput"
WorkingCopyBase:
allOf:
- { $ref: "#/components/schemas/WorkingCopyInputBase" }
- type: object
required: [id, version, relations, updatedAt]
properties:
id: { type: string, format: uuid }
version: { type: integer, minimum: 1 }
relations: { type: array, maxItems: 20, items: { $ref: "#/components/schemas/Relation" } }
updatedAt: { type: string, format: date-time }
CaseWorkingCopy:
unevaluatedProperties: false
allOf:
- { $ref: "#/components/schemas/WorkingCopyBase" }
- type: object
required: [kind, problem, conclusion, environment, reproduction, lastVerifiedOn, bodyMarkdown]
properties:
kind: { type: string, const: CASE }
problem: { type: string, maxLength: 100000 }
conclusion: { type: string, maxLength: 100000 }
environment: { type: string, maxLength: 100000 }
reproduction: { type: string, maxLength: 100000 }
lastVerifiedOn: { type: [string, "null"], format: date }
bodyMarkdown: { type: string, maxLength: 100000 }
ReferenceWorkingCopy:
unevaluatedProperties: false
allOf:
- { $ref: "#/components/schemas/WorkingCopyBase" }
- type: object
required: [kind, purpose, rules, applyWhen, exceptions, examples, verifiedOn]
properties:
kind: { type: string, const: REFERENCE }
purpose: { type: string, maxLength: 100000 }
rules: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/ReferenceRule" } }
applyWhen: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
exceptions: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
examples: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
verifiedOn: { type: [string, "null"], format: date }
QuestionWorkingCopy:
unevaluatedProperties: false
allOf:
- { $ref: "#/components/schemas/WorkingCopyBase" }
- type: object
required: [kind, questionStatus, facts, assumptions, unknowns, constraints, options, nextValidation, resolution]
properties:
kind: { type: string, const: QUESTION }
questionStatus: { type: [string, "null"], enum: [OPEN, RESOLVED, null] }
facts: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
assumptions: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
unknowns: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
constraints: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
options: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/QuestionOption" } }
nextValidation: { type: string, maxLength: 100000 }
resolution:
oneOf:
- { $ref: "#/components/schemas/QuestionResolution" }
- { type: "null" }
WorkingCopy:
oneOf:
- { $ref: "#/components/schemas/CaseWorkingCopy" }
- { $ref: "#/components/schemas/ReferenceWorkingCopy" }
- { $ref: "#/components/schemas/QuestionWorkingCopy" }
discriminator:
propertyName: kind
mapping:
CASE: "#/components/schemas/CaseWorkingCopy"
REFERENCE: "#/components/schemas/ReferenceWorkingCopy"
QUESTION: "#/components/schemas/QuestionWorkingCopy"
CreateDocumentInput: { $ref: "#/components/schemas/WorkingCopyInput" }
SaveDocumentCommand:
type: object
additionalProperties: false
required: [expectedVersion, document]
properties:
expectedVersion: { type: integer, minimum: 1 }
document: { $ref: "#/components/schemas/WorkingCopyInput" }
ValidateDocumentCommand:
type: object
additionalProperties: false
required: [expectedVersion]
properties: { expectedVersion: { type: integer, minimum: 1 } }
ValidationIssue:
type: object
additionalProperties: false
required: [code, severity, path, message]
properties:
code: { type: string, minLength: 1, maxLength: 100 }
severity: { type: string, enum: [ERROR, WARNING] }
path:
type: string
pattern: "^(?:/(?:[^~/]|~0|~1)*)*$"
description: JSON Pointer to the affected field
message: { type: string, minLength: 1, maxLength: 1000 }
ValidationReport:
type: object
additionalProperties: false
required: [validationId, documentId, validatedVersion, status, issues, validatedAt, validUntil, dependencyRevision]
properties:
validationId: { type: string, format: uuid }
documentId: { type: string, format: uuid }
validatedVersion: { type: integer, minimum: 1 }
status: { type: string, enum: [INVALID, WARNINGS, VALID] }
issues: { type: array, maxItems: 200, items: { $ref: "#/components/schemas/ValidationIssue" } }
validatedAt: { type: string, format: date-time }
validUntil: { type: string, format: date-time }
dependencyRevision: { type: string, minLength: 1, maxLength: 200 }
CreatePreviewCommand:
type: object
additionalProperties: false
required: [expectedVersion, validationId]
properties:
expectedVersion: { type: integer, minimum: 1 }
validationId: { type: string, format: uuid }
PublishDocumentCommand:
type: object
additionalProperties: false
required: [expectedVersion, validationId, previewId, acknowledgedWarningCodes]
properties:
expectedVersion: { type: integer, minimum: 1 }
validationId: { type: string, format: uuid }
previewId: { type: string, format: uuid }
acknowledgedWarningCodes: { type: array, uniqueItems: true, maxItems: 200, items: { type: string, minLength: 1, maxLength: 100 } }
UnpublishCommand:
type: object
additionalProperties: false
required: [expectedPublicationRevision]
properties: { expectedPublicationRevision: { type: integer, minimum: 1 } }
DisplayTarget:
type: object
additionalProperties: false
required: [id, label, publicPath]
properties:
id: { type: string, format: uuid }
label: { type: string, minLength: 1, maxLength: 200 }
publicPath: { type: [string, "null"], maxLength: 500 }
ResolvedRelation:
type: object
additionalProperties: false
required: [id, targetId, targetKind, title, publicPath, reason, order]
properties:
id: { type: string, format: uuid }
targetId: { type: string, format: uuid }
targetKind: { type: string, enum: [CASE, REFERENCE, QUESTION, PROJECT, PROJECT_DECISION] }
title: { type: string, minLength: 1, maxLength: 200 }
publicPath: { type: [string, "null"], maxLength: 500 }
reason: { type: string, maxLength: 100000 }
order: { type: integer, minimum: 0 }
RenderContext:
type: object
additionalProperties: false
required: [generatedAt, dependencyRevision]
properties:
generatedAt: { type: string, format: date-time }
dependencyRevision: { type: string, minLength: 1, maxLength: 200 }
PublicRenderModelBase:
type: object
required: [kind, slug, title, summary, publicPath, topic, project, relations, renderContext]
properties:
kind: { $ref: "#/components/schemas/RecordKind" }
slug: { type: string, minLength: 3, maxLength: 100, pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$" }
title: { type: string, minLength: 1, maxLength: 120 }
summary: { type: string, minLength: 1, maxLength: 300 }
publicPath: { type: string, minLength: 1, maxLength: 500 }
topic: { $ref: "#/components/schemas/DisplayTarget" }
project:
oneOf:
- { $ref: "#/components/schemas/DisplayTarget" }
- { type: "null" }
relations: { type: array, maxItems: 20, items: { $ref: "#/components/schemas/ResolvedRelation" } }
renderContext: { $ref: "#/components/schemas/RenderContext" }
InlineText:
type: object
additionalProperties: false
required: [type, text]
properties:
type: { type: string, const: TEXT }
text: { type: string, minLength: 1, maxLength: 100000 }
InlineContainer:
type: object
required: [type, children]
properties:
type: { type: string, enum: [EMPHASIS, STRONG] }
children: { type: array, maxItems: 1000, items: { $ref: "#/components/schemas/Inline" } }
InlineCode:
type: object
additionalProperties: false
required: [type, code]
properties:
type: { type: string, const: INLINE_CODE }
code: { type: string, minLength: 1, maxLength: 100000 }
InlineLink:
type: object
additionalProperties: false
required: [type, label, href]
properties:
type: { type: string, const: LINK }
label: { type: string, minLength: 1, maxLength: 100000 }
href: { type: string, format: uri, maxLength: 2000 }
InlineStatus:
type: object
additionalProperties: false
required: [type, label, tone]
properties:
type: { type: string, const: STATUS }
label: { type: string, minLength: 1, maxLength: 120 }
tone: { type: string, enum: [warning, evidence, neutral] }
Inline:
oneOf:
- { $ref: "#/components/schemas/InlineText" }
- { $ref: "#/components/schemas/InlineEmphasis" }
- { $ref: "#/components/schemas/InlineStrong" }
- { $ref: "#/components/schemas/InlineCode" }
- { $ref: "#/components/schemas/InlineLink" }
- { $ref: "#/components/schemas/InlineStatus" }
discriminator:
propertyName: type
mapping:
TEXT: "#/components/schemas/InlineText"
EMPHASIS: "#/components/schemas/InlineEmphasis"
STRONG: "#/components/schemas/InlineStrong"
INLINE_CODE: "#/components/schemas/InlineCode"
LINK: "#/components/schemas/InlineLink"
STATUS: "#/components/schemas/InlineStatus"
InlineEmphasis:
unevaluatedProperties: false
allOf:
- { $ref: "#/components/schemas/InlineContainer" }
- { type: object, properties: { type: { type: string, const: EMPHASIS } } }
InlineStrong:
unevaluatedProperties: false
allOf:
- { $ref: "#/components/schemas/InlineContainer" }
- { type: object, properties: { type: { type: string, const: STRONG } } }
HeadingBlock:
type: object
additionalProperties: false
required: [type, id, level, content]
properties:
type: { type: string, const: HEADING }
id: { type: string, minLength: 1, maxLength: 200 }
level: { type: integer, minimum: 2, maximum: 4 }
content: { type: array, maxItems: 1000, items: { $ref: "#/components/schemas/Inline" } }
ParagraphBlock:
type: object
additionalProperties: false
required: [type, content]
properties:
type: { type: string, const: PARAGRAPH }
content: { type: array, maxItems: 1000, items: { $ref: "#/components/schemas/Inline" } }
BlockquoteBlock:
type: object
additionalProperties: false
required: [type, content]
properties:
type: { type: string, const: BLOCKQUOTE }
content: { type: array, maxItems: 1000, items: { $ref: "#/components/schemas/Inline" } }
ListItem:
type: object
additionalProperties: false
required: [id, content]
properties:
id: { type: string, minLength: 1, maxLength: 200 }
content: { type: array, maxItems: 1000, items: { $ref: "#/components/schemas/Inline" } }
ListBlockBase:
type: object
required: [type, items]
properties:
type: { type: string, enum: [UNORDERED_LIST, ORDERED_LIST] }
items: { type: array, maxItems: 1000, items: { $ref: "#/components/schemas/ListItem" } }
UnorderedListBlock:
unevaluatedProperties: false
allOf:
- { $ref: "#/components/schemas/ListBlockBase" }
- { type: object, properties: { type: { type: string, const: UNORDERED_LIST } } }
OrderedListBlock:
unevaluatedProperties: false
allOf:
- { $ref: "#/components/schemas/ListBlockBase" }
- { type: object, properties: { type: { type: string, const: ORDERED_LIST } } }
CodeBlock:
type: object
additionalProperties: false
required: [type, code, language, label]
properties:
type: { type: string, const: CODE_BLOCK }
code: { type: string, maxLength: 100000 }
language: { type: [string, "null"], maxLength: 100 }
label: { type: [string, "null"], maxLength: 200 }
DataTableColumn:
type: object
additionalProperties: false
required: [id, label, alignment]
properties:
id: { type: string, minLength: 1, maxLength: 200 }
label: { type: string, minLength: 1, maxLength: 500 }
alignment: { type: string, enum: [LEFT, CENTER, RIGHT] }
DataTableCell:
type: object
additionalProperties: false
required: [columnId, content]
properties:
columnId: { type: string, minLength: 1, maxLength: 200 }
content: { type: array, maxItems: 1000, items: { $ref: "#/components/schemas/Inline" } }
DataTableRow:
type: object
additionalProperties: false
required: [id, cells]
properties:
id: { type: string, minLength: 1, maxLength: 200 }
cells: { type: array, maxItems: 100, items: { $ref: "#/components/schemas/DataTableCell" } }
DataTableBlock:
type: object
additionalProperties: false
required: [type, id, caption, rowHeaderColumn, columns, rows]
properties:
type: { type: string, const: DATA_TABLE }
id: { type: string, minLength: 1, maxLength: 200 }
caption: { type: string, maxLength: 1000 }
rowHeaderColumn: { type: [integer, "null"], minimum: 1 }
columns: { type: array, minItems: 1, maxItems: 100, items: { $ref: "#/components/schemas/DataTableColumn" } }
rows: { type: array, maxItems: 1000, items: { $ref: "#/components/schemas/DataTableRow" } }
CalloutBlock:
type: object
additionalProperties: false
required: [type, tone, label, content]
properties:
type: { type: string, const: CALLOUT }
tone: { type: string, enum: [warning, info] }
label: { type: string, maxLength: 200 }
content: { type: array, maxItems: 1000, items: { $ref: "#/components/schemas/Inline" } }
EvidenceFigureBlock:
type: object
additionalProperties: false
required: [type, key, alt, caption, zoom]
properties:
type: { type: string, const: EVIDENCE_FIGURE }
key: { type: string, minLength: 1, maxLength: 200 }
alt: { type: string, minLength: 1, maxLength: 1000 }
caption: { type: string, maxLength: 1000 }
zoom: { type: boolean }
CaseRenderBlock:
oneOf:
- { $ref: "#/components/schemas/HeadingBlock" }
- { $ref: "#/components/schemas/ParagraphBlock" }
- { $ref: "#/components/schemas/BlockquoteBlock" }
- { $ref: "#/components/schemas/UnorderedListBlock" }
- { $ref: "#/components/schemas/OrderedListBlock" }
- { $ref: "#/components/schemas/CodeBlock" }
- { $ref: "#/components/schemas/DataTableBlock" }
- { $ref: "#/components/schemas/CalloutBlock" }
- { $ref: "#/components/schemas/EvidenceFigureBlock" }
discriminator:
propertyName: type
mapping:
HEADING: "#/components/schemas/HeadingBlock"
PARAGRAPH: "#/components/schemas/ParagraphBlock"
BLOCKQUOTE: "#/components/schemas/BlockquoteBlock"
UNORDERED_LIST: "#/components/schemas/UnorderedListBlock"
ORDERED_LIST: "#/components/schemas/OrderedListBlock"
CODE_BLOCK: "#/components/schemas/CodeBlock"
DATA_TABLE: "#/components/schemas/DataTableBlock"
CALLOUT: "#/components/schemas/CalloutBlock"
EVIDENCE_FIGURE: "#/components/schemas/EvidenceFigureBlock"
CasePublicRenderModel:
unevaluatedProperties: false
allOf:
- { $ref: "#/components/schemas/PublicRenderModelBase" }
- type: object
required: [kind, problem, conclusion, environment, reproduction, lastVerifiedOn, bodyBlocks]
properties:
kind: { type: string, const: CASE }
problem: { type: string, minLength: 1, maxLength: 100000 }
conclusion: { type: string, minLength: 1, maxLength: 100000 }
environment: { type: string, maxLength: 100000 }
reproduction: { type: string, maxLength: 100000 }
lastVerifiedOn: { type: string, format: date }
bodyBlocks: { type: array, maxItems: 10000, items: { $ref: "#/components/schemas/CaseRenderBlock" } }
ReferencePublicRenderModel:
unevaluatedProperties: false
allOf:
- { $ref: "#/components/schemas/PublicRenderModelBase" }
- type: object
required: [kind, purpose, rules, applyWhen, exceptions, examples, verifiedOn]
properties:
kind: { type: string, const: REFERENCE }
purpose: { type: string, minLength: 1, maxLength: 100000 }
rules: { type: array, minItems: 1, maxItems: 50, items: { $ref: "#/components/schemas/ReferenceRule" } }
applyWhen: { type: array, minItems: 1, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
exceptions: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
examples: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
verifiedOn: { type: string, format: date }
ResolvedQuestionResolution:
type: object
additionalProperties: false
required: [summary, evidenceTarget, linkLabel]
properties:
summary: { type: string, minLength: 1, maxLength: 100000 }
evidenceTarget: { $ref: "#/components/schemas/DisplayTarget" }
linkLabel: { type: string, minLength: 1, maxLength: 120 }
QuestionPublicRenderModel:
unevaluatedProperties: false
allOf:
- { $ref: "#/components/schemas/PublicRenderModelBase" }
- type: object
required: [kind, status, facts, assumptions, unknowns, constraints, options, nextValidation, resolution]
properties:
kind: { type: string, const: QUESTION }
status: { type: string, enum: [OPEN, RESOLVED] }
facts: { type: array, minItems: 1, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
assumptions: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
unknowns: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
constraints: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
options: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/QuestionOption" } }
nextValidation: { type: string, minLength: 1, maxLength: 100000 }
resolution:
oneOf:
- { $ref: "#/components/schemas/ResolvedQuestionResolution" }
- { type: "null" }
PublicRenderModel:
oneOf:
- { $ref: "#/components/schemas/CasePublicRenderModel" }
- { $ref: "#/components/schemas/ReferencePublicRenderModel" }
- { $ref: "#/components/schemas/QuestionPublicRenderModel" }
discriminator:
propertyName: kind
mapping:
CASE: "#/components/schemas/CasePublicRenderModel"
REFERENCE: "#/components/schemas/ReferencePublicRenderModel"
QUESTION: "#/components/schemas/QuestionPublicRenderModel"
PublicPreview:
type: object
additionalProperties: false
required: [previewId, documentId, previewVersion, validationId, createdAt, expiresAt, renderModel]
properties:
previewId: { type: string, format: uuid }
documentId: { type: string, format: uuid }
previewVersion: { type: integer, minimum: 1 }
validationId: { type: string, format: uuid }
createdAt: { type: string, format: date-time }
expiresAt: { type: string, format: date-time }
renderModel: { $ref: "#/components/schemas/PublicRenderModel" }
PreviewDetail:
type: object
additionalProperties: false
required: [preview, state, currentDocumentVersion, currentValidationId]
properties:
preview: { $ref: "#/components/schemas/PublicPreview" }
state: { type: string, enum: [CURRENT, STALE, EXPIRED] }
currentDocumentVersion: { type: integer, minimum: 1 }
currentValidationId: { type: [string, "null"], format: uuid }
PublicationAggregate:
type: object
additionalProperties: false
required: [publicationId, documentId, status, publishedVersion, publicationRevision, latestEventId, publicPath, updatedAt]
properties:
publicationId: { type: string, format: uuid }
documentId: { type: string, format: uuid }
status: { type: string, enum: [PUBLISHED, UNPUBLISHED] }
publishedVersion: { type: integer, minimum: 1 }
publicationRevision: { type: integer, minimum: 1 }
latestEventId: { type: string, format: uuid }
publicPath: { type: string, minLength: 1, maxLength: 500 }
updatedAt: { type: string, format: date-time }
PublicationEventType: { type: string, enum: [PUBLISHED, REPUBLISHED, UNPUBLISHED] }
PublicationEvent:
type: object
additionalProperties: false
required: [publicationEventId, publicationId, documentId, type, occurredAt, publishedVersion, sourcePublishedEventId, snapshotAvailable]
properties:
publicationEventId: { type: string, format: uuid }
publicationId: { type: string, format: uuid }
documentId: { type: string, format: uuid }
type: { $ref: "#/components/schemas/PublicationEventType" }
occurredAt: { type: string, format: date-time }
publishedVersion: { type: integer, minimum: 1 }
sourcePublishedEventId: { type: [string, "null"], format: uuid }
snapshotAvailable: { type: boolean }
PublicationSnapshot:
type: object
additionalProperties: false
required: [event, renderModel]
properties:
event: { $ref: "#/components/schemas/PublicationEvent" }
renderModel: { $ref: "#/components/schemas/PublicRenderModel" }
PublishResult:
type: object
additionalProperties: false
required: [publication, event]
properties:
publication: { $ref: "#/components/schemas/PublicationAggregate" }
event: { $ref: "#/components/schemas/PublicationEvent" }
DocumentSummary:
type: object
additionalProperties: false
required: [id, title, kind, project, updatedAt, publicationStatus, publishedVersion, hasUnpublishedChanges, nextAction]
properties:
id: { type: string, format: uuid }
title: { type: string, maxLength: 120 }
kind: { $ref: "#/components/schemas/RecordKind" }
project:
oneOf:
- { $ref: "#/components/schemas/DisplayTarget" }
- { type: "null" }
updatedAt: { type: string, format: date-time }
publicationStatus: { $ref: "#/components/schemas/PublicationStatus" }
publishedVersion: { type: [integer, "null"], minimum: 1 }
hasUnpublishedChanges: { type: boolean }
nextAction: { $ref: "#/components/schemas/NextAction" }
PublicationAction: { type: string, enum: [VIEW_SNAPSHOT, VIEW_SOURCE_SNAPSHOT, UNPUBLISH] }
PublicationListItem:
type: object
additionalProperties: false
required: [event, publication, document, availableActions]
properties:
event: { $ref: "#/components/schemas/PublicationEvent" }
publication: { $ref: "#/components/schemas/PublicationAggregate" }
document: { $ref: "#/components/schemas/DocumentSummary" }
availableActions: { type: array, uniqueItems: true, maxItems: 3, items: { $ref: "#/components/schemas/PublicationAction" } }
WorkingCopyDetail:
type: object
additionalProperties: false
required: [document, currentValidation, latestPreview, currentPublication, dependencyRevision]
properties:
document: { $ref: "#/components/schemas/WorkingCopy" }
currentValidation:
oneOf:
- { $ref: "#/components/schemas/ValidationReport" }
- { type: "null" }
latestPreview:
oneOf:
- { $ref: "#/components/schemas/PublicPreview" }
- { type: "null" }
currentPublication:
oneOf:
- { $ref: "#/components/schemas/PublicationAggregate" }
- { type: "null" }
dependencyRevision: { type: string, minLength: 1, maxLength: 200 }
StudioDashboard:
type: object
additionalProperties: false
required: [continueWriting, readyToPublish, recentPublications, totals]
properties:
continueWriting: { type: array, maxItems: 5, items: { $ref: "#/components/schemas/DocumentSummary" } }
readyToPublish: { type: array, maxItems: 5, items: { $ref: "#/components/schemas/DocumentSummary" } }
recentPublications: { type: array, maxItems: 5, items: { $ref: "#/components/schemas/PublicationListItem" } }
totals: { $ref: "#/components/schemas/DashboardTotals" }
DashboardTotals:
type: object
additionalProperties: false
required: [documents, readyToPublish, publications]
properties:
documents: { type: integer, minimum: 0 }
readyToPublish: { type: integer, minimum: 0 }
publications: { type: integer, minimum: 0 }
DocumentPage:
type: object
additionalProperties: false
required: [items, nextCursor]
properties:
items: { type: array, maxItems: 100, items: { $ref: "#/components/schemas/DocumentSummary" } }
nextCursor: { type: [string, "null"], maxLength: 2000 }
PublicationPage:
type: object
additionalProperties: false
required: [items, nextCursor]
properties:
items: { type: array, maxItems: 100, items: { $ref: "#/components/schemas/PublicationListItem" } }
nextCursor: { type: [string, "null"], maxLength: 2000 }
CatalogEntryType: { type: string, enum: [TOPIC, PROJECT, RELATION, EVIDENCE] }
CatalogEntry:
type: object
additionalProperties: false
required: [id, type, label, dependencyRevision]
properties:
id: { type: string, format: uuid }
type: { $ref: "#/components/schemas/CatalogEntryType" }
label: { type: string, minLength: 1, maxLength: 200 }
kind: { type: string, enum: [CASE, REFERENCE, QUESTION, PROJECT, PROJECT_DECISION] }
publicPath: { type: string, minLength: 1, maxLength: 500 }
dependencyRevision: { type: string, minLength: 1, maxLength: 200 }
CatalogPage:
type: object
additionalProperties: false
required: [items, nextCursor]
properties:
items: { type: array, maxItems: 100, items: { $ref: "#/components/schemas/CatalogEntry" } }
nextCursor: { type: [string, "null"], maxLength: 2000 }
FieldError:
type: object
additionalProperties: false
required: [path, message]
properties:
path:
type: string
pattern: "^(?:/(?:[^~/]|~0|~1)*)*$"
description: JSON Pointer to the invalid field
message: { type: string, minLength: 1, maxLength: 1000 }
ProblemDetails:
type: object
additionalProperties: true
required: [type, title, status, detail, code]
properties:
type: { type: string, format: uri-reference }
title: { type: string, minLength: 1, maxLength: 200 }
status: { type: integer, minimum: 400, maximum: 599 }
detail: { type: string, minLength: 1, maxLength: 5000 }
code:
type: string
enum: [AUTHENTICATION_REQUIRED, STUDIO_ACCESS_DENIED, DOCUMENT_NOT_FOUND, PREVIEW_NOT_FOUND, PUBLICATION_NOT_FOUND, PUBLICATION_EVENT_NOT_FOUND, PUBLICATION_SNAPSHOT_NOT_FOUND, VERSION_CONFLICT, PUBLICATION_CONFLICT, VALIDATION_STALE, PREVIEW_STALE, PREVIEW_EXPIRED, IDEMPOTENCY_KEY_REUSED, REQUEST_VALIDATION_FAILED, STUDIO_UNAVAILABLE]
instance: { type: string, format: uri-reference }
traceId: { type: string, maxLength: 200 }
fieldErrors: { type: array, maxItems: 200, items: { $ref: "#/components/schemas/FieldError" } }
latestDocument: { $ref: "#/components/schemas/WorkingCopyDetail" }
latestPublication: { $ref: "#/components/schemas/PublicationAggregate" }
conflictingFields:
type: array
uniqueItems: true
maxItems: 200
items:
type: string
pattern: "^(?:/(?:[^~/]|~0|~1)*)*$"
retryable: { type: boolean }