6587 lines
227 KiB
YAML
6587 lines
227 KiB
YAML
openapi: 3.1.0
|
|
info:
|
|
title: ItWorx Knowledge Hub API
|
|
version: 1.0.0-rc.1
|
|
description: |
|
|
Versioned API for the ItWorx Knowledge Hub web client and scoped automation.
|
|
This is not an MCP transport. The future MCP service will consume this API.
|
|
servers:
|
|
- url: /api/v1
|
|
tags:
|
|
- name: System
|
|
- name: Setup
|
|
- name: Auth
|
|
- name: Admin
|
|
- name: Search
|
|
- name: Rag
|
|
- name: Pages
|
|
- name: Drafts
|
|
- name: Templates
|
|
- name: Attachments
|
|
- name: Imports
|
|
- name: Source batch purge
|
|
- name: Projects
|
|
- name: Decisions
|
|
- name: Runbooks
|
|
- name: Service accounts
|
|
- name: Backups
|
|
- name: Infrastructure
|
|
- name: Activity
|
|
- name: Workspace
|
|
- name: Ingestion proposals
|
|
- name: Preferences
|
|
- name: DataSources
|
|
- name: Jobs
|
|
security:
|
|
- cookieAuth: []
|
|
- bearerAuth: []
|
|
paths:
|
|
/ingestion-proposals:
|
|
get:
|
|
tags: [Ingestion proposals]
|
|
operationId: listIngestionProposals
|
|
description: Human review queue only. Page/pageSize and all filters are evaluated server-side; cursor/limit remain supported for existing clients. Project and risk are accepted but return no matches until this version persists those attributes, rather than fabricating filter results.
|
|
parameters:
|
|
- name: status
|
|
in: query
|
|
schema: { type: string }
|
|
- $ref: "#/components/parameters/Cursor"
|
|
- $ref: "#/components/parameters/Limit"
|
|
- name: page
|
|
in: query
|
|
schema: { type: integer, minimum: 1, maximum: 10000, default: 1 }
|
|
- name: pageSize
|
|
in: query
|
|
schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
|
|
- name: projectId
|
|
in: query
|
|
schema: { type: string, format: uuid }
|
|
- name: submitterId
|
|
in: query
|
|
schema: { type: string, format: uuid }
|
|
- name: minAgeDays
|
|
in: query
|
|
description: Minimum proposal age, calculated from the first immutable proposal revision.
|
|
schema: { type: integer, minimum: 0, maximum: 3650 }
|
|
- name: risk
|
|
in: query
|
|
schema: { type: string, enum: [low, medium, high] }
|
|
- name: documentType
|
|
in: query
|
|
schema: { type: string, maxLength: 64 }
|
|
responses:
|
|
"200": { description: Review queue, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalListResponse" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
post:
|
|
tags: [Ingestion proposals]
|
|
operationId: createIngestionProposal
|
|
description: >-
|
|
Requires an Idempotency-Key and an editor cookie or an ingestion:write service
|
|
token. Repeating the same request with the same caller and key replays the
|
|
original 201 response. Reusing that key with a different request body returns
|
|
a 409 problem with type https://itworx.dev/problems/idempotency-key-conflict.
|
|
parameters:
|
|
- $ref: "#/components/parameters/RequiredIdempotencyKey"
|
|
requestBody:
|
|
required: true
|
|
content: { application/json: { schema: { $ref: "#/components/schemas/CreateIngestionProposalRequest" } } }
|
|
responses:
|
|
"201": { description: Proposal created, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
|
|
"400": { description: Idempotency-Key is missing or invalid, content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
"409":
|
|
description: Idempotency-Key was already used with a different request body.
|
|
content:
|
|
application/problem+json:
|
|
schema: { $ref: "#/components/schemas/ProblemDetails" }
|
|
examples:
|
|
idempotencyKeyConflict:
|
|
value:
|
|
type: https://itworx.dev/problems/idempotency-key-conflict
|
|
title: Idempotency-Key hergebruikt met een andere aanvraag.
|
|
status: 409
|
|
correlationId: pilot-contract
|
|
detail: Deze Idempotency-Key is eerder gebruikt voor een andere aanvraag-inhoud.
|
|
"422": { $ref: "#/components/responses/BadRequest" }
|
|
/ingestion-proposals/{proposalId}:
|
|
parameters:
|
|
- name: proposalId
|
|
in: path
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
get:
|
|
tags: [Ingestion proposals]
|
|
operationId: getIngestionProposal
|
|
description: A human reviewer may read any workspace proposal. An ingestion:write token may read only the proposal it submitted.
|
|
responses:
|
|
"200": { description: Complete metadata-only review/status dossier, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
patch:
|
|
tags: [Ingestion proposals]
|
|
operationId: patchIngestionProposal
|
|
description: >-
|
|
Requires If-Match. Human editors may change manifest/rationale; an edit to an
|
|
Approved proposal clears its approval and returns it to ReadyForReview. Only
|
|
the submitting ingestion:write account may submit analysis, which advances the
|
|
proposal to ReadyForReview. A stale proposal ETag returns 412 with type
|
|
https://itworx.local/problems/ingestion-concurrency-conflict.
|
|
parameters:
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
requestBody:
|
|
required: true
|
|
content: { application/json: { schema: { $ref: "#/components/schemas/PatchIngestionProposalRequest" } } }
|
|
responses:
|
|
"200": { description: Updated proposal, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
"412":
|
|
description: Proposal ETag does not match the current version.
|
|
content:
|
|
application/problem+json:
|
|
schema: { $ref: "#/components/schemas/ProblemDetails" }
|
|
examples:
|
|
ingestionConcurrencyConflict:
|
|
value:
|
|
type: https://itworx.local/problems/ingestion-concurrency-conflict
|
|
title: Versieconflict.
|
|
status: 412
|
|
correlationId: pilot-contract
|
|
/ingestion-proposals/{proposalId}/sources:
|
|
post:
|
|
tags: [Ingestion proposals]
|
|
operationId: stageIngestionProposalSource
|
|
description: Requires If-Match and Idempotency-Key. Upload is quarantined, signature-validated and stored only as immutable metadata plus a content-addressed attachment reference; neither bytes nor storage paths are returned.
|
|
parameters:
|
|
- name: proposalId
|
|
in: path
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
- $ref: "#/components/parameters/RequiredIdempotencyKey"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
multipart/form-data:
|
|
schema:
|
|
type: object
|
|
required: [file]
|
|
properties: { file: { type: string, format: binary } }
|
|
responses:
|
|
"200": { description: Source staged, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
|
|
"413": { description: Source exceeds the configured size limit }
|
|
"422": { $ref: "#/components/responses/BadRequest" }
|
|
/ingestion-proposals/{proposalId}/submit:
|
|
post:
|
|
tags: [Ingestion proposals]
|
|
operationId: submitManualIngestionProposal
|
|
description: >-
|
|
Human submitter only. Moves a proposal with publication details and at least
|
|
one staged source into ReadyForReview without claiming automated analysis.
|
|
Requires If-Match. A separate human review and approval remain mandatory.
|
|
parameters:
|
|
- name: proposalId
|
|
in: path
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
responses:
|
|
"200": { description: Submitted for review, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
"409": { $ref: "#/components/responses/Conflict" }
|
|
"412": { $ref: "#/components/responses/PreconditionFailed" }
|
|
"428": { $ref: "#/components/responses/PreconditionRequired" }
|
|
/ingestion-proposals/{proposalId}/request-changes:
|
|
post:
|
|
tags: [Ingestion proposals]
|
|
operationId: requestIngestionProposalChanges
|
|
description: >-
|
|
Human reviewer only. Requires If-Match and returns the updated metadata-only
|
|
dossier. Stale ETags return 412 with type
|
|
https://itworx.local/problems/ingestion-concurrency-conflict.
|
|
parameters:
|
|
- name: proposalId
|
|
in: path
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
requestBody:
|
|
required: true
|
|
content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDecisionRequest" } } }
|
|
responses:
|
|
"200": { description: Change request recorded, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
"409": { $ref: "#/components/responses/Conflict" }
|
|
"412": { $ref: "#/components/responses/PreconditionFailed" }
|
|
"428": { $ref: "#/components/responses/PreconditionRequired" }
|
|
/ingestion-proposals/{proposalId}/approve:
|
|
post:
|
|
tags: [Ingestion proposals]
|
|
operationId: approveIngestionProposal
|
|
description: >-
|
|
Human reviewer only. Service accounts cannot approve. Requires If-Match and
|
|
approves a ReadyForReview proposal without publishing it. Stale ETags return
|
|
412 with type https://itworx.local/problems/ingestion-concurrency-conflict.
|
|
parameters:
|
|
- name: proposalId
|
|
in: path
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
requestBody:
|
|
required: true
|
|
content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDecisionRequest" } } }
|
|
responses:
|
|
"200": { description: Proposal approved for later publication, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
"409": { $ref: "#/components/responses/Conflict" }
|
|
"412": { $ref: "#/components/responses/PreconditionFailed" }
|
|
"428": { $ref: "#/components/responses/PreconditionRequired" }
|
|
/ingestion-proposals/{proposalId}/reject:
|
|
post:
|
|
tags: [Ingestion proposals]
|
|
operationId: rejectIngestionProposal
|
|
description: >-
|
|
Human reviewer only. Service accounts cannot reject. Requires If-Match and
|
|
records the rejection comment on the metadata-only proposal dossier. Stale
|
|
ETags return 412 with type
|
|
https://itworx.local/problems/ingestion-concurrency-conflict.
|
|
parameters:
|
|
- name: proposalId
|
|
in: path
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
requestBody:
|
|
required: true
|
|
content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDecisionRequest" } } }
|
|
responses:
|
|
"200": { description: Proposal rejected, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
"409": { $ref: "#/components/responses/Conflict" }
|
|
"412": { $ref: "#/components/responses/PreconditionFailed" }
|
|
"428": { $ref: "#/components/responses/PreconditionRequired" }
|
|
/ingestion-proposals/{proposalId}/retry:
|
|
post:
|
|
tags: [Ingestion proposals]
|
|
operationId: retryIngestionProposal
|
|
description: Requires If-Match. An ingestion:write caller can retry only its own eligible failed proposal; a human editor may retry an eligible proposal. An optional Idempotency-Key safely replays a successful retry response.
|
|
parameters:
|
|
- name: proposalId
|
|
in: path
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
- $ref: "#/components/parameters/IdempotencyKey"
|
|
responses:
|
|
"200": { description: Eligible proposal reset for re-analysis, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
"409": { $ref: "#/components/responses/Conflict" }
|
|
"412": { $ref: "#/components/responses/PreconditionFailed" }
|
|
"428": { $ref: "#/components/responses/PreconditionRequired" }
|
|
/ingestion-proposals/{proposalId}/publish:
|
|
post:
|
|
tags: [Ingestion proposals]
|
|
operationId: publishIngestionProposal
|
|
description: >-
|
|
Human review only. Requires a human editor cookie session, If-Match and
|
|
Idempotency-Key; service accounts are forbidden from publishing. A replay with
|
|
the same key and request body returns the original success response even when
|
|
it carries the original now-stale If-Match value, and does not create a second
|
|
page revision. Reusing the key with a different body returns a typed
|
|
idempotency conflict. A stale target page ETag returns 409 with type
|
|
https://itworx.local/problems/ingestion-target-stale and moves the proposal
|
|
back to ReadyForReview.
|
|
parameters:
|
|
- name: proposalId
|
|
in: path
|
|
required: true
|
|
schema: { type: string, format: uuid }
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
- $ref: "#/components/parameters/RequiredIdempotencyKey"
|
|
responses:
|
|
"200": { description: Approved proposal published, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
"404": { $ref: "#/components/responses/NotFound" }
|
|
"409":
|
|
description: >-
|
|
Invalid state transition, stale target, or idempotency-key conflict. Typed
|
|
problem types are https://itworx.local/problems/ingestion-invalid-transition,
|
|
https://itworx.local/problems/ingestion-target-stale, and
|
|
https://itworx.dev/problems/idempotency-key-conflict.
|
|
content:
|
|
application/problem+json:
|
|
schema: { $ref: "#/components/schemas/ProblemDetails" }
|
|
examples:
|
|
staleTarget:
|
|
value:
|
|
type: https://itworx.local/problems/ingestion-target-stale
|
|
title: Publicatiedoel is gewijzigd.
|
|
status: 409
|
|
correlationId: pilot-contract
|
|
detail: Het voorstel is teruggezet naar de reviewwachtrij.
|
|
invalidTransition:
|
|
value:
|
|
type: https://itworx.local/problems/ingestion-invalid-transition
|
|
title: Conflict.
|
|
status: 409
|
|
correlationId: pilot-contract
|
|
idempotencyKeyConflict:
|
|
value:
|
|
type: https://itworx.dev/problems/idempotency-key-conflict
|
|
title: Idempotency-Key hergebruikt met een andere aanvraag.
|
|
status: 409
|
|
correlationId: pilot-contract
|
|
detail: Deze Idempotency-Key is eerder gebruikt voor een andere aanvraag-inhoud.
|
|
"412":
|
|
description: Proposal ETag is stale.
|
|
content:
|
|
application/problem+json:
|
|
schema: { $ref: "#/components/schemas/ProblemDetails" }
|
|
examples:
|
|
ingestionConcurrencyConflict:
|
|
value:
|
|
type: https://itworx.local/problems/ingestion-concurrency-conflict
|
|
title: Versieconflict.
|
|
status: 412
|
|
correlationId: pilot-contract
|
|
"428": { $ref: "#/components/responses/PreconditionRequired" }
|
|
/system/info:
|
|
get:
|
|
tags: [System]
|
|
operationId: getSystemInfo
|
|
responses:
|
|
"200":
|
|
description: Permission-filtered system information
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SystemInfo"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
/setup/status:
|
|
get:
|
|
tags: [Setup]
|
|
operationId: getSetupStatus
|
|
security: []
|
|
description: >-
|
|
Reveals only whether first-run setup is still required. Never reveals any other
|
|
installation detail to an unauthenticated caller.
|
|
responses:
|
|
"200":
|
|
description: Setup status
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SetupStatus"
|
|
/setup/complete:
|
|
post:
|
|
tags: [Setup]
|
|
operationId: completeSetup
|
|
security: []
|
|
description: >-
|
|
Single-use first-run owner setup. Requires CSRF protection and is rate limited.
|
|
Permanently fails once setup has completed.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CompleteSetupRequest"
|
|
responses:
|
|
"200":
|
|
description: Installation completed; caller is signed in
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CompleteSetupResponse"
|
|
|
|
"409":
|
|
description: Setup already completed
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"422":
|
|
description: Validation failed (email/password/workspace name)
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"429":
|
|
$ref: "#/components/responses/RateLimited"
|
|
/auth/csrf:
|
|
get:
|
|
tags: [Auth]
|
|
operationId: getCsrfToken
|
|
security: []
|
|
description: Issues the double-submit anti-forgery token required on cookie-authenticated mutations.
|
|
responses:
|
|
"200":
|
|
description: Anti-forgery token
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CsrfToken"
|
|
/auth/session:
|
|
get:
|
|
tags: [Auth]
|
|
operationId: getSession
|
|
security: []
|
|
description: >-
|
|
Always returns 200. An unauthenticated caller only ever sees
|
|
`authenticated: false` plus whether setup is required — never whether a
|
|
specific account exists.
|
|
responses:
|
|
"200":
|
|
description: Current session state
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SessionResponse"
|
|
/auth/login:
|
|
post:
|
|
tags: [Auth]
|
|
operationId: login
|
|
security: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/LoginRequest"
|
|
responses:
|
|
"200":
|
|
description: Signed in
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SessionResponse"
|
|
"401":
|
|
description: Invalid credentials (also returned for an unknown account and a disabled account)
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"429":
|
|
$ref: "#/components/responses/RateLimited"
|
|
/auth/logout:
|
|
post:
|
|
tags: [Auth]
|
|
operationId: logout
|
|
responses:
|
|
"200":
|
|
description: Signed out
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
/auth/recovery/redeem:
|
|
post:
|
|
tags: [Auth]
|
|
operationId: redeemRecoveryToken
|
|
security: []
|
|
description: Consumes a single-use operator-issued recovery token and sets a new password.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RecoveryRedeemRequest"
|
|
responses:
|
|
"200":
|
|
description: Password reset; all prior sessions for this account are invalidated
|
|
"400":
|
|
description: Invalid or expired token
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"422":
|
|
description: New password rejected
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"429":
|
|
$ref: "#/components/responses/RateLimited"
|
|
/admin/users:
|
|
get:
|
|
tags: [Admin]
|
|
operationId: listUsers
|
|
description: Administrator only.
|
|
responses:
|
|
"200":
|
|
description: Workspace members
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/UserSummary"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
post:
|
|
tags: [Admin]
|
|
operationId: createUser
|
|
description: >-
|
|
Administrator only. There is no invite email; the administrator sets an initial
|
|
password and communicates it out of band.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateUserRequest"
|
|
responses:
|
|
"200":
|
|
description: User created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UserSummary"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
$ref: "#/components/responses/BadRequest"
|
|
/admin/users/{userId}:
|
|
patch:
|
|
tags: [Admin]
|
|
operationId: updateUser
|
|
description: >-
|
|
Administrator only. Changes role and/or enabled state. Refuses a change that
|
|
would leave zero enabled administrators.
|
|
parameters:
|
|
- $ref: "#/components/parameters/UserId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpdateUserRequest"
|
|
responses:
|
|
"200":
|
|
description: Updated
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"409":
|
|
description: Would remove the last enabled administrator
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
/admin/users/{userId}/recovery-tokens:
|
|
post:
|
|
tags: [Admin]
|
|
operationId: issueRecoveryToken
|
|
description: >-
|
|
Administrator only. Issues a single-use, one-hour password-recovery token for
|
|
another account. The raw token is returned exactly once, in this response.
|
|
parameters:
|
|
- $ref: "#/components/parameters/UserId"
|
|
responses:
|
|
"200":
|
|
description: Token issued
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/IssueRecoveryTokenResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/admin/trash/purge:
|
|
post:
|
|
tags: [Admin]
|
|
operationId: purgeTrash
|
|
description: >-
|
|
Administrator only. The explicit administrative purge
|
|
(docs/09-data-safety: never a permanent delete through a normal UI flow).
|
|
Permanently removes every trashed page in the workspace whose retention
|
|
window has already elapsed; never a page still within retention, never a
|
|
page that was never trashed.
|
|
responses:
|
|
"200":
|
|
description: Purge completed
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/TrashPurgeResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
/admin/staging-cleanup/status:
|
|
get:
|
|
tags: [Admin]
|
|
operationId: getStagingCleanupStatus
|
|
description: >-
|
|
Administrator only (docs/adr/ADR-029-v2-r5-final-hardening.md). Live counts of
|
|
currently-open and expired-pending-cleanup page-creation staging sessions, plus
|
|
the most recent cleanup run (scheduled or manual) and its result. The "last run"
|
|
fields are in-memory only and reset on application restart — an accepted,
|
|
documented limitation; the cleanup guarantee itself does not depend on this
|
|
history.
|
|
responses:
|
|
"200":
|
|
description: Current staging-cleanup status
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StagingCleanupStatusResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
/admin/staging-cleanup/run:
|
|
post:
|
|
tags: [Admin]
|
|
operationId: runStagingCleanup
|
|
description: >-
|
|
Administrator only, CSRF-protected (docs/adr/ADR-029-v2-r5-final-hardening.md).
|
|
Runs the exact same reclamation logic as the periodic background worker, once,
|
|
synchronously. `dryRun: true` only counts currently-expired, claimable sessions
|
|
— it claims nothing, deletes nothing and is safe to call as often as wanted;
|
|
never expose a destructive "run now" action without this dry-run mode.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RunStagingCleanupRequest"
|
|
responses:
|
|
"200":
|
|
description: Cleanup run (or dry-run preview) result
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RunStagingCleanupResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
/activity:
|
|
get:
|
|
tags: [Activity]
|
|
operationId: listActivity
|
|
description: >-
|
|
Requires a reader cookie session or an `audit:read` service-token scope. The
|
|
most recent audit/activity events across the workspace (page/project/decision
|
|
mutations, imports, backups, staging-session cleanup, etc.), newest first —
|
|
the same durable `audit_events` trail every mutation writes to
|
|
(docs/12-auth-security.md "Logging and privacy": never a password, raw token or
|
|
session identifier).
|
|
parameters:
|
|
- name: limit
|
|
in: query
|
|
required: false
|
|
description: Defaults to 100.
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 500
|
|
responses:
|
|
"200":
|
|
description: Recent activity, newest first
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ActivityListResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
/workspace/members:
|
|
get:
|
|
tags: [Workspace]
|
|
operationId: searchWorkspaceMembers
|
|
description: >-
|
|
Requires a reader cookie session — deliberately not reachable by a
|
|
service-account bearer token (no automation use case has been named for this
|
|
yet). Backs the accessible owner picker (docs/adr/ADR-029-v2-r5-final-hardening.md,
|
|
replacing manual `OwnerId`/GUID entry): only enabled members of the current
|
|
workspace, matched case-insensitively against display name or email, capped at
|
|
25 results. Never discloses accounts outside the workspace or disabled
|
|
accounts. An empty/omitted `q` returns the first 25 members alphabetically (the
|
|
picker's initial open state, before typing).
|
|
parameters:
|
|
- name: q
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Matching workspace members
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/WorkspaceMemberListResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
/search:
|
|
get:
|
|
tags: [Search]
|
|
operationId: searchKnowledge
|
|
description: >-
|
|
PostgreSQL full-text (weighted title/tags/body) plus trigram typo-tolerant
|
|
title matching. Authorization is entirely SQL-level: always scoped to the
|
|
caller's workspace, always excludes trashed pages
|
|
(docs/10-search-and-indexing.md). An empty or absent `q` returns all
|
|
permitted, non-trashed pages (browsing by tag/type filter alone).
|
|
parameters:
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
- name: q
|
|
in: query
|
|
schema:
|
|
type: string
|
|
maxLength: 500
|
|
- name: type
|
|
in: query
|
|
description: A single DocumentType value.
|
|
schema:
|
|
$ref: "#/components/schemas/DocumentType"
|
|
- name: tag
|
|
in: query
|
|
description: A single exact tag match.
|
|
schema:
|
|
type: string
|
|
- $ref: "#/components/parameters/Cursor"
|
|
- $ref: "#/components/parameters/Limit"
|
|
responses:
|
|
"200":
|
|
description: Permission-filtered search results
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SearchResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
/unified-search:
|
|
get:
|
|
tags: [Search]
|
|
operationId: searchWorkspaceContent
|
|
description: >-
|
|
Human reader session only. Searches visible knowledge, projects, reviewed
|
|
services, runbooks and decisions in the current workspace. Trashed content
|
|
and hidden or archived services are excluded. Counts cover all five kinds;
|
|
`kind` filters the returned page. A `tag` filters exact, case-insensitive
|
|
page tags on knowledge, runbook and decision results and excludes projects
|
|
and services. An empty query returns no results unless `tag` is supplied. The
|
|
endpoint shares the search rate limit of 60 requests per effective remote IP
|
|
per minute. An invalid `kind` returns 422.
|
|
parameters:
|
|
- name: q
|
|
in: query
|
|
schema:
|
|
type: string
|
|
maxLength: 500
|
|
- name: kind
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [knowledge, projects, services, runbooks, decisions]
|
|
- name: tag
|
|
in: query
|
|
description: Exact, case-insensitive page tag; applies to knowledge, runbooks and decisions.
|
|
schema:
|
|
type: string
|
|
maxLength: 64
|
|
- $ref: "#/components/parameters/Cursor"
|
|
- name: limit
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 100
|
|
default: 25
|
|
responses:
|
|
"200":
|
|
description: Permission-filtered, ranked results and complete grouped counts
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UnifiedSearchResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
description: Invalid kind
|
|
"429":
|
|
description: Search request budget exhausted
|
|
/rag/publication-manifest:
|
|
get:
|
|
tags: [Rag]
|
|
operationId: getRagPublicationManifest
|
|
description: >-
|
|
A narrow, read-only view of every page tagged `rag:publish`
|
|
(docs/32-rag-publication-convention.md), built for RAGcore's n8n sync — the
|
|
one reliable way to discover the full curated publication set, since this API
|
|
has no `GET /pages` enumeration (only `/pages/roots` plus a tree walk). Reuses
|
|
the existing `pages:read` scope; no new scope was added. A page that still
|
|
carries `rag:publish` but is trashed, still a draft, awaiting review, archived,
|
|
or deprecated without the `rag:allow-deprecated` tag comes back with
|
|
`retired: true` and a `retiredReason`, and its `markdown`/`checksum` are
|
|
`null` — so a sync consumer can safely withdraw previously ingested content
|
|
instead of only ever learning about new content.
|
|
parameters:
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
- name: space
|
|
in: query
|
|
description: >-
|
|
A single `rag:space:` tag suffix. Case-sensitive lowercase slug; an
|
|
invalid shape is rejected with 422 rather than passed into the array-
|
|
containment filter.
|
|
schema:
|
|
type: string
|
|
maxLength: 64
|
|
- $ref: "#/components/parameters/Cursor"
|
|
- name: limit
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 200
|
|
default: 100
|
|
responses:
|
|
"200":
|
|
description: Publication manifest page
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RagPublicationManifestResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"422":
|
|
description: Invalid `space` parameter
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
/pages/roots:
|
|
get:
|
|
tags: [Pages]
|
|
operationId: listRootPages
|
|
description: Top-level pages (no parent), cursor-paginated.
|
|
parameters:
|
|
- $ref: "#/components/parameters/Cursor"
|
|
- $ref: "#/components/parameters/Limit"
|
|
responses:
|
|
"200":
|
|
description: Root pages
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PageChildrenResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
/pages/preview:
|
|
post:
|
|
tags: [Pages]
|
|
operationId: previewMarkdown
|
|
description: >-
|
|
Requires a reader cookie session or a `pages:read` service-token scope. Renders
|
|
arbitrary Markdown through the exact same server-side pipeline (Markdig with
|
|
`.DisableHtml()` plus the allowlist `HtmlSanitizer` pass) used for a published
|
|
page revision — safe to call with unsaved editor content, never persists
|
|
anything. The same safe pipeline is reused for project free-text Markdown
|
|
fields (`currentStateMarkdown`, `summaryMarkdown`, etc. —
|
|
docs/adr/ADR-029-v2-r5-final-hardening.md).
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PreviewMarkdownRequest"
|
|
responses:
|
|
"200":
|
|
description: Rendered, sanitized HTML
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PreviewMarkdownResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
/pages:
|
|
post:
|
|
tags: [Pages]
|
|
operationId: createPage
|
|
parameters:
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreatePageRequest"
|
|
responses:
|
|
"200":
|
|
description: Page created and published as revision 1
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PageDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
description: Validation failed, or an unknown templateKey was supplied
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
/pages/{pageId}:
|
|
parameters:
|
|
- $ref: "#/components/parameters/PageId"
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
get:
|
|
tags: [Pages]
|
|
operationId: getPage
|
|
responses:
|
|
"200":
|
|
description: Current page, with server-sanitized rendered HTML
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PageDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
put:
|
|
tags: [Pages]
|
|
operationId: updatePage
|
|
description: Publishes a new revision. Requires If-Match with the page's current ETag.
|
|
parameters:
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpdatePageRequest"
|
|
responses:
|
|
"200":
|
|
description: Updated page
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PageDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"409":
|
|
description: Page is trashed and must be restored before it can be edited
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"412":
|
|
$ref: "#/components/responses/PreconditionFailed"
|
|
"422":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"428":
|
|
$ref: "#/components/responses/PreconditionRequired"
|
|
/pages/{pageId}/children:
|
|
get:
|
|
tags: [Pages]
|
|
operationId: listPageChildren
|
|
parameters:
|
|
- $ref: "#/components/parameters/PageId"
|
|
- $ref: "#/components/parameters/Cursor"
|
|
- $ref: "#/components/parameters/Limit"
|
|
responses:
|
|
"200":
|
|
description: Direct children, cursor-paginated (proven at 10,000-child scale, docs/27-acceptance-matrix.md M2-A07)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PageChildrenResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/pages/{pageId}/move:
|
|
post:
|
|
tags: [Pages]
|
|
operationId: movePage
|
|
description: >-
|
|
Reparents and/or reorders a page among its siblings. Rejects a move that would
|
|
make a page its own descendant. Requires If-Match with the page's current ETag.
|
|
parameters:
|
|
- $ref: "#/components/parameters/PageId"
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/MovePageRequest"
|
|
responses:
|
|
"200":
|
|
description: Moved page
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PageDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"409":
|
|
description: The requested move would make the page its own descendant (a cycle)
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"412":
|
|
$ref: "#/components/responses/PreconditionFailed"
|
|
"428":
|
|
$ref: "#/components/responses/PreconditionRequired"
|
|
/pages/{pageId}/trash:
|
|
post:
|
|
tags: [Pages]
|
|
operationId: trashPage
|
|
description: >-
|
|
Moves a page to the trash/retention model. Never a permanent delete
|
|
(docs/09-data-safety). Requires If-Match with the page's current ETag and
|
|
accepts either an editor cookie session or a `pages:write` service token,
|
|
enabling bounded import rollback without permanent deletion.
|
|
parameters:
|
|
- $ref: "#/components/parameters/PageId"
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
responses:
|
|
"200":
|
|
description: Page trashed
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"412":
|
|
$ref: "#/components/responses/PreconditionFailed"
|
|
"428":
|
|
$ref: "#/components/responses/PreconditionRequired"
|
|
/pages/{pageId}/restore:
|
|
post:
|
|
tags: [Pages]
|
|
operationId: restorePage
|
|
description: Restores a trashed page. Requires If-Match with the page's current ETag.
|
|
parameters:
|
|
- $ref: "#/components/parameters/PageId"
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
responses:
|
|
"200":
|
|
description: Page restored
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"412":
|
|
$ref: "#/components/responses/PreconditionFailed"
|
|
"428":
|
|
$ref: "#/components/responses/PreconditionRequired"
|
|
/pages/{pageId}/revisions:
|
|
get:
|
|
tags: [Pages]
|
|
operationId: listPageRevisions
|
|
description: Immutable revision history, most recent first (M3-A01/M3-A02).
|
|
parameters:
|
|
- $ref: "#/components/parameters/PageId"
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
responses:
|
|
"200":
|
|
description: Revisions
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/PageRevisionSummary"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/pages/{pageId}/revisions/{revisionNumber}:
|
|
get:
|
|
tags: [Pages]
|
|
operationId: getPageRevision
|
|
parameters:
|
|
- $ref: "#/components/parameters/PageId"
|
|
- $ref: "#/components/parameters/RevisionNumber"
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
responses:
|
|
"200":
|
|
description: One revision's full content, server-rendered
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PageRevisionDetailResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/pages/{pageId}/revisions/{revisionNumber}/restore:
|
|
post:
|
|
tags: [Pages]
|
|
operationId: restorePageRevision
|
|
description: >-
|
|
Publishes a new current revision whose content matches the chosen past
|
|
snapshot. Never rewrites history: the state right before this call is itself
|
|
preserved as its own revision. Requires If-Match with the page's current ETag.
|
|
parameters:
|
|
- $ref: "#/components/parameters/PageId"
|
|
- $ref: "#/components/parameters/RevisionNumber"
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
responses:
|
|
"200":
|
|
description: Restored page
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PageDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"409":
|
|
description: Page is trashed and must be restored before it can be edited
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"412":
|
|
$ref: "#/components/responses/PreconditionFailed"
|
|
"428":
|
|
$ref: "#/components/responses/PreconditionRequired"
|
|
/pages/{pageId}/backlinks:
|
|
get:
|
|
tags: [Pages]
|
|
operationId: getPageBacklinks
|
|
description: >-
|
|
"What links here": pages whose most recently published content contains a
|
|
resolved internal link to this page (M3-A03).
|
|
parameters:
|
|
- $ref: "#/components/parameters/PageId"
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
responses:
|
|
"200":
|
|
description: Backlinks
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/BacklinksResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/drafts/{draftKey}:
|
|
parameters:
|
|
- $ref: "#/components/parameters/DraftKey"
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
get:
|
|
tags: [Drafts]
|
|
operationId: getDraft
|
|
description: >-
|
|
Private per-user autosaved draft. A draft never overwrites the published
|
|
revision (docs/27-acceptance-matrix.md, M2-A04).
|
|
responses:
|
|
"200":
|
|
description: Draft
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DraftResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
put:
|
|
tags: [Drafts]
|
|
operationId: saveDraft
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SaveDraftRequest"
|
|
responses:
|
|
"200":
|
|
description: >-
|
|
Draft saved. `baseRevisionIsStale` is true when the page has been published
|
|
again since this draft's base revision, without blocking the save.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DraftResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
delete:
|
|
tags: [Drafts]
|
|
operationId: discardDraft
|
|
responses:
|
|
"200":
|
|
description: Draft discarded
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/templates:
|
|
get:
|
|
tags: [Templates]
|
|
operationId: listTemplates
|
|
description: Starter document templates available when creating a new page.
|
|
responses:
|
|
"200":
|
|
description: Available templates
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/TemplateSummary"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
/pages/{pageId}/attachments:
|
|
parameters:
|
|
- $ref: "#/components/parameters/PageId"
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
get:
|
|
tags: [Attachments]
|
|
operationId: listPageAttachments
|
|
description: Requires a reader cookie session or an `attachments:read` service-token scope.
|
|
responses:
|
|
"200":
|
|
description: Attachments referenced by this page
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AttachmentListResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
post:
|
|
tags: [Attachments]
|
|
operationId: uploadAttachment
|
|
description: >-
|
|
Requires an editor cookie session or an `attachments:write` service-token
|
|
scope.
|
|
The upload pipeline from docs/13-attachments.md: streams once to quarantine
|
|
while hashing, verifies size, detects the real signature (never trusting the
|
|
declared type/extension alone), applies delivery policy, then commits.
|
|
Identical content deduplicates to one physical blob while still creating an
|
|
independent logical reference.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
multipart/form-data:
|
|
schema:
|
|
type: object
|
|
required: [file]
|
|
properties:
|
|
file:
|
|
type: string
|
|
format: binary
|
|
responses:
|
|
"200":
|
|
description: Uploaded
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AttachmentSummary"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"413":
|
|
description: File exceeds the configured maximum upload size
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"422":
|
|
description: Rejected content (unsafe signature) or an invalid filename
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
/pages/staged:
|
|
post:
|
|
tags: [Pages]
|
|
operationId: beginPageCreationSession
|
|
description: >-
|
|
Opens a staging session for creating a new page that may need attachments
|
|
inserted before the page is ever visible to a reader
|
|
(docs/adr/ADR-025-v2-r1-data-integrity-remediation.md). Requires an editor
|
|
cookie session or a `pages:write` service-token scope.
|
|
parameters:
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
- $ref: "#/components/parameters/IdempotencyKey"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/BeginPageCreationSessionRequest"
|
|
responses:
|
|
"200":
|
|
description: Session opened
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PageCreationSessionResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
description: The supplied parentPageId does not exist
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
/pages/staged/{sessionId}/attachments:
|
|
parameters:
|
|
- $ref: "#/components/parameters/SessionId"
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
post:
|
|
tags: [Attachments]
|
|
operationId: uploadStagedAttachment
|
|
description: >-
|
|
The same real, immediately-addressable attachment as
|
|
`POST /pages/{pageId}/attachments` — owned by an open page-creation session
|
|
instead of an existing page until that session's commit re-points ownership
|
|
atomically. Requires an editor cookie session or an `attachments:write`
|
|
service-token scope.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
multipart/form-data:
|
|
schema:
|
|
type: object
|
|
required: [file]
|
|
properties:
|
|
file:
|
|
type: string
|
|
format: binary
|
|
responses:
|
|
"200":
|
|
description: Uploaded
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AttachmentSummary"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
description: The session does not exist, is no longer open, or has expired
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"413":
|
|
description: File exceeds the configured maximum upload size
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"422":
|
|
description: Rejected content (unsafe signature) or an invalid filename
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
/pages/staged/{sessionId}/commit:
|
|
parameters:
|
|
- $ref: "#/components/parameters/SessionId"
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
post:
|
|
tags: [Pages]
|
|
operationId: commitPageCreationSession
|
|
description: >-
|
|
The single explicit publication action: atomically creates the page and
|
|
re-points every attachment already uploaded into this session to it, in one
|
|
transaction — no half-published page with broken attachment links can ever
|
|
become visible (docs/adr/ADR-025-v2-r1-data-integrity-remediation.md).
|
|
Requires an editor cookie session or a `pages:write` service-token scope.
|
|
parameters:
|
|
- $ref: "#/components/parameters/IdempotencyKey"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CommitPageCreationSessionRequest"
|
|
responses:
|
|
"200":
|
|
description: Page created and published, with every staged attachment now owned by it
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PageDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
description: The session does not exist
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"409":
|
|
description: The session is no longer open (already committed, discarded, or expired)
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"422":
|
|
$ref: "#/components/responses/BadRequest"
|
|
/pages/staged/{sessionId}/discard:
|
|
parameters:
|
|
- $ref: "#/components/parameters/SessionId"
|
|
- $ref: "#/components/parameters/CorrelationId"
|
|
post:
|
|
tags: [Pages]
|
|
operationId: discardPageCreationSession
|
|
description: >-
|
|
Explicit compensation for an abandoned session: removes every attachment
|
|
uploaded into it that is not used elsewhere, then marks the session
|
|
discarded. Idempotent — discarding an already-discarded session is a safe
|
|
no-op. Requires an editor cookie session or a `pages:write` service-token
|
|
scope.
|
|
parameters:
|
|
- $ref: "#/components/parameters/IdempotencyKey"
|
|
responses:
|
|
"200":
|
|
description: Session discarded (or already was)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DiscardPageCreationSessionResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
description: The session does not exist
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"409":
|
|
description: The session was already committed and can no longer be discarded
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
/infrastructure/overview:
|
|
get:
|
|
tags: [Infrastructure]
|
|
operationId: getInfrastructureOverview
|
|
description: >-
|
|
Requires a reader cookie session or an `infrastructure:read` service-token
|
|
scope. The dashboard summary: the most recent inventory snapshot (if any),
|
|
every known host, and aggregate service counts (total, proposed/unreviewed,
|
|
missing, unhealthy, unprotected-by-backup). Returns an all-zero/empty response
|
|
rather than an error when no workspace exists yet.
|
|
responses:
|
|
"200":
|
|
description: Infrastructure overview
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/InfrastructureOverviewResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
/infrastructure/services:
|
|
get:
|
|
tags: [Infrastructure]
|
|
operationId: listInfrastructureServices
|
|
description: >-
|
|
Requires a reader cookie session or an `infrastructure:read` service-token
|
|
scope. Cursor-paginated services catalog
|
|
(docs/adr/ADR-026-v2-r2-infrastructure-scale-and-sync-durability.md) — proven
|
|
at real production scale (InfrastructureCatalogReleaseScaleTests.cs, 50,000
|
|
services) with an index-order scan, never a full in-memory load. `projectId`,
|
|
`search` and `status` are applied server-side on the same paginated query;
|
|
filter results never depend on pages already loaded by the client.
|
|
parameters:
|
|
- name: includeHidden
|
|
in: query
|
|
required: false
|
|
description: Defaults to false — hidden/archived services are excluded from the default catalog view.
|
|
schema:
|
|
type: boolean
|
|
- name: projectId
|
|
in: query
|
|
required: false
|
|
description: Only services whose editorial profile links this project.
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
- name: search
|
|
in: query
|
|
required: false
|
|
description: Case-insensitive match against detected name, image reference, and editorial display name/category.
|
|
schema:
|
|
type: string
|
|
- name: status
|
|
in: query
|
|
required: false
|
|
description: Server-side operational status filter.
|
|
schema:
|
|
type: string
|
|
enum: [attention, healthy, missing]
|
|
- $ref: "#/components/parameters/Cursor"
|
|
- $ref: "#/components/parameters/Limit"
|
|
responses:
|
|
"200":
|
|
description: A page of services, newest-`(DetectedName, Id)`-ordered
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/InfrastructureServiceListResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
/infrastructure/storage:
|
|
get:
|
|
tags: [Infrastructure]
|
|
operationId: listInfrastructureStorage
|
|
description: >-
|
|
Requires a reader cookie session or an `infrastructure:read` service-token
|
|
scope. Cursor-paginated storage listing (V2-R5,
|
|
docs/adr/ADR-029-v2-r5-final-hardening.md) — every known volume across every
|
|
service, each row carrying its owning service's ID/name for display.
|
|
parameters:
|
|
- name: search
|
|
in: query
|
|
required: false
|
|
description: Case-insensitive match against the owning service's detected name and the volume's own name/mount path, applied server-side.
|
|
schema:
|
|
type: string
|
|
- $ref: "#/components/parameters/Cursor"
|
|
- $ref: "#/components/parameters/Limit"
|
|
responses:
|
|
"200":
|
|
description: A page of storage volumes
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/InfrastructureStorageListResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
/infrastructure/services/{serviceId}:
|
|
get:
|
|
tags: [Infrastructure]
|
|
operationId: getInfrastructureService
|
|
description: >-
|
|
Requires a reader cookie session or an `infrastructure:read` service-token
|
|
scope. The full fact graph for one service: editorial profile, manual
|
|
overrides, components, endpoints, networks, volumes, databases, dependencies,
|
|
health observations, deployment references and backup coverage, each fact
|
|
carrying its own provenance (source key, confidence, last-seen snapshot,
|
|
observed-at). The `ETag` response header is the editorial profile's
|
|
optimistic-concurrency token, required as `If-Match` on
|
|
`PUT .../editorial-profile`.
|
|
parameters:
|
|
- name: serviceId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
responses:
|
|
"200":
|
|
description: Full service detail
|
|
headers:
|
|
ETag:
|
|
schema:
|
|
type: string
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/InfrastructureServiceDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/infrastructure/services/{serviceId}/editorial-profile:
|
|
put:
|
|
tags: [Infrastructure]
|
|
operationId: updateInfrastructureEditorialProfile
|
|
description: >-
|
|
Editor role or `infrastructure:write` service-token scope required, CSRF- and
|
|
`If-Match`-protected. Updates the human-authored editorial layer for a service
|
|
(display name, summary, category, purpose, owning project, procedures, tags,
|
|
favorite/hidden/archived flags) — automatically-detected facts are never
|
|
touched by this endpoint. A stale `If-Match` is a 412, never a silent overwrite.
|
|
parameters:
|
|
- name: serviceId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpdateEditorialProfileRequest"
|
|
responses:
|
|
"200":
|
|
description: Updated service detail
|
|
headers:
|
|
ETag:
|
|
schema:
|
|
type: string
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/InfrastructureServiceDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"412":
|
|
description: If-Match did not match the current editorial profile ETag
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationFailed"
|
|
"428":
|
|
description: If-Match header is required and was missing
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
/infrastructure/services/{serviceId}/overrides:
|
|
post:
|
|
tags: [Infrastructure]
|
|
operationId: setManualOverride
|
|
description: >-
|
|
Editor role or `infrastructure:write` service-token scope required,
|
|
CSRF-protected. Sets (creates or replaces) one field-level manual correction
|
|
over an automatically-detected fact, validated against the exact allowlist
|
|
`GET /infrastructure/overrides/catalog` returns for this object kind — an
|
|
inventory sync never overwrites or removes an existing override
|
|
(docs/adr/ADR-025-v2-r1-data-integrity-remediation.md, audit finding H2).
|
|
parameters:
|
|
- name: serviceId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SetManualOverrideRequest"
|
|
responses:
|
|
"200":
|
|
description: The created/updated override's ID
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
required: [id]
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationFailed"
|
|
/infrastructure/overrides:
|
|
post:
|
|
tags: [Infrastructure]
|
|
operationId: setInfrastructureOverride
|
|
description: >-
|
|
Editor role or `infrastructure:write` service-token scope required,
|
|
CSRF-protected. The generic sibling of
|
|
`POST /infrastructure/services/{serviceId}/overrides` (V2-R5,
|
|
docs/adr/ADR-029-v2-r5-final-hardening.md): sets a field-level manual
|
|
correction for any `InfrastructureObjectKind` (Host, Network, Volume, Database,
|
|
Endpoint, Component, Deployment, Backup), not only a Service reached through
|
|
its own nested route — validated against the exact allowlist
|
|
`GET /infrastructure/overrides/catalog?targetKind=...` returns for that kind.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SetInfrastructureOverrideRequest"
|
|
responses:
|
|
"200":
|
|
description: The created/updated override's ID
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
required: [id]
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationFailed"
|
|
/infrastructure/overrides/{overrideId}:
|
|
delete:
|
|
tags: [Infrastructure]
|
|
operationId: removeManualOverride
|
|
description: >-
|
|
Editor role or `infrastructure:write` service-token scope required,
|
|
CSRF-protected. Removes one manual override, reverting that field to its
|
|
automatically-detected value (if any).
|
|
parameters:
|
|
- name: overrideId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
responses:
|
|
"204":
|
|
description: Override removed
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/infrastructure/services/{serviceId}/review:
|
|
post:
|
|
tags: [Infrastructure]
|
|
operationId: reviewInfrastructureService
|
|
description: >-
|
|
Administrator only, CSRF-protected. Accepts or rejects a `Proposed` (newly
|
|
detected, unreviewed) service — the deliberate human-in-the-loop gate before an
|
|
automatically-detected container becomes part of the confirmed catalog. A
|
|
name match alone is never treated as a high-confidence relationship
|
|
(docs/adr/ADR-029-v2-r5-final-hardening.md).
|
|
parameters:
|
|
- name: serviceId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ReviewInfrastructureServiceRequest"
|
|
responses:
|
|
"200":
|
|
description: The service's resulting presence state
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ReviewInfrastructureServiceResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationFailed"
|
|
/infrastructure/inventory/snapshots:
|
|
post:
|
|
tags: [Infrastructure]
|
|
operationId: synchronizeInfrastructureInventory
|
|
description: >-
|
|
Administrator only, CSRF- and `Idempotency-Key`-protected. Imports one
|
|
inventory snapshot (hosts + services + every nested fact) captured by the
|
|
external, read-only inventory tooling — never destructive: existing editorial
|
|
profiles and manual overrides are always preserved, a previously-seen service
|
|
that is missing from this snapshot is marked missing (not deleted), and
|
|
replaying the same `sourceKey`/`sourceHash` is a safe no-op
|
|
(`alreadyImported: true`), not a duplicate import.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SynchronizeInventoryRequest"
|
|
responses:
|
|
"200":
|
|
description: Synchronization result
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/InventorySyncResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"409":
|
|
description: Idempotency-Key reused with a different request body
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationFailed"
|
|
/infrastructure/inventory/curated-package:
|
|
post:
|
|
tags: [Infrastructure]
|
|
operationId: importCuratedInventoryPackage
|
|
description: >-
|
|
Administrator only, CSRF- and `Idempotency-Key`-protected. A single bounded
|
|
call that both synchronizes a raw inventory snapshot and applies a curated set
|
|
of editorial profiles/review decisions on top of it in one step — the
|
|
mechanism the curated-infrastructure-wiki publication used
|
|
(docs/deployments/2026-08-02-curated-infrastructure-wiki.md). Never overwrites
|
|
an editorial profile a human has since hand-edited.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ImportCuratedInventoryRequest"
|
|
responses:
|
|
"200":
|
|
description: Combined synchronization + curation result
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ImportCuratedInventoryResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"409":
|
|
description: Idempotency-Key reused with a different request body
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"422":
|
|
$ref: "#/components/responses/ValidationFailed"
|
|
/infrastructure/changes:
|
|
get:
|
|
tags: [Infrastructure]
|
|
operationId: listInfrastructureChanges
|
|
description: >-
|
|
Requires a reader cookie session or an `infrastructure:read` service-token
|
|
scope. The most recent inventory-sync change log entries (new/changed/missing
|
|
objects), newest first, each naming its target kind/ID, source key, change
|
|
kind and the specific fields that changed.
|
|
parameters:
|
|
- name: limit
|
|
in: query
|
|
required: false
|
|
description: Defaults to 100.
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 500
|
|
responses:
|
|
"200":
|
|
description: Recent inventory changes, newest first
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/InventoryChangeResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
/infrastructure/overrides/catalog:
|
|
get:
|
|
tags: [Infrastructure]
|
|
operationId: getManualOverrideFieldCatalog
|
|
description: >-
|
|
The exact, server-authoritative set of fields a ManualOverride may target for
|
|
the given InfrastructureObjectKind, with each field's JSON value kind, max
|
|
length and (for enum fields) allowed values
|
|
(docs/adr/ADR-025-v2-r1-data-integrity-remediation.md, audit finding H3). The
|
|
frontend fetches this instead of keeping its own list, so the two can never
|
|
independently drift. Requires a reader cookie session or an
|
|
`infrastructure:read` service-token scope.
|
|
parameters:
|
|
- name: targetKind
|
|
in: query
|
|
required: false
|
|
description: Defaults to Service, the only kind reachable through the current UI/API.
|
|
schema:
|
|
type: string
|
|
enum: [Host, Service, Component, Endpoint, Network, Volume, Database, Deployment, Backup]
|
|
responses:
|
|
"200":
|
|
description: Field catalog for the requested target kind
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ManualOverrideFieldCatalogResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"422":
|
|
description: Unknown targetKind
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
/attachments/{attachmentId}/content:
|
|
get:
|
|
tags: [Attachments]
|
|
operationId: getAttachmentContent
|
|
description: >-
|
|
Requires a reader cookie session or an `attachments:read` service-token scope.
|
|
Every request re-authorizes the logical reference (docs/13-attachments.md) —
|
|
a content hash alone is never sufficient permission to retrieve a blob. Served
|
|
with a safe Content-Disposition (inline only for a known-safe raster image,
|
|
attachment otherwise) and X-Content-Type-Options: nosniff.
|
|
parameters:
|
|
- name: attachmentId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
responses:
|
|
"200":
|
|
description: The raw file content
|
|
content:
|
|
application/octet-stream: {}
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/admin/imports/preflight:
|
|
post:
|
|
tags: [Imports]
|
|
operationId: preflightImport
|
|
description: >-
|
|
Administrator only. Discovers and fingerprints a read-only source beneath the
|
|
operator-mounted imports directory, selects an adapter, and builds a plan —
|
|
no target mutation occurs (docs/17-leafwiki-migration.md).
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PreflightImportRequest"
|
|
responses:
|
|
"200":
|
|
description: Plan ready (or an idempotent repeat of an already-preflighted request)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ImportPreflightResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
description: Invalid source path, or no adapter can interpret this source
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
/admin/imports/{jobId}:
|
|
get:
|
|
tags: [Imports]
|
|
operationId: getImportJob
|
|
parameters:
|
|
- $ref: "#/components/parameters/ImportJobId"
|
|
responses:
|
|
"200":
|
|
description: Import job status
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ImportJobSummaryResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/admin/imports/{jobId}/execute:
|
|
post:
|
|
tags: [Imports]
|
|
operationId: executeImport
|
|
description: >-
|
|
Administrator only. Re-verifies the source fingerprint and plan hash have not
|
|
changed since preflight, then creates each planned page — parents before
|
|
children — with full provenance for rollback. Never overwrites an existing
|
|
page; an item already imported with unchanged content is skipped idempotently.
|
|
parameters:
|
|
- $ref: "#/components/parameters/ImportJobId"
|
|
- name: sourceRelativePath
|
|
in: query
|
|
required: true
|
|
description: The same relative path supplied to preflight, re-verified against its recorded fingerprint.
|
|
schema:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Execution result
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ExecuteImportResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"409":
|
|
description: The source or plan changed since preflight, or the job is not in a plan-ready state
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
/admin/imports/{jobId}/rollback:
|
|
post:
|
|
tags: [Imports]
|
|
operationId: rollbackImport
|
|
description: >-
|
|
Administrator only. Removes exactly the pages/attachments this job's own
|
|
provenance created — never a page a user has since edited, which is preserved
|
|
instead (docs/17-leafwiki-migration.md).
|
|
parameters:
|
|
- $ref: "#/components/parameters/ImportJobId"
|
|
responses:
|
|
"200":
|
|
description: Rollback result
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RollbackImportResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"409":
|
|
description: There is nothing to roll back for this job
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
/admin/source-batches/purge/dry-run:
|
|
post:
|
|
tags: [Source batch purge]
|
|
operationId: dryRunSourceBatchPurge
|
|
description: >-
|
|
Administrator only. Inspects an explicitly supplied, independently recorded
|
|
page-ID list for one source batch. It blocks on missing or unlisted source
|
|
pages, derived content outside the list, retained backlinks or project links.
|
|
This endpoint never writes data.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SourceBatchPurgeRequest"
|
|
responses:
|
|
"200":
|
|
description: Immutable-scope dry-run and confirmation hash
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SourceBatchPurgeDryRunResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
/admin/source-batches/purge/execute:
|
|
post:
|
|
tags: [Source batch purge]
|
|
operationId: executeSourceBatchPurge
|
|
description: >-
|
|
Administrator only. Permanently removes only the pages and attachment
|
|
references proven by the preceding dry-run. The confirmation hash binds the
|
|
request to the current page hashes and attachment graph; any change requires a
|
|
new dry-run. Physical blobs are hash-verified and removed only after their
|
|
database references have been committed as absent.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ExecuteSourceBatchPurgeRequest"
|
|
responses:
|
|
"200":
|
|
description: Completed source-batch purge
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SourceBatchPurgeResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"409":
|
|
description: The dry-run changed or the source scope is ambiguous
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
/projects:
|
|
get:
|
|
tags: [Projects]
|
|
operationId: listProjects
|
|
parameters:
|
|
- $ref: "#/components/parameters/Cursor"
|
|
- $ref: "#/components/parameters/Limit"
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
$ref: "#/components/schemas/ProjectStatus"
|
|
- name: priority
|
|
in: query
|
|
schema:
|
|
$ref: "#/components/schemas/ProjectPriority"
|
|
- name: ownerId
|
|
in: query
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
- name: search
|
|
in: query
|
|
description: Case-insensitive match against project name or summary, applied before cursor pagination.
|
|
schema:
|
|
type: string
|
|
- name: updatedAfter
|
|
in: query
|
|
description: Only projects updated at or after this ISO-8601 timestamp.
|
|
schema:
|
|
type: string
|
|
format: date-time
|
|
- name: sort
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [name, updatedAt]
|
|
responses:
|
|
"200":
|
|
description: Project collection
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProjectListResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
post:
|
|
tags: [Projects]
|
|
operationId: createProject
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateProjectRequest"
|
|
responses:
|
|
"200":
|
|
description: Created project (full bundle)
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProjectDetail"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
/projects/totals:
|
|
get:
|
|
tags: [Projects]
|
|
operationId: getProjectCatalogTotals
|
|
description: Authoritative workspace totals for dashboard KPI rendering; independent of cursor-paginated catalog pages.
|
|
responses:
|
|
"200":
|
|
description: Total and active project counts
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProjectCatalogTotalsResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
/projects/{projectId}:
|
|
parameters:
|
|
- $ref: "#/components/parameters/ProjectId"
|
|
get:
|
|
tags: [Projects]
|
|
operationId: getProject
|
|
description: >-
|
|
The project's bounded context bundle — structured data plus stable citations
|
|
into linked pages, never a copy of their content (docs/15-project-hub.md
|
|
"Project context bundle", M5-A05).
|
|
responses:
|
|
"200":
|
|
description: Project detail bundle (documents, recent updates, milestones, decisions)
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProjectDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
put:
|
|
tags: [Projects]
|
|
operationId: updateProject
|
|
parameters:
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpdateProjectRequest"
|
|
responses:
|
|
"200":
|
|
description: Updated project
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProjectDetail"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"412":
|
|
$ref: "#/components/responses/PreconditionFailed"
|
|
"428":
|
|
$ref: "#/components/responses/PreconditionRequired"
|
|
/projects/{projectId}/context:
|
|
parameters:
|
|
- $ref: "#/components/parameters/ProjectId"
|
|
get:
|
|
tags: [Projects]
|
|
operationId: getProjectContext
|
|
description: >-
|
|
A bounded, single-call context bundle for automation and future MCP-server
|
|
consumers (docs/09-api-contract.md "Project context", docs/24-mcp-readiness.md,
|
|
M6-A04). Update/decision/document counts are clamped; the response reports
|
|
what was truncated instead of ever growing unbounded. Distinct from the
|
|
unbounded `GET /projects/{projectId}` detail bundle.
|
|
responses:
|
|
"200":
|
|
description: Bounded project context bundle with stable citations
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProjectContextResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/projects/{projectId}/icon:
|
|
parameters:
|
|
- $ref: "#/components/parameters/ProjectId"
|
|
post:
|
|
tags: [Projects]
|
|
operationId: uploadProjectIcon
|
|
description: >-
|
|
Replaces the current project icon using the attachment quarantine and
|
|
byte-signature validation pipeline. Accepts only PNG, JPEG, GIF or WebP up to
|
|
2 MiB. The response points to same-origin, workspace-authorized attachment
|
|
content. Requires the project's current ETag.
|
|
parameters:
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
multipart/form-data:
|
|
schema:
|
|
type: object
|
|
required: [file]
|
|
properties:
|
|
file:
|
|
type: string
|
|
format: binary
|
|
responses:
|
|
"200":
|
|
description: Project icon replaced
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProjectIconResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"412":
|
|
$ref: "#/components/responses/PreconditionFailed"
|
|
"413":
|
|
description: Icon file exceeds 2 MiB
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"422":
|
|
description: Unsupported or invalid image content
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"428":
|
|
$ref: "#/components/responses/PreconditionRequired"
|
|
/projects/{projectId}/status:
|
|
parameters:
|
|
- $ref: "#/components/parameters/ProjectId"
|
|
post:
|
|
tags: [Projects]
|
|
operationId: changeProjectStatus
|
|
description: Only the documented lifecycle transitions are accepted (docs/15-project-hub.md).
|
|
parameters:
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [status]
|
|
properties:
|
|
status:
|
|
$ref: "#/components/schemas/ProjectStatus"
|
|
responses:
|
|
"200":
|
|
description: Updated project
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProjectDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"409":
|
|
description: The requested transition is not allowed from the project's current status
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"412":
|
|
$ref: "#/components/responses/PreconditionFailed"
|
|
"428":
|
|
$ref: "#/components/responses/PreconditionRequired"
|
|
/projects/{projectId}/documents:
|
|
parameters:
|
|
- $ref: "#/components/parameters/ProjectId"
|
|
post:
|
|
tags: [Projects]
|
|
operationId: linkProjectDocument
|
|
description: >-
|
|
Links a page to the project with a role; the page remains canonical, only the
|
|
relationship is stored (docs/15-project-hub.md).
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/LinkProjectDocumentRequest"
|
|
responses:
|
|
"200":
|
|
description: Created link
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProjectDocumentSummary"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/projects/documents/{documentId}:
|
|
parameters:
|
|
- name: documentId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
delete:
|
|
tags: [Projects]
|
|
operationId: unlinkProjectDocument
|
|
description: Removes only the relationship — the linked page is never touched or deleted.
|
|
responses:
|
|
"200":
|
|
description: Unlinked
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/projects/{projectId}/updates:
|
|
parameters:
|
|
- $ref: "#/components/parameters/ProjectId"
|
|
get:
|
|
tags: [Projects]
|
|
operationId: getProjectUpdates
|
|
description: Ordered, attributable, linkable timeline (M5-A02).
|
|
parameters:
|
|
- $ref: "#/components/parameters/Cursor"
|
|
- $ref: "#/components/parameters/Limit"
|
|
responses:
|
|
"200":
|
|
description: Update timeline page
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProjectUpdatesResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
post:
|
|
tags: [Projects]
|
|
operationId: createProjectUpdate
|
|
description: >-
|
|
Immutable, append-only entry. Setting supersedesUpdateId marks the earlier
|
|
update Superseded without rewriting it (docs/15-project-hub.md).
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateProjectUpdateRequest"
|
|
responses:
|
|
"200":
|
|
description: Created update
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProjectUpdateSummary"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/projects/{projectId}/milestones:
|
|
parameters:
|
|
- $ref: "#/components/parameters/ProjectId"
|
|
get:
|
|
tags: [Projects]
|
|
operationId: getProjectMilestones
|
|
responses:
|
|
"200":
|
|
description: Milestones, in their stable sort order
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ProjectMilestoneSummary"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
post:
|
|
tags: [Projects]
|
|
operationId: createProjectMilestone
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateProjectMilestoneRequest"
|
|
responses:
|
|
"200":
|
|
description: Created milestone
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProjectMilestoneSummary"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/projects/milestones/{milestoneId}:
|
|
parameters:
|
|
- name: milestoneId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
put:
|
|
tags: [Projects]
|
|
operationId: updateProjectMilestone
|
|
parameters:
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpdateProjectMilestoneRequest"
|
|
responses:
|
|
"200":
|
|
description: Updated milestone
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProjectMilestoneSummary"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"412":
|
|
$ref: "#/components/responses/PreconditionFailed"
|
|
"428":
|
|
$ref: "#/components/responses/PreconditionRequired"
|
|
/decisions:
|
|
get:
|
|
tags: [Decisions]
|
|
operationId: listDecisions
|
|
parameters:
|
|
- $ref: "#/components/parameters/Cursor"
|
|
- $ref: "#/components/parameters/Limit"
|
|
- name: projectId
|
|
in: query
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
$ref: "#/components/schemas/DecisionStatus"
|
|
responses:
|
|
"200":
|
|
description: Decision collection
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DecisionListResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
post:
|
|
tags: [Decisions]
|
|
operationId: createDecision
|
|
description: >-
|
|
Creates one ArchitectureDecision page plus its lifecycle profile in one step
|
|
(docs/16-runbooks-and-adrs.md "unique page profile").
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateDecisionRequest"
|
|
responses:
|
|
"200":
|
|
description: Created decision (full detail, including rendered content)
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DecisionDetail"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
/decisions/{decisionId}:
|
|
parameters:
|
|
- name: decisionId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
get:
|
|
tags: [Decisions]
|
|
operationId: getDecision
|
|
description: Includes lineage (supersedes/supersededBy) — links are bidirectional and cycle-free.
|
|
responses:
|
|
"200":
|
|
description: Decision detail with lineage
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DecisionDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/decisions/{decisionId}/accept:
|
|
parameters:
|
|
- name: decisionId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
post:
|
|
tags: [Decisions]
|
|
operationId: acceptDecision
|
|
description: >-
|
|
If this decision supersedes another, that decision must currently be Accepted
|
|
and is atomically flipped to Superseded with bidirectional links
|
|
(docs/16-runbooks-and-adrs.md).
|
|
parameters:
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
responses:
|
|
"200":
|
|
description: Accepted decision
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DecisionDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"409":
|
|
description: Only a Proposed decision can be accepted
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"412":
|
|
$ref: "#/components/responses/PreconditionFailed"
|
|
"428":
|
|
$ref: "#/components/responses/PreconditionRequired"
|
|
/decisions/{decisionId}/reject:
|
|
parameters:
|
|
- name: decisionId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
post:
|
|
tags: [Decisions]
|
|
operationId: rejectDecision
|
|
parameters:
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
responses:
|
|
"200":
|
|
description: Rejected decision
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DecisionDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"409":
|
|
description: Only a Proposed decision can be rejected
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"412":
|
|
$ref: "#/components/responses/PreconditionFailed"
|
|
"428":
|
|
$ref: "#/components/responses/PreconditionRequired"
|
|
/decisions/{decisionId}/withdraw:
|
|
parameters:
|
|
- name: decisionId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
post:
|
|
tags: [Decisions]
|
|
operationId: withdrawDecision
|
|
parameters:
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
responses:
|
|
"200":
|
|
description: Withdrawn decision
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DecisionDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"409":
|
|
description: Only a Proposed or Accepted decision can be withdrawn
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
"412":
|
|
$ref: "#/components/responses/PreconditionFailed"
|
|
"428":
|
|
$ref: "#/components/responses/PreconditionRequired"
|
|
/runbooks:
|
|
get:
|
|
tags: [Runbooks]
|
|
operationId: listRunbooks
|
|
parameters:
|
|
- $ref: "#/components/parameters/Cursor"
|
|
- $ref: "#/components/parameters/Limit"
|
|
- name: riskLevel
|
|
in: query
|
|
schema:
|
|
$ref: "#/components/schemas/RunbookRiskLevel"
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
$ref: "#/components/schemas/RunbookStatus"
|
|
responses:
|
|
"200":
|
|
description: Runbook collection
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RunbookListResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
post:
|
|
tags: [Runbooks]
|
|
operationId: createRunbook
|
|
description: >-
|
|
Creates one Runbook page plus its validation/risk profile in one step
|
|
(docs/16-runbooks-and-adrs.md "unique page profile").
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateRunbookRequest"
|
|
responses:
|
|
"200":
|
|
description: Created runbook (full detail, including rendered content)
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RunbookDetail"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
/runbooks/{runbookId}:
|
|
parameters:
|
|
- name: runbookId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
get:
|
|
tags: [Runbooks]
|
|
operationId: getRunbook
|
|
description: >-
|
|
isStale is true whenever the linked page has a newer revision than the last
|
|
validation, or was never validated (docs/16-runbooks-and-adrs.md).
|
|
responses:
|
|
"200":
|
|
description: Runbook detail
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RunbookDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/runbooks/{runbookId}/validate:
|
|
parameters:
|
|
- name: runbookId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
post:
|
|
tags: [Runbooks]
|
|
operationId: validateRunbook
|
|
description: >-
|
|
Records that a human reviewed the current revision — the platform never
|
|
executes any step (docs/16-runbooks-and-adrs.md).
|
|
parameters:
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
responses:
|
|
"200":
|
|
description: Validated runbook
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RunbookDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"412":
|
|
$ref: "#/components/responses/PreconditionFailed"
|
|
"428":
|
|
$ref: "#/components/responses/PreconditionRequired"
|
|
/runbooks/{runbookId}/profile:
|
|
parameters:
|
|
- name: runbookId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
put:
|
|
tags: [Runbooks]
|
|
operationId: updateRunbookProfile
|
|
parameters:
|
|
- $ref: "#/components/parameters/IfMatch"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpdateRunbookProfileRequest"
|
|
responses:
|
|
"200":
|
|
description: Updated runbook
|
|
headers:
|
|
ETag:
|
|
$ref: "#/components/headers/ETag"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RunbookDetail"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"412":
|
|
$ref: "#/components/responses/PreconditionFailed"
|
|
"428":
|
|
$ref: "#/components/responses/PreconditionRequired"
|
|
/service-accounts:
|
|
get:
|
|
tags: [Service accounts]
|
|
operationId: listServiceAccounts
|
|
description: Administrator-only, cookie-authenticated — never reachable by a service token itself (docs/09-api-contract.md).
|
|
responses:
|
|
"200":
|
|
description: Service accounts and their (secret-free) tokens
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ServiceAccountListResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
post:
|
|
tags: [Service accounts]
|
|
operationId: createServiceAccount
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateServiceAccountRequest"
|
|
responses:
|
|
"200":
|
|
description: Service account and its one-time raw token — never retrievable again
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreatedServiceAccountToken"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"422":
|
|
description: One or more requested scopes are not recognized
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
/service-accounts/{serviceAccountId}/tokens:
|
|
parameters:
|
|
- name: serviceAccountId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
post:
|
|
tags: [Service accounts]
|
|
operationId: issueServiceAccountToken
|
|
description: >-
|
|
Rotation pattern: issue a new token, update the client, then revoke the old
|
|
one — a credential is never briefly unusable.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/IssueServiceAccountTokenRequest"
|
|
responses:
|
|
"200":
|
|
description: New token — its one-time raw value
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreatedServiceAccountToken"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/service-accounts/tokens/{tokenId}/revoke:
|
|
parameters:
|
|
- name: tokenId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
post:
|
|
tags: [Service accounts]
|
|
operationId: revokeServiceAccountToken
|
|
description: Takes effect on the very next request using this token — tokens are verified live against the database, never cached as valid.
|
|
responses:
|
|
"200":
|
|
description: Revoked
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
/admin/backups:
|
|
get:
|
|
tags: [Backups]
|
|
operationId: listBackups
|
|
description: >-
|
|
Administrator-only (docs/18-backup-restore.md). Never includes a filesystem
|
|
path or connection string — only what an administrator needs to judge backup
|
|
health.
|
|
responses:
|
|
"200":
|
|
description: Completed backups, newest first
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/BackupListResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
post:
|
|
tags: [Backups]
|
|
operationId: createBackup
|
|
description: >-
|
|
Synchronously creates and verifies a full backup (every table plus every
|
|
content-addressed attachment blob), then applies retention. A failed backup
|
|
never replaces the last known-good one. Restore is intentionally not exposed
|
|
here — it runs as a separate maintenance-mode invocation of the same
|
|
deployable image (docs/18-backup-restore.md; ADR-016 decision 4).
|
|
responses:
|
|
"200":
|
|
description: The newly created, verified backup
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/BackupSummaryResponse"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"409":
|
|
description: A backup is already in progress
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
/me/preferences:
|
|
get:
|
|
tags: [Preferences]
|
|
operationId: getMyPreferences
|
|
security: [{ cookieAuth: [] }]
|
|
responses:
|
|
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/UserPreferences" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
put:
|
|
tags: [Preferences]
|
|
operationId: updateMyPreferences
|
|
security: [{ cookieAuth: [] }]
|
|
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/UserPreferencesRequest" } } } }
|
|
responses:
|
|
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/UserPreferences" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
"412": { $ref: "#/components/responses/PreconditionFailed" }
|
|
"428": { $ref: "#/components/responses/PreconditionRequired" }
|
|
/admin/platform-settings:
|
|
get:
|
|
tags: [Admin]
|
|
operationId: getPlatformSettings
|
|
security: [{ cookieAuth: [] }]
|
|
responses:
|
|
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/PlatformSettings" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
put:
|
|
tags: [Admin]
|
|
operationId: updatePlatformSettings
|
|
security: [{ cookieAuth: [] }]
|
|
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/PlatformSettingsRequest" } } } }
|
|
responses:
|
|
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/PlatformSettings" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
"412": { $ref: "#/components/responses/PreconditionFailed" }
|
|
"428": { $ref: "#/components/responses/PreconditionRequired" }
|
|
/admin/data-sources:
|
|
get:
|
|
tags: [DataSources]
|
|
operationId: listDataSources
|
|
security: [{ cookieAuth: [] }]
|
|
responses:
|
|
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/DataSourceList" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
post:
|
|
tags: [DataSources]
|
|
operationId: registerDataSource
|
|
security: [{ cookieAuth: [] }]
|
|
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/DataSourceRegistration" } } } }
|
|
responses:
|
|
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/DataSource" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
/admin/data-sources/{id}:
|
|
put:
|
|
tags: [DataSources]
|
|
operationId: configureDataSource
|
|
security: [{ cookieAuth: [] }]
|
|
parameters: [{ name: id, in: path, required: true, schema: { type: string, format: uuid } }]
|
|
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/DataSourceConfiguration" } } } }
|
|
responses:
|
|
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/DataSource" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
"412": { $ref: "#/components/responses/PreconditionFailed" }
|
|
"428": { $ref: "#/components/responses/PreconditionRequired" }
|
|
/admin/data-sources/{id}/observations:
|
|
post:
|
|
tags: [DataSources]
|
|
operationId: reportDataSourceObservation
|
|
security: [{ cookieAuth: [] }]
|
|
parameters: [{ name: id, in: path, required: true, schema: { type: string, format: uuid } }]
|
|
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/DataSourceObservation" } } } }
|
|
responses:
|
|
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/DataSource" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
/admin/backups/status:
|
|
get:
|
|
tags: [Backups]
|
|
operationId: getBackupStatus
|
|
security: [{ cookieAuth: [] }]
|
|
responses:
|
|
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackupStatus" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
/admin/backups/{backupId}/protection:
|
|
put:
|
|
tags: [Backups]
|
|
operationId: setBackupProtection
|
|
security: [{ cookieAuth: [] }]
|
|
parameters: [{ name: backupId, in: path, required: true, schema: { type: string } }]
|
|
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/BackupProtectionRequest" } } } }
|
|
responses:
|
|
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackupEvidence" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
"412": { $ref: "#/components/responses/PreconditionFailed" }
|
|
"428": { $ref: "#/components/responses/PreconditionRequired" }
|
|
/admin/audit-log:
|
|
get:
|
|
tags: [Admin]
|
|
operationId: listAdminAuditLog
|
|
security: [{ cookieAuth: [] }]
|
|
responses:
|
|
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/AdminAuditList" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
/jobs:
|
|
get:
|
|
tags: [Jobs]
|
|
operationId: listBackgroundJobs
|
|
security: [{ cookieAuth: [] }]
|
|
responses:
|
|
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackgroundJobList" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
/jobs/course-tagging:
|
|
post:
|
|
tags: [Jobs]
|
|
operationId: queueCourseTaggingJob
|
|
security: [{ cookieAuth: [] }]
|
|
parameters: [{ $ref: "#/components/parameters/RequiredIdempotencyKey" }]
|
|
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/QueueCourseTaggingJobRequest" } } } }
|
|
responses:
|
|
"202": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackgroundJob" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
/jobs/backups:
|
|
post:
|
|
tags: [Jobs]
|
|
operationId: queueBackupJob
|
|
security: [{ cookieAuth: [] }]
|
|
parameters: [{ $ref: "#/components/parameters/RequiredIdempotencyKey" }]
|
|
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/QueueBackupJobRequest" } } } }
|
|
responses:
|
|
"202": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackgroundJob" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
/jobs/{jobId}:
|
|
get:
|
|
tags: [Jobs]
|
|
operationId: getBackgroundJob
|
|
security: [{ cookieAuth: [] }]
|
|
parameters: [{ name: jobId, in: path, required: true, schema: { type: string, format: uuid } }]
|
|
responses:
|
|
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackgroundJob" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
/jobs/{jobId}/cancel:
|
|
post:
|
|
tags: [Jobs]
|
|
operationId: cancelBackgroundJob
|
|
security: [{ cookieAuth: [] }]
|
|
parameters: [{ name: jobId, in: path, required: true, schema: { type: string, format: uuid } }]
|
|
responses:
|
|
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackgroundJob" } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
/jobs/{jobId}/events:
|
|
get:
|
|
tags: [Jobs]
|
|
operationId: streamBackgroundJobEvents
|
|
security: [{ cookieAuth: [] }]
|
|
parameters: [{ name: jobId, in: path, required: true, schema: { type: string, format: uuid } }]
|
|
responses:
|
|
"200": { description: Job and progress Server-Sent Events with heartbeat and terminal close, content: { text/event-stream: { schema: { type: string } } } }
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
|
components:
|
|
securitySchemes:
|
|
cookieAuth:
|
|
type: apiKey
|
|
in: cookie
|
|
name: ikh_auth
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: IKH service token
|
|
parameters:
|
|
CorrelationId:
|
|
name: X-Correlation-ID
|
|
in: header
|
|
required: false
|
|
schema:
|
|
type: string
|
|
maxLength: 100
|
|
IdempotencyKey:
|
|
name: Idempotency-Key
|
|
in: header
|
|
required: false
|
|
schema:
|
|
type: string
|
|
minLength: 16
|
|
maxLength: 200
|
|
RequiredIdempotencyKey:
|
|
name: Idempotency-Key
|
|
in: header
|
|
required: true
|
|
schema:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
Cursor:
|
|
name: cursor
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
maxLength: 2048
|
|
Limit:
|
|
name: limit
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 100
|
|
default: 25
|
|
PageId:
|
|
name: pageId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
SessionId:
|
|
name: sessionId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
RevisionNumber:
|
|
name: revisionNumber
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
format: int64
|
|
minimum: 1
|
|
ImportJobId:
|
|
name: jobId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
ProjectId:
|
|
name: projectId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
UserId:
|
|
name: userId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
format: uuid
|
|
DraftKey:
|
|
name: draftKey
|
|
in: path
|
|
required: true
|
|
description: The page ID in edit mode, or a client-generated key for an unpublished new page.
|
|
schema:
|
|
type: string
|
|
IfMatch:
|
|
name: If-Match
|
|
in: header
|
|
required: true
|
|
schema:
|
|
type: string
|
|
headers:
|
|
ETag:
|
|
description: Strong entity version tag
|
|
schema:
|
|
type: string
|
|
responses:
|
|
BadRequest:
|
|
description: Invalid request
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
Unauthorized:
|
|
description: Authentication required
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
Forbidden:
|
|
description: Insufficient permission
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
NotFound:
|
|
description: Resource not found or not disclosed
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
Conflict:
|
|
description: Idempotency or domain conflict
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
PreconditionFailed:
|
|
description: ETag does not match the current resource
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
PreconditionRequired:
|
|
description: If-Match is required
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
ValidationFailed:
|
|
description: Request failed validation
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
RateLimited:
|
|
description: Request rate limited
|
|
headers:
|
|
Retry-After:
|
|
schema:
|
|
type: integer
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: "#/components/schemas/ProblemDetails"
|
|
schemas:
|
|
UserPreferencesRequest:
|
|
type: object
|
|
required: [theme, density, primaryNavigationCollapsed, contextPanelOpen, navigationOrder, dashboardWidgetOrder, hiddenDashboardWidgets, defaultLandingPage, catalogView, contextPanelWidth, navigationWidth]
|
|
properties:
|
|
theme: { type: string, enum: [system, light, dark] }
|
|
density: { type: string, enum: [comfortable, compact] }
|
|
primaryNavigationCollapsed: { type: boolean }
|
|
contextPanelOpen: { type: boolean }
|
|
navigationOrder: { type: array, items: { type: string } }
|
|
dashboardWidgetOrder: { type: array, items: { type: string } }
|
|
hiddenDashboardWidgets: { type: array, items: { type: string } }
|
|
defaultLandingPage: { type: string }
|
|
catalogView: { type: string, enum: [grid, list] }
|
|
contextPanelWidth: { type: integer, minimum: 256, maximum: 440 }
|
|
navigationWidth: { type: integer, minimum: 192, maximum: 320 }
|
|
UserPreferences:
|
|
allOf:
|
|
- $ref: "#/components/schemas/UserPreferencesRequest"
|
|
- type: object
|
|
required: [schemaVersion, eTag, isDefault, updatedAt]
|
|
properties:
|
|
schemaVersion: { type: integer, enum: [1] }
|
|
eTag: { type: string }
|
|
isDefault: { type: boolean }
|
|
updatedAt: { type: [string, "null"], format: date-time }
|
|
PlatformSettingsRequest:
|
|
type: object
|
|
required: [displayName, primaryLocale, timeZone, ingestionProposals, backupPolicyExpectation, inventoryFreshnessPolicy]
|
|
properties:
|
|
displayName: { type: string, minLength: 1, maxLength: 200 }
|
|
primaryLocale: { type: string }
|
|
timeZone: { type: string }
|
|
ingestionProposals: { type: object, required: [enabled, maximumSourceCount, maximumSourceBytes, failedRetryLimit], properties: { enabled: { type: boolean }, maximumSourceCount: { type: integer, minimum: 1, maximum: 20 }, maximumSourceBytes: { type: integer, format: int64 }, failedRetryLimit: { type: integer, minimum: 1, maximum: 3 } } }
|
|
backupPolicyExpectation: { type: object, required: [expectedIntervalHours, scheduleDescription, dailyRetentionCount, weeklyRetentionCount, monthlyRetentionCount, protectedBackupCount, restoreVerificationMaxAgeDays], properties: { expectedIntervalHours: { type: integer }, scheduleDescription: { type: string }, dailyRetentionCount: { type: integer }, weeklyRetentionCount: { type: integer }, monthlyRetentionCount: { type: integer }, protectedBackupCount: { type: integer }, restoreVerificationMaxAgeDays: { type: integer } } }
|
|
inventoryFreshnessPolicy: { type: object, required: [warningAfterHours], properties: { warningAfterHours: { type: integer } } }
|
|
PlatformSettings:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PlatformSettingsRequest"
|
|
- type: object
|
|
required: [schemaVersion, eTag, isDefault, updatedAt]
|
|
properties:
|
|
schemaVersion: { type: integer, enum: [1] }
|
|
eTag: { type: string }
|
|
isDefault: { type: boolean }
|
|
updatedAt: { type: [string, "null"], format: date-time }
|
|
backupPolicyExpectation: { type: object, description: Includes enforcement External; configured schedule is an expectation, not a scheduler run. }
|
|
DataSourceRegistration:
|
|
type: object
|
|
required: [sourceKey, displayName, kind, expectedIntervalHours]
|
|
properties:
|
|
sourceKey: { type: string, maxLength: 200 }
|
|
displayName: { type: string, maxLength: 120 }
|
|
kind: { type: string, enum: [Inventory, Gitea, KnowledgeClient, ManualIntake, LeafWiki, CuratedPackage] }
|
|
expectedIntervalHours: { type: integer, minimum: 1, maximum: 8760 }
|
|
DataSourceConfiguration:
|
|
type: object
|
|
required: [displayName, enabled, expectedIntervalHours]
|
|
properties:
|
|
displayName: { type: string, maxLength: 120 }
|
|
enabled: { type: boolean }
|
|
expectedIntervalHours: { type: integer, minimum: 1, maximum: 8760 }
|
|
DataSourceObservation:
|
|
type: object
|
|
required: [success]
|
|
properties:
|
|
success: { type: boolean }
|
|
recordCount: { type: [integer, "null"], format: int64 }
|
|
errorCode: { type: [string, "null"] }
|
|
correlationId: { type: [string, "null"], maxLength: 100 }
|
|
DataSource:
|
|
type: object
|
|
required: [id, sourceKey, displayName, kind, enabled, state, configurationState, syncAuthority, expectedIntervalHours, eTag, actions]
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
sourceKey: { type: string }
|
|
displayName: { type: string }
|
|
kind: { type: string }
|
|
enabled: { type: boolean }
|
|
state: { type: string, enum: [Unknown, Current, Stale, Failed, Disabled] }
|
|
configurationState: { type: string }
|
|
syncAuthority: { type: string }
|
|
expectedIntervalHours: { type: integer }
|
|
lastAttemptAt: { type: [string, "null"], format: date-time }
|
|
lastSuccessAt: { type: [string, "null"], format: date-time }
|
|
lastFailureAt: { type: [string, "null"], format: date-time }
|
|
dataObservedAt: { type: [string, "null"], format: date-time }
|
|
recordCount: { type: [integer, "null"], format: int64 }
|
|
lastErrorCode: { type: [string, "null"] }
|
|
lastCorrelationId: { type: [string, "null"] }
|
|
eTag: { type: string }
|
|
actions: { type: array, items: { type: string } }
|
|
DataSourceList:
|
|
type: object
|
|
required: [items, nextCursor]
|
|
properties:
|
|
items: { type: array, items: { $ref: "#/components/schemas/DataSource" } }
|
|
nextCursor: { type: [string, "null"] }
|
|
BackupProtectionRequest:
|
|
type: object
|
|
required: [protected]
|
|
properties:
|
|
protected: { type: boolean }
|
|
BackupEvidence:
|
|
type: object
|
|
required: [protected, protectionVersion]
|
|
properties:
|
|
protected: { type: boolean }
|
|
protectionVersion: { type: integer }
|
|
integrityVerifiedAt: { type: [string, "null"], format: date-time }
|
|
restoreVerifiedAt: { type: [string, "null"], format: date-time }
|
|
lastRestoreSucceeded: { type: [boolean, "null"] }
|
|
BackupStatus:
|
|
type: object
|
|
required: [state, scheduleAuthority, scheduleDescription, restoreConfidence, storedBackupBytes, protectedCount, expectedIntervalHours, applicationRetentionCount, dailyRetentionExpectation, weeklyRetentionExpectation, monthlyRetentionExpectation, restoreVerificationMaxAgeDays]
|
|
properties:
|
|
state: { type: string, enum: [Unknown, Current, Stale] }
|
|
scheduleAuthority: { type: string, enum: [External] }
|
|
scheduleDescription: { type: string }
|
|
nextScheduledRun: { type: [string, "null"], format: date-time }
|
|
nextExpectedBy: { type: [string, "null"], format: date-time }
|
|
lastSuccessfulBackupAt: { type: [string, "null"], format: date-time }
|
|
lastIntegrityVerifiedAt: { type: [string, "null"], format: date-time }
|
|
lastVerifiedRestoreAt: { type: [string, "null"], format: date-time }
|
|
restoreConfidence: { type: string, enum: [Unknown, Stale, Verified] }
|
|
storedBackupBytes: { type: integer, format: int64, description: Sum of stored backup manifest sizes; not filesystem capacity. }
|
|
protectedCount: { type: integer }
|
|
expectedIntervalHours: { type: integer }
|
|
applicationRetentionCount: { type: integer }
|
|
dailyRetentionExpectation: { type: integer }
|
|
weeklyRetentionExpectation: { type: integer }
|
|
monthlyRetentionExpectation: { type: integer }
|
|
restoreVerificationMaxAgeDays: { type: integer }
|
|
AdminAuditList:
|
|
type: object
|
|
required: [items, nextCursor]
|
|
properties:
|
|
items: { type: array, items: { $ref: "#/components/schemas/AdminAuditItem" } }
|
|
nextCursor: { type: [integer, "null"], format: int64 }
|
|
AdminAuditItem:
|
|
type: object
|
|
required: [id, actorKind, source, action, summary, occurredAt]
|
|
properties:
|
|
id: { type: integer, format: int64 }
|
|
actorKind: { type: string }
|
|
actorId: { type: [string, "null"], format: uuid }
|
|
source: { type: string }
|
|
action: { type: string }
|
|
targetType: { type: [string, "null"] }
|
|
targetId: { type: [string, "null"] }
|
|
summary: { type: string }
|
|
occurredAt: { type: string, format: date-time }
|
|
correlationId: { type: [string, "null"] }
|
|
QueueCourseTaggingJobRequest:
|
|
type: object
|
|
required: [rootPageId, tag]
|
|
properties:
|
|
rootPageId: { type: string, format: uuid }
|
|
tag: { type: string }
|
|
QueueBackupJobRequest: { type: object, description: Empty object; the job snapshots the current server-side backup policy. }
|
|
BackgroundJob:
|
|
type: object
|
|
required: [id, workspaceId, actorId, type, status, createdAt, cancelRequested, progress, total, processed, succeeded, skipped, failed, errors, version]
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
workspaceId: { type: string, format: uuid }
|
|
actorId: { type: string, format: uuid }
|
|
type: { type: string }
|
|
status: { type: string, enum: [queued, running, succeeded, partially_succeeded, failed, cancelled] }
|
|
createdAt: { type: string, format: date-time }
|
|
startedAt: { type: [string, "null"], format: date-time }
|
|
completedAt: { type: [string, "null"], format: date-time }
|
|
cancelRequestedAt: { type: [string, "null"], format: date-time }
|
|
cancelRequested: { type: boolean }
|
|
progress: { type: integer, minimum: 0, maximum: 100 }
|
|
total: { type: integer }
|
|
processed: { type: integer }
|
|
succeeded: { type: integer }
|
|
skipped: { type: integer }
|
|
failed: { type: integer }
|
|
errors: { type: array, items: { type: object, properties: { code: { type: string }, message: { type: string }, itemId: { type: [string, "null"], format: uuid } } } }
|
|
result: { type: [object, "null"] }
|
|
correlationId: { type: [string, "null"] }
|
|
version: { type: integer, format: int64 }
|
|
BackgroundJobList:
|
|
type: object
|
|
required: [items]
|
|
properties:
|
|
items: { type: array, items: { $ref: "#/components/schemas/BackgroundJob" } }
|
|
CreateIngestionProposalRequest:
|
|
type: object
|
|
required: [manifest]
|
|
properties:
|
|
manifest: { type: string, minLength: 1, maxLength: 100000 }
|
|
correlationId: { type: [string, "null"], maxLength: 200 }
|
|
publication: { oneOf: [{ $ref: "#/components/schemas/IngestionPublicationRequest" }, { type: "null" }] }
|
|
IngestionPublicationRequest:
|
|
type: object
|
|
required: [title, markdown, documentType, documentStatus, mutationKind]
|
|
properties:
|
|
title: { type: string, minLength: 1, maxLength: 200 }
|
|
markdown: { type: string, minLength: 1, maxLength: 100000 }
|
|
documentType: { $ref: "#/components/schemas/DocumentType" }
|
|
documentStatus: { $ref: "#/components/schemas/DocumentStatus" }
|
|
mutationKind: { type: string, enum: [CreatePage, UpdatePage] }
|
|
parentPageId: { type: [string, "null"], format: uuid }
|
|
targetPageId: { type: [string, "null"], format: uuid }
|
|
expectedTargetETag: { type: [string, "null"], maxLength: 32 }
|
|
PatchIngestionProposalRequest:
|
|
type: object
|
|
description: Human callers use manifest/rationale; the submitting service account uses analysis only.
|
|
properties:
|
|
manifest: { type: [string, "null"], maxLength: 100000 }
|
|
rationale: { type: [string, "null"], maxLength: 2000 }
|
|
analysis: { type: [string, "null"], maxLength: 100000 }
|
|
IngestionProposalDecisionRequest:
|
|
type: object
|
|
properties:
|
|
comment: { type: [string, "null"], maxLength: 2000 }
|
|
IngestionProposalSourceDetail:
|
|
type: object
|
|
required: [id, fileName, mimeType, byteSize, digestSha256, scanResult, attachmentReferenceId]
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
fileName: { type: string }
|
|
mimeType: { type: string }
|
|
byteSize: { type: integer, format: int64 }
|
|
digestSha256: { type: string, pattern: '^[a-f0-9]{64}$' }
|
|
scanResult: { type: string }
|
|
attachmentReferenceId: { type: string, format: uuid }
|
|
IngestionProposalRevisionDetail:
|
|
type: object
|
|
required: [revisionNumber, status, operation, actorId, occurredAt]
|
|
properties:
|
|
revisionNumber: { type: integer, format: int64 }
|
|
status: { type: string }
|
|
operation: { type: string }
|
|
actorId: { type: string, format: uuid }
|
|
occurredAt: { type: string, format: date-time }
|
|
IngestionProposalSummary:
|
|
type: object
|
|
required: [id, correlationId, status, revisionNumber, sourceCount, submitterId, createdAt, destination, metadata, mutationKind]
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
correlationId: { type: string }
|
|
status: { type: string }
|
|
revisionNumber: { type: integer, format: int64 }
|
|
sourceCount: { type: integer }
|
|
submitterId: { type: string, format: uuid }
|
|
createdAt: { type: string, format: date-time }
|
|
destination: { oneOf: [{ $ref: "#/components/schemas/IngestionProposalDestination" }, { type: "null" }] }
|
|
metadata: { $ref: "#/components/schemas/IngestionProposalMetadata" }
|
|
mutationKind: { type: [string, "null"], enum: [CreatePage, UpdatePage, null] }
|
|
IngestionProposalListResponse:
|
|
type: object
|
|
required: [items, nextCursor, page, pageSize, total, correlationId]
|
|
properties:
|
|
items: { type: array, items: { $ref: "#/components/schemas/IngestionProposalSummary" } }
|
|
nextCursor: { type: [string, "null"] }
|
|
page: { type: integer, minimum: 1 }
|
|
pageSize: { type: integer, minimum: 1, maximum: 100 }
|
|
total: { type: integer, format: int64, minimum: 0 }
|
|
correlationId: { type: string }
|
|
IngestionProposalDestination:
|
|
type: object
|
|
required: [pageId, parentPageId, title]
|
|
properties:
|
|
pageId: { type: [string, "null"], format: uuid }
|
|
parentPageId: { type: [string, "null"], format: uuid }
|
|
title: { type: [string, "null"] }
|
|
IngestionProposalMetadata:
|
|
type: object
|
|
description: Project and risk are currently unavailable persisted attributes and are therefore explicitly null.
|
|
required: [documentType, documentStatus, projectId, risk]
|
|
properties:
|
|
documentType: { type: [string, "null"] }
|
|
documentStatus: { type: [string, "null"] }
|
|
projectId: { type: [string, "null"], format: uuid }
|
|
risk: { type: [string, "null"], enum: [low, medium, high, null] }
|
|
IngestionProposalCitation:
|
|
type: object
|
|
required: [label, pageId, excerpt, confidence]
|
|
properties:
|
|
label: { type: string }
|
|
pageId: { type: [string, "null"], format: uuid }
|
|
excerpt: { type: [string, "null"] }
|
|
confidence: { type: [number, "null"] }
|
|
IngestionProposalWarning:
|
|
type: object
|
|
required: [code, message, severity]
|
|
properties:
|
|
code: { type: string }
|
|
message: { type: string }
|
|
severity: { type: [string, "null"], enum: [low, medium, high, null] }
|
|
IngestionProposalCandidate:
|
|
type: object
|
|
required: [pageId, title, kind]
|
|
properties:
|
|
pageId: { type: [string, "null"], format: uuid }
|
|
title: { type: [string, "null"] }
|
|
kind: { type: [string, "null"] }
|
|
IngestionProposalDiff:
|
|
type: object
|
|
required: [before, after, summary]
|
|
properties:
|
|
before: { type: [string, "null"] }
|
|
after: { type: [string, "null"] }
|
|
summary: { type: [string, "null"] }
|
|
IngestionProposalPermissions:
|
|
type: object
|
|
required: [canEdit, canDecide, canPublish]
|
|
properties:
|
|
canEdit: { type: boolean }
|
|
canDecide: { type: boolean }
|
|
canPublish: { type: boolean }
|
|
IngestionProposalDetail:
|
|
type: object
|
|
description: Safe dossier metadata only; it never contains source bytes, paths, URLs, credentials, or tokens.
|
|
required: [id, correlationId, status, revisionNumber, manifest, retryCount, maxPublishRetries, isRetryEligible, sources, revisions, etag, submitterId, createdAt, destination, metadata, mutationKind, attachments, expectedTargetETag, citations, warnings, duplicateCandidates, conflictCandidates, diff, permissions]
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
correlationId: { type: string }
|
|
status: { type: string }
|
|
revisionNumber: { type: integer, format: int64 }
|
|
manifest: { type: string }
|
|
analysis: { type: [string, "null"] }
|
|
reviewerComment: { type: [string, "null"] }
|
|
retryCount: { type: integer }
|
|
maxPublishRetries: { type: integer }
|
|
isRetryEligible: { type: boolean }
|
|
failedLifecycleStatus: { type: [string, "null"] }
|
|
failedProcessingStage: { type: [string, "null"] }
|
|
failureErrorType: { type: [string, "null"] }
|
|
failureMessage: { type: [string, "null"] }
|
|
sources: { type: array, items: { $ref: "#/components/schemas/IngestionProposalSourceDetail" } }
|
|
revisions: { type: array, items: { $ref: "#/components/schemas/IngestionProposalRevisionDetail" } }
|
|
etag: { type: string }
|
|
publishedPageId: { type: [string, "null"], format: uuid }
|
|
publishedPageRevisionNumber: { type: [integer, "null"], format: int64 }
|
|
submitterId: { type: string, format: uuid }
|
|
createdAt: { type: string, format: date-time }
|
|
destination: { oneOf: [{ $ref: "#/components/schemas/IngestionProposalDestination" }, { type: "null" }] }
|
|
metadata: { $ref: "#/components/schemas/IngestionProposalMetadata" }
|
|
mutationKind: { type: [string, "null"], enum: [CreatePage, UpdatePage, null] }
|
|
attachments: { type: array, items: { $ref: "#/components/schemas/IngestionProposalSourceDetail" } }
|
|
expectedTargetETag: { type: [string, "null"] }
|
|
citations: { type: array, items: { $ref: "#/components/schemas/IngestionProposalCitation" } }
|
|
warnings: { type: array, items: { $ref: "#/components/schemas/IngestionProposalWarning" } }
|
|
duplicateCandidates: { type: array, items: { $ref: "#/components/schemas/IngestionProposalCandidate" } }
|
|
conflictCandidates: { type: array, items: { $ref: "#/components/schemas/IngestionProposalCandidate" } }
|
|
diff:
|
|
description: after contains the proposed publication Markdown. before is null when no current target snapshot is available.
|
|
oneOf: [{ $ref: "#/components/schemas/IngestionProposalDiff" }, { type: "null" }]
|
|
permissions: { $ref: "#/components/schemas/IngestionProposalPermissions" }
|
|
SystemInfo:
|
|
type: object
|
|
required: [product, version, commit, schemaVersion, environment, primaryLocale, timeZone, uptimeSeconds, release]
|
|
properties:
|
|
product:
|
|
type: string
|
|
const: ItWorx Knowledge Hub
|
|
version:
|
|
type: string
|
|
commit:
|
|
type: string
|
|
schemaVersion:
|
|
type: string
|
|
environment:
|
|
type: string
|
|
primaryLocale:
|
|
type: string
|
|
timeZone:
|
|
type: string
|
|
uptimeSeconds:
|
|
type: integer
|
|
release:
|
|
$ref: "#/components/schemas/ReleaseIdentity"
|
|
ReleaseIdentity:
|
|
type: object
|
|
description: >-
|
|
The canonical, machine-readable release identity
|
|
(docs/adr/ADR-029-v2-r5-final-hardening.md). Every field is honestly nullable —
|
|
a value not actually known at build or deploy time is never fabricated.
|
|
required: [semanticVersion, releaseTag, sourceCommit, imageRepository, imageTag, imageId, imageDigest, buildTimestamp, deploymentTimestamp, openApiVersion, openApiHash, frontendBuildHash, databaseMigrationRange, inventorySnapshotId, backupId]
|
|
properties:
|
|
semanticVersion:
|
|
type: [string, "null"]
|
|
releaseTag:
|
|
type: [string, "null"]
|
|
sourceCommit:
|
|
type: [string, "null"]
|
|
imageRepository:
|
|
type: [string, "null"]
|
|
imageTag:
|
|
type: [string, "null"]
|
|
imageId:
|
|
type: [string, "null"]
|
|
imageDigest:
|
|
type: [string, "null"]
|
|
buildTimestamp:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
deploymentTimestamp:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
openApiVersion:
|
|
type: [string, "null"]
|
|
openApiHash:
|
|
type: [string, "null"]
|
|
frontendBuildHash:
|
|
type: [string, "null"]
|
|
databaseMigrationRange:
|
|
type: [string, "null"]
|
|
description: "The first and last applied EF Core migration for the database this instance is actually connected to, formatted as 'first..last' (or a single migration name if only one has been applied)."
|
|
inventorySnapshotId:
|
|
type: [string, "null"]
|
|
backupId:
|
|
type: [string, "null"]
|
|
format: int64
|
|
minimum: 0
|
|
DocumentType:
|
|
type: string
|
|
enum:
|
|
- WikiPage
|
|
- StudyNote
|
|
- CourseModule
|
|
- ProjectDocument
|
|
- ArchitectureDecision
|
|
- Runbook
|
|
- Troubleshooting
|
|
- Reference
|
|
- ChangeLog
|
|
- SessionNote
|
|
- Checklist
|
|
DocumentStatus:
|
|
type: string
|
|
enum: [Draft, Active, NeedsReview, Deprecated, Archived]
|
|
PageSummary:
|
|
type: object
|
|
required: [id, parentPageId, title, slug, documentType, documentStatus, hasChildren, updatedAt]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
parentPageId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
title:
|
|
type: string
|
|
slug:
|
|
type: string
|
|
documentType:
|
|
$ref: "#/components/schemas/DocumentType"
|
|
documentStatus:
|
|
$ref: "#/components/schemas/DocumentStatus"
|
|
hasChildren:
|
|
type: boolean
|
|
updatedAt:
|
|
type: string
|
|
format: date-time
|
|
PageChildrenResponse:
|
|
type: object
|
|
required: [items, nextCursor]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/PageSummary"
|
|
nextCursor:
|
|
type: [string, "null"]
|
|
BreadcrumbEntry:
|
|
type: object
|
|
required: [id, title]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
title:
|
|
type: string
|
|
PageDetail:
|
|
type: object
|
|
required:
|
|
- id
|
|
- workspaceId
|
|
- parentPageId
|
|
- title
|
|
- slug
|
|
- markdown
|
|
- renderedHtml
|
|
- documentType
|
|
- documentStatus
|
|
- currentRevisionNumber
|
|
- tags
|
|
- breadcrumb
|
|
- createdBy
|
|
- updatedBy
|
|
- createdAt
|
|
- updatedAt
|
|
- publishedAt
|
|
- trashedAt
|
|
- eTag
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
workspaceId:
|
|
type: string
|
|
format: uuid
|
|
parentPageId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
title:
|
|
type: string
|
|
slug:
|
|
type: string
|
|
markdown:
|
|
type: string
|
|
renderedHtml:
|
|
type: string
|
|
description: Server-rendered and sanitized. Never render untrusted Markdown as HTML client-side.
|
|
documentType:
|
|
$ref: "#/components/schemas/DocumentType"
|
|
documentStatus:
|
|
$ref: "#/components/schemas/DocumentStatus"
|
|
currentRevisionNumber:
|
|
type: integer
|
|
format: int64
|
|
tags:
|
|
type: array
|
|
items:
|
|
type: string
|
|
breadcrumb:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/BreadcrumbEntry"
|
|
createdBy:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
updatedBy:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
updatedAt:
|
|
type: string
|
|
format: date-time
|
|
publishedAt:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
trashedAt:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
description: Null unless the page is in the trash/retention model.
|
|
eTag:
|
|
type: string
|
|
description: Also present as the response ETag header; included in the body so clients need not parse headers.
|
|
CreatePageRequest:
|
|
type: object
|
|
required: [parentPageId, title, documentType, markdown, templateKey]
|
|
description: >-
|
|
Either supply documentType and markdown directly, or supply templateKey to seed
|
|
the page from a starter template (in which case documentType/markdown are
|
|
ignored and may be null).
|
|
properties:
|
|
parentPageId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
title:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 300
|
|
documentType:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/DocumentType"
|
|
- type: "null"
|
|
markdown:
|
|
type: [string, "null"]
|
|
maxLength: 2097152
|
|
templateKey:
|
|
type: [string, "null"]
|
|
tags:
|
|
type: array
|
|
items:
|
|
type: string
|
|
maxItems: 25
|
|
BeginPageCreationSessionRequest:
|
|
type: object
|
|
properties:
|
|
parentPageId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
PageCreationSessionResponse:
|
|
type: object
|
|
required: [sessionId, expiresAt]
|
|
properties:
|
|
sessionId:
|
|
type: string
|
|
format: uuid
|
|
expiresAt:
|
|
type: string
|
|
format: date-time
|
|
CommitPageCreationSessionRequest:
|
|
type: object
|
|
required: [title]
|
|
description: >-
|
|
No parentPageId (fixed at session-begin time) or templateKey (a template and
|
|
pre-publish attachments are mutually exclusive, matching the existing
|
|
product behavior).
|
|
properties:
|
|
title:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 300
|
|
documentType:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/DocumentType"
|
|
- type: "null"
|
|
markdown:
|
|
type: [string, "null"]
|
|
maxLength: 2097152
|
|
tags:
|
|
type: array
|
|
items:
|
|
type: string
|
|
maxItems: 25
|
|
DiscardPageCreationSessionResponse:
|
|
type: object
|
|
required: [attachmentsRemoved]
|
|
properties:
|
|
attachmentsRemoved:
|
|
type: integer
|
|
UpdatePageRequest:
|
|
type: object
|
|
required: [title, markdown, documentType, documentStatus, changeSummary]
|
|
properties:
|
|
title:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 300
|
|
markdown:
|
|
type: string
|
|
maxLength: 2097152
|
|
documentType:
|
|
$ref: "#/components/schemas/DocumentType"
|
|
documentStatus:
|
|
$ref: "#/components/schemas/DocumentStatus"
|
|
changeSummary:
|
|
type: [string, "null"]
|
|
maxLength: 500
|
|
tags:
|
|
type: array
|
|
items:
|
|
type: string
|
|
maxItems: 25
|
|
description: Omitted or null leaves the page's tags unchanged.
|
|
MovePageRequest:
|
|
type: object
|
|
required: [newParentPageId, afterSiblingId]
|
|
properties:
|
|
newParentPageId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
afterSiblingId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
description: Null means "first among the new siblings".
|
|
TemplateSummary:
|
|
type: object
|
|
required: [key, name, documentType, description]
|
|
properties:
|
|
key:
|
|
type: string
|
|
name:
|
|
type: string
|
|
documentType:
|
|
$ref: "#/components/schemas/DocumentType"
|
|
description:
|
|
type: string
|
|
DraftResponse:
|
|
type: object
|
|
required: [draftKey, pageId, baseRevisionNumber, title, markdown, updatedAt, expiresAt, baseRevisionIsStale]
|
|
properties:
|
|
draftKey:
|
|
type: string
|
|
pageId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
baseRevisionNumber:
|
|
type: [integer, "null"]
|
|
format: int64
|
|
title:
|
|
type: string
|
|
markdown:
|
|
type: string
|
|
updatedAt:
|
|
type: string
|
|
format: date-time
|
|
expiresAt:
|
|
type: string
|
|
format: date-time
|
|
baseRevisionIsStale:
|
|
type: boolean
|
|
description: True when the page has published a newer revision since this draft's base.
|
|
SaveDraftRequest:
|
|
type: object
|
|
required: [pageId, baseRevisionNumber, title, markdown]
|
|
properties:
|
|
pageId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
baseRevisionNumber:
|
|
type: [integer, "null"]
|
|
format: int64
|
|
title:
|
|
type: string
|
|
markdown:
|
|
type: string
|
|
PageRevisionSummary:
|
|
type: object
|
|
required: [revisionNumber, title, documentType, documentStatus, authoredBy, changeSummary, createdAt]
|
|
properties:
|
|
revisionNumber:
|
|
type: integer
|
|
format: int64
|
|
title:
|
|
type: string
|
|
documentType:
|
|
$ref: "#/components/schemas/DocumentType"
|
|
documentStatus:
|
|
$ref: "#/components/schemas/DocumentStatus"
|
|
authoredBy:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
changeSummary:
|
|
type: [string, "null"]
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
PageRevisionDetailResponse:
|
|
type: object
|
|
required: [revisionNumber, title, markdown, renderedHtml, documentType, documentStatus, authoredBy, changeSummary, createdAt]
|
|
properties:
|
|
revisionNumber:
|
|
type: integer
|
|
format: int64
|
|
title:
|
|
type: string
|
|
markdown:
|
|
type: string
|
|
renderedHtml:
|
|
type: string
|
|
description: >-
|
|
Server-rendered and sanitized without re-resolving internal links against
|
|
today's link table (a historical snapshot's links are not live).
|
|
documentType:
|
|
$ref: "#/components/schemas/DocumentType"
|
|
documentStatus:
|
|
$ref: "#/components/schemas/DocumentStatus"
|
|
authoredBy:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
changeSummary:
|
|
type: [string, "null"]
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
BacklinkEntry:
|
|
type: object
|
|
required: [id, title, slug, documentType]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
title:
|
|
type: string
|
|
slug:
|
|
type: string
|
|
documentType:
|
|
$ref: "#/components/schemas/DocumentType"
|
|
BacklinksResponse:
|
|
type: object
|
|
required: [items]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/BacklinkEntry"
|
|
TrashPurgeResponse:
|
|
type: object
|
|
required: [purgedCount]
|
|
properties:
|
|
purgedCount:
|
|
type: integer
|
|
AttachmentDeliveryPolicy:
|
|
type: string
|
|
enum: [InlinePreviewEligible, DownloadOnly, Unknown]
|
|
AttachmentSummary:
|
|
type: object
|
|
required: [id, filename, mediaType, deliveryPolicy, byteLength, description, altText, createdAt]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
filename:
|
|
type: string
|
|
description: The sanitized, stored display filename — never necessarily identical to what the client declared.
|
|
mediaType:
|
|
type: string
|
|
description: The detected media type from the byte signature, not the caller-declared Content-Type.
|
|
deliveryPolicy:
|
|
$ref: "#/components/schemas/AttachmentDeliveryPolicy"
|
|
byteLength:
|
|
type: integer
|
|
format: int64
|
|
description:
|
|
type: [string, "null"]
|
|
altText:
|
|
type: [string, "null"]
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
AttachmentListResponse:
|
|
type: object
|
|
required: [items]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/AttachmentSummary"
|
|
ManualOverrideFieldCatalogEntry:
|
|
type: object
|
|
required: [fieldName, displayLabel, valueKind, maxStringLength, allowedEnumValues]
|
|
properties:
|
|
fieldName:
|
|
type: string
|
|
displayLabel:
|
|
type: string
|
|
valueKind:
|
|
type: string
|
|
enum: [Text, Enum]
|
|
maxStringLength:
|
|
type: integer
|
|
description: 0 for an Enum-kind field, where length is governed by allowedEnumValues instead.
|
|
allowedEnumValues:
|
|
type: [array, "null"]
|
|
items:
|
|
type: string
|
|
ManualOverrideFieldCatalogResponse:
|
|
type: object
|
|
required: [targetKind, fields]
|
|
properties:
|
|
targetKind:
|
|
type: string
|
|
fields:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ManualOverrideFieldCatalogEntry"
|
|
FactProvenance:
|
|
type: object
|
|
required: [sourceKey, confidence, lastSnapshotId, observedAt]
|
|
properties:
|
|
sourceKey:
|
|
type: string
|
|
confidence:
|
|
type: string
|
|
enum: [Observed, Inferred, Declared, Unknown]
|
|
lastSnapshotId:
|
|
type: string
|
|
format: uuid
|
|
observedAt:
|
|
type: string
|
|
format: date-time
|
|
InventorySnapshotSummary:
|
|
type: object
|
|
required: [id, sourceKey, sourceHash, capturedAt, importedAt, hostCount, serviceCount]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
sourceKey:
|
|
type: string
|
|
sourceHash:
|
|
type: string
|
|
capturedAt:
|
|
type: string
|
|
format: date-time
|
|
importedAt:
|
|
type: string
|
|
format: date-time
|
|
hostCount:
|
|
type: integer
|
|
serviceCount:
|
|
type: integer
|
|
InfrastructureHostSummary:
|
|
type: object
|
|
required: [id, sourceKey, hostname, operatingSystem, kernelVersion, architecture, logicalCpuCount, totalMemoryBytes, presence, lastSeenAt]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
sourceKey:
|
|
type: string
|
|
hostname:
|
|
type: string
|
|
operatingSystem:
|
|
type: string
|
|
kernelVersion:
|
|
type: string
|
|
architecture:
|
|
type: string
|
|
logicalCpuCount:
|
|
type: integer
|
|
totalMemoryBytes:
|
|
type: integer
|
|
format: int64
|
|
presence:
|
|
type: string
|
|
enum: [Present, Missing]
|
|
lastSeenAt:
|
|
type: string
|
|
format: date-time
|
|
InfrastructureOverviewResponse:
|
|
type: object
|
|
required: [latestSnapshot, hosts, serviceCount, proposedServiceCount, missingServiceCount, unhealthyServiceCount, unprotectedServiceCount]
|
|
properties:
|
|
latestSnapshot:
|
|
type: [object, "null"]
|
|
allOf:
|
|
- $ref: "#/components/schemas/InventorySnapshotSummary"
|
|
hosts:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/InfrastructureHostSummary"
|
|
serviceCount:
|
|
type: integer
|
|
proposedServiceCount:
|
|
type: integer
|
|
missingServiceCount:
|
|
type: integer
|
|
unhealthyServiceCount:
|
|
type: integer
|
|
unprotectedServiceCount:
|
|
type: integer
|
|
InfrastructureServiceSummary:
|
|
type: object
|
|
required: [id, sourceKey, name, detectedName, category, summary, runtimeKind, runtimeStatus, health, presence, confidence, imageReference, projectId, projectName, backupStatus, primaryEndpointUrl, isFavorite, isHidden, componentCount, endpointCount, volumeCount, databaseCount, lastSeenAt]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
sourceKey:
|
|
type: string
|
|
name:
|
|
type: string
|
|
description: Editorial display name if set, otherwise the detected name.
|
|
detectedName:
|
|
type: string
|
|
category:
|
|
type: string
|
|
summary:
|
|
type: string
|
|
runtimeKind:
|
|
type: string
|
|
runtimeStatus:
|
|
type: string
|
|
health:
|
|
type: string
|
|
enum: [Healthy, Degraded, Unhealthy, Unknown]
|
|
presence:
|
|
type: string
|
|
enum: [Present, Proposed, Missing]
|
|
confidence:
|
|
type: string
|
|
enum: [Observed, Inferred, Declared, Unknown]
|
|
imageReference:
|
|
type: string
|
|
projectId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
projectName:
|
|
type: [string, "null"]
|
|
backupStatus:
|
|
type: string
|
|
primaryEndpointUrl:
|
|
type: [string, "null"]
|
|
isFavorite:
|
|
type: boolean
|
|
isHidden:
|
|
type: boolean
|
|
componentCount:
|
|
type: integer
|
|
endpointCount:
|
|
type: integer
|
|
volumeCount:
|
|
type: integer
|
|
databaseCount:
|
|
type: integer
|
|
lastSeenAt:
|
|
type: string
|
|
format: date-time
|
|
InfrastructureServiceListResponse:
|
|
type: object
|
|
required: [items, nextCursor, totalCount]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/InfrastructureServiceSummary"
|
|
nextCursor:
|
|
type: [string, "null"]
|
|
totalCount:
|
|
type: integer
|
|
description: >-
|
|
How many services match the current `search`/`status`/`projectId`/
|
|
`includeHidden` filters in total (OUX-4). Counted server-side before
|
|
cursor pagination, so it is the size of the filtered catalog rather than
|
|
of the returned page, and stays stable while paging through it.
|
|
InfrastructureStorageRow:
|
|
type: object
|
|
required: [id, serviceId, serviceName, name, mountPath, storageClass, readOnly, sizeBytes, provenance]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
serviceId:
|
|
type: string
|
|
format: uuid
|
|
serviceName:
|
|
type: string
|
|
name:
|
|
type: string
|
|
mountPath:
|
|
type: string
|
|
storageClass:
|
|
type: string
|
|
readOnly:
|
|
type: boolean
|
|
sizeBytes:
|
|
type: [integer, "null"]
|
|
format: int64
|
|
provenance:
|
|
$ref: "#/components/schemas/FactProvenance"
|
|
InfrastructureStorageListResponse:
|
|
type: object
|
|
required: [items, nextCursor]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/InfrastructureStorageRow"
|
|
nextCursor:
|
|
type: [string, "null"]
|
|
EditorialProfileResponse:
|
|
type: object
|
|
required: [id, displayName, summary, category, purpose, ownerId, projectId, repositoryUrl, liveUrl, backupStatus, monitoringUrl, maintenanceNotes, knownRisks, updateProcedure, rollbackProcedure, tags, isFavorite, isHidden, isArchived, updatedAt, etag]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
displayName:
|
|
type: string
|
|
summary:
|
|
type: string
|
|
category:
|
|
type: string
|
|
purpose:
|
|
type: string
|
|
ownerId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
projectId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
repositoryUrl:
|
|
type: string
|
|
liveUrl:
|
|
type: string
|
|
backupStatus:
|
|
type: string
|
|
monitoringUrl:
|
|
type: string
|
|
maintenanceNotes:
|
|
type: string
|
|
knownRisks:
|
|
type: string
|
|
updateProcedure:
|
|
type: string
|
|
rollbackProcedure:
|
|
type: string
|
|
tags:
|
|
type: array
|
|
items:
|
|
type: string
|
|
isFavorite:
|
|
type: boolean
|
|
isHidden:
|
|
type: boolean
|
|
isArchived:
|
|
type: boolean
|
|
updatedAt:
|
|
type: string
|
|
format: date-time
|
|
etag:
|
|
type: string
|
|
ManualOverrideResponse:
|
|
type: object
|
|
required: [id, fieldName, value, reason, createdBy, updatedAt]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
fieldName:
|
|
type: string
|
|
value:
|
|
description: The overridden value — any JSON scalar, array or object, matching the field's ManualOverrideFieldCatalogEntry.valueKind.
|
|
reason:
|
|
type: string
|
|
createdBy:
|
|
type: string
|
|
format: uuid
|
|
updatedAt:
|
|
type: string
|
|
format: date-time
|
|
ServiceComponentResponse:
|
|
type: object
|
|
required: [id, name, kind, runtimeId, status, imageReference, provenance]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
name:
|
|
type: string
|
|
kind:
|
|
type: string
|
|
runtimeId:
|
|
type: string
|
|
status:
|
|
type: string
|
|
imageReference:
|
|
type: string
|
|
provenance:
|
|
$ref: "#/components/schemas/FactProvenance"
|
|
InfrastructureEndpointResponse:
|
|
type: object
|
|
required: [id, scheme, host, publishedPort, targetPort, url, isPublic, provenance]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
scheme:
|
|
type: string
|
|
host:
|
|
type: string
|
|
publishedPort:
|
|
type: [integer, "null"]
|
|
targetPort:
|
|
type: [integer, "null"]
|
|
url:
|
|
type: string
|
|
isPublic:
|
|
type: boolean
|
|
provenance:
|
|
$ref: "#/components/schemas/FactProvenance"
|
|
InfrastructureNetworkResponse:
|
|
type: object
|
|
required: [id, name, driver, scope, internal, provenance]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
name:
|
|
type: string
|
|
driver:
|
|
type: string
|
|
scope:
|
|
type: string
|
|
internal:
|
|
type: boolean
|
|
provenance:
|
|
$ref: "#/components/schemas/FactProvenance"
|
|
InfrastructureVolumeResponse:
|
|
type: object
|
|
required: [id, name, mountPath, storageClass, readOnly, sizeBytes, provenance]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
name:
|
|
type: string
|
|
mountPath:
|
|
type: string
|
|
storageClass:
|
|
type: string
|
|
readOnly:
|
|
type: boolean
|
|
sizeBytes:
|
|
type: [integer, "null"]
|
|
format: int64
|
|
provenance:
|
|
$ref: "#/components/schemas/FactProvenance"
|
|
InfrastructureDatabaseResponse:
|
|
type: object
|
|
required: [id, name, engine, version, host, port, hostPortPublished, provenance]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
name:
|
|
type: string
|
|
engine:
|
|
type: string
|
|
version:
|
|
type: string
|
|
host:
|
|
type: string
|
|
port:
|
|
type: [integer, "null"]
|
|
hostPortPublished:
|
|
type: boolean
|
|
provenance:
|
|
$ref: "#/components/schemas/FactProvenance"
|
|
ServiceDependencyResponse:
|
|
type: object
|
|
required: [id, dependsOnServiceId, dependsOnSourceKey, kind, provenance]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
dependsOnServiceId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
dependsOnSourceKey:
|
|
type: string
|
|
kind:
|
|
type: string
|
|
provenance:
|
|
$ref: "#/components/schemas/FactProvenance"
|
|
HealthObservationResponse:
|
|
type: object
|
|
required: [id, state, checkKind, responseTimeMilliseconds, provenance]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
state:
|
|
type: string
|
|
enum: [Healthy, Degraded, Unhealthy, Unknown]
|
|
checkKind:
|
|
type: string
|
|
responseTimeMilliseconds:
|
|
type: [integer, "null"]
|
|
provenance:
|
|
$ref: "#/components/schemas/FactProvenance"
|
|
DeploymentReferenceResponse:
|
|
type: object
|
|
required: [id, repositoryUrl, commit, imageReference, imageDigest, deployedAt, provenance]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
repositoryUrl:
|
|
type: string
|
|
commit:
|
|
type: string
|
|
imageReference:
|
|
type: string
|
|
imageDigest:
|
|
type: string
|
|
deployedAt:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
provenance:
|
|
$ref: "#/components/schemas/FactProvenance"
|
|
BackupCoverageResponse:
|
|
type: object
|
|
required: [id, method, schedule, lastSuccessfulAt, restoreTested, provenance]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
method:
|
|
type: string
|
|
schedule:
|
|
type: string
|
|
lastSuccessfulAt:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
restoreTested:
|
|
type: boolean
|
|
provenance:
|
|
$ref: "#/components/schemas/FactProvenance"
|
|
InfrastructureServiceDetail:
|
|
type: object
|
|
required: [service, editorialProfile, editorialETag, overrides, components, endpoints, networks, volumes, databases, dependencies, healthObservations, deployments, backups]
|
|
properties:
|
|
service:
|
|
$ref: "#/components/schemas/InfrastructureServiceSummary"
|
|
editorialProfile:
|
|
type: [object, "null"]
|
|
allOf:
|
|
- $ref: "#/components/schemas/EditorialProfileResponse"
|
|
editorialETag:
|
|
type: string
|
|
overrides:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ManualOverrideResponse"
|
|
components:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ServiceComponentResponse"
|
|
endpoints:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/InfrastructureEndpointResponse"
|
|
networks:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/InfrastructureNetworkResponse"
|
|
volumes:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/InfrastructureVolumeResponse"
|
|
databases:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/InfrastructureDatabaseResponse"
|
|
dependencies:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ServiceDependencyResponse"
|
|
healthObservations:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/HealthObservationResponse"
|
|
deployments:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/DeploymentReferenceResponse"
|
|
backups:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/BackupCoverageResponse"
|
|
UpdateEditorialProfileRequest:
|
|
type: object
|
|
required: [isFavorite, isHidden, isArchived]
|
|
description: Every field besides the three booleans is optional — omit a field to leave it unchanged.
|
|
properties:
|
|
displayName:
|
|
type: [string, "null"]
|
|
summary:
|
|
type: [string, "null"]
|
|
category:
|
|
type: [string, "null"]
|
|
purpose:
|
|
type: [string, "null"]
|
|
ownerId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
projectId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
repositoryUrl:
|
|
type: [string, "null"]
|
|
liveUrl:
|
|
type: [string, "null"]
|
|
backupStatus:
|
|
type: [string, "null"]
|
|
monitoringUrl:
|
|
type: [string, "null"]
|
|
maintenanceNotes:
|
|
type: [string, "null"]
|
|
knownRisks:
|
|
type: [string, "null"]
|
|
updateProcedure:
|
|
type: [string, "null"]
|
|
rollbackProcedure:
|
|
type: [string, "null"]
|
|
tags:
|
|
type: [array, "null"]
|
|
items:
|
|
type: string
|
|
isFavorite:
|
|
type: boolean
|
|
isHidden:
|
|
type: boolean
|
|
isArchived:
|
|
type: boolean
|
|
SetManualOverrideRequest:
|
|
type: object
|
|
required: [fieldName, value]
|
|
properties:
|
|
fieldName:
|
|
type: string
|
|
description: Must be one of the field names GET /infrastructure/overrides/catalog returns for the target's object kind.
|
|
value:
|
|
description: Any JSON scalar, array or object, validated server-side against the field's catalog entry (valueKind, maxStringLength, allowedEnumValues).
|
|
reason:
|
|
type: [string, "null"]
|
|
SetInfrastructureOverrideRequest:
|
|
type: object
|
|
required: [targetKind, targetId, fieldName, value]
|
|
properties:
|
|
targetKind:
|
|
type: string
|
|
enum: [Host, Service, Component, Endpoint, Network, Volume, Database, Deployment, Backup]
|
|
targetId:
|
|
type: string
|
|
format: uuid
|
|
fieldName:
|
|
type: string
|
|
description: Must be one of the field names GET /infrastructure/overrides/catalog?targetKind=... returns for this targetKind.
|
|
value:
|
|
description: Any JSON scalar, array or object, validated server-side against the field's catalog entry (valueKind, maxStringLength, allowedEnumValues).
|
|
reason:
|
|
type: [string, "null"]
|
|
ReviewInfrastructureServiceRequest:
|
|
type: object
|
|
required: [decision]
|
|
properties:
|
|
decision:
|
|
type: string
|
|
enum: [Accept, Reject]
|
|
reason:
|
|
type: [string, "null"]
|
|
ReviewInfrastructureServiceResponse:
|
|
type: object
|
|
required: [serviceId, presence]
|
|
properties:
|
|
serviceId:
|
|
type: string
|
|
format: uuid
|
|
presence:
|
|
type: string
|
|
enum: [Present, Missing]
|
|
InventoryChangeResponse:
|
|
type: object
|
|
required: [id, snapshotId, targetKind, targetId, sourceKey, changeKind, changedFields, recordedAt]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
snapshotId:
|
|
type: string
|
|
format: uuid
|
|
targetKind:
|
|
type: string
|
|
targetId:
|
|
type: string
|
|
format: uuid
|
|
sourceKey:
|
|
type: string
|
|
changeKind:
|
|
type: string
|
|
enum: [Added, Updated, Missing, Unchanged]
|
|
changedFields:
|
|
type: array
|
|
items:
|
|
type: string
|
|
recordedAt:
|
|
type: string
|
|
format: date-time
|
|
InventoryHostRequest:
|
|
type: object
|
|
required: [sourceKey, hostname, logicalCpuCount, totalMemoryBytes]
|
|
properties:
|
|
sourceKey:
|
|
type: string
|
|
hostname:
|
|
type: string
|
|
operatingSystem:
|
|
type: [string, "null"]
|
|
kernelVersion:
|
|
type: [string, "null"]
|
|
architecture:
|
|
type: [string, "null"]
|
|
logicalCpuCount:
|
|
type: integer
|
|
totalMemoryBytes:
|
|
type: integer
|
|
format: int64
|
|
InventoryFactConfidence:
|
|
type: string
|
|
enum: [Observed, Inferred, Declared, Unknown]
|
|
InventoryComponentRequest:
|
|
type: object
|
|
required: [sourceKey, name, confidence]
|
|
properties:
|
|
sourceKey:
|
|
type: string
|
|
name:
|
|
type: string
|
|
kind:
|
|
type: [string, "null"]
|
|
runtimeId:
|
|
type: [string, "null"]
|
|
status:
|
|
type: [string, "null"]
|
|
imageReference:
|
|
type: [string, "null"]
|
|
confidence:
|
|
$ref: "#/components/schemas/InventoryFactConfidence"
|
|
InventoryEndpointRequest:
|
|
type: object
|
|
required: [sourceKey, isPublic, confidence]
|
|
properties:
|
|
sourceKey:
|
|
type: string
|
|
scheme:
|
|
type: [string, "null"]
|
|
host:
|
|
type: [string, "null"]
|
|
publishedPort:
|
|
type: [integer, "null"]
|
|
targetPort:
|
|
type: [integer, "null"]
|
|
url:
|
|
type: [string, "null"]
|
|
isPublic:
|
|
type: boolean
|
|
confidence:
|
|
$ref: "#/components/schemas/InventoryFactConfidence"
|
|
InventoryNetworkRequest:
|
|
type: object
|
|
required: [sourceKey, name, internal, confidence]
|
|
properties:
|
|
sourceKey:
|
|
type: string
|
|
name:
|
|
type: string
|
|
driver:
|
|
type: [string, "null"]
|
|
scope:
|
|
type: [string, "null"]
|
|
internal:
|
|
type: boolean
|
|
confidence:
|
|
$ref: "#/components/schemas/InventoryFactConfidence"
|
|
InventoryVolumeRequest:
|
|
type: object
|
|
required: [sourceKey, name, readOnly, confidence]
|
|
properties:
|
|
sourceKey:
|
|
type: string
|
|
name:
|
|
type: string
|
|
mountPath:
|
|
type: [string, "null"]
|
|
storageClass:
|
|
type: [string, "null"]
|
|
readOnly:
|
|
type: boolean
|
|
sizeBytes:
|
|
type: [integer, "null"]
|
|
format: int64
|
|
confidence:
|
|
$ref: "#/components/schemas/InventoryFactConfidence"
|
|
InventoryDatabaseRequest:
|
|
type: object
|
|
required: [sourceKey, name, hostPortPublished, confidence]
|
|
properties:
|
|
sourceKey:
|
|
type: string
|
|
name:
|
|
type: string
|
|
engine:
|
|
type: [string, "null"]
|
|
version:
|
|
type: [string, "null"]
|
|
host:
|
|
type: [string, "null"]
|
|
port:
|
|
type: [integer, "null"]
|
|
hostPortPublished:
|
|
type: boolean
|
|
confidence:
|
|
$ref: "#/components/schemas/InventoryFactConfidence"
|
|
InventoryDependencyRequest:
|
|
type: object
|
|
required: [sourceKey, dependsOnSourceKey, confidence]
|
|
properties:
|
|
sourceKey:
|
|
type: string
|
|
dependsOnSourceKey:
|
|
type: string
|
|
kind:
|
|
type: [string, "null"]
|
|
confidence:
|
|
$ref: "#/components/schemas/InventoryFactConfidence"
|
|
InventoryHealthRequest:
|
|
type: object
|
|
required: [sourceKey, state, confidence]
|
|
properties:
|
|
sourceKey:
|
|
type: string
|
|
state:
|
|
type: string
|
|
enum: [Healthy, Degraded, Unhealthy, Unknown]
|
|
checkKind:
|
|
type: [string, "null"]
|
|
responseTimeMilliseconds:
|
|
type: [integer, "null"]
|
|
confidence:
|
|
$ref: "#/components/schemas/InventoryFactConfidence"
|
|
InventoryDeploymentRequest:
|
|
type: object
|
|
required: [sourceKey, confidence]
|
|
properties:
|
|
sourceKey:
|
|
type: string
|
|
repositoryUrl:
|
|
type: [string, "null"]
|
|
commit:
|
|
type: [string, "null"]
|
|
imageReference:
|
|
type: [string, "null"]
|
|
imageDigest:
|
|
type: [string, "null"]
|
|
deployedAt:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
confidence:
|
|
$ref: "#/components/schemas/InventoryFactConfidence"
|
|
InventoryBackupRequest:
|
|
type: object
|
|
required: [sourceKey, restoreTested, confidence]
|
|
properties:
|
|
sourceKey:
|
|
type: string
|
|
method:
|
|
type: [string, "null"]
|
|
schedule:
|
|
type: [string, "null"]
|
|
lastSuccessfulAt:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
restoreTested:
|
|
type: boolean
|
|
confidence:
|
|
$ref: "#/components/schemas/InventoryFactConfidence"
|
|
InventoryServiceRequest:
|
|
type: object
|
|
required: [sourceKey, detectedName, health, readOnlyRootFilesystem, noNewPrivileges, privileged, confidence]
|
|
properties:
|
|
sourceKey:
|
|
type: string
|
|
hostSourceKey:
|
|
type: [string, "null"]
|
|
detectedName:
|
|
type: string
|
|
runtimeKind:
|
|
type: [string, "null"]
|
|
runtimeStatus:
|
|
type: [string, "null"]
|
|
health:
|
|
type: string
|
|
enum: [Healthy, Degraded, Unhealthy, Unknown]
|
|
imageReference:
|
|
type: [string, "null"]
|
|
imageDigest:
|
|
type: [string, "null"]
|
|
runtimeUser:
|
|
type: [string, "null"]
|
|
readOnlyRootFilesystem:
|
|
type: boolean
|
|
noNewPrivileges:
|
|
type: boolean
|
|
privileged:
|
|
type: boolean
|
|
confidence:
|
|
$ref: "#/components/schemas/InventoryFactConfidence"
|
|
components:
|
|
type: [array, "null"]
|
|
items:
|
|
$ref: "#/components/schemas/InventoryComponentRequest"
|
|
endpoints:
|
|
type: [array, "null"]
|
|
items:
|
|
$ref: "#/components/schemas/InventoryEndpointRequest"
|
|
networks:
|
|
type: [array, "null"]
|
|
items:
|
|
$ref: "#/components/schemas/InventoryNetworkRequest"
|
|
volumes:
|
|
type: [array, "null"]
|
|
items:
|
|
$ref: "#/components/schemas/InventoryVolumeRequest"
|
|
databases:
|
|
type: [array, "null"]
|
|
items:
|
|
$ref: "#/components/schemas/InventoryDatabaseRequest"
|
|
dependencies:
|
|
type: [array, "null"]
|
|
items:
|
|
$ref: "#/components/schemas/InventoryDependencyRequest"
|
|
healthObservations:
|
|
type: [array, "null"]
|
|
items:
|
|
$ref: "#/components/schemas/InventoryHealthRequest"
|
|
deployments:
|
|
type: [array, "null"]
|
|
items:
|
|
$ref: "#/components/schemas/InventoryDeploymentRequest"
|
|
backups:
|
|
type: [array, "null"]
|
|
items:
|
|
$ref: "#/components/schemas/InventoryBackupRequest"
|
|
SynchronizeInventoryRequest:
|
|
type: object
|
|
required: [sourceKey, sourceHash, capturedAt]
|
|
properties:
|
|
sourceKey:
|
|
type: string
|
|
sourceHash:
|
|
type: string
|
|
capturedAt:
|
|
type: string
|
|
format: date-time
|
|
hosts:
|
|
type: [array, "null"]
|
|
items:
|
|
$ref: "#/components/schemas/InventoryHostRequest"
|
|
services:
|
|
type: [array, "null"]
|
|
items:
|
|
$ref: "#/components/schemas/InventoryServiceRequest"
|
|
InventorySyncResponse:
|
|
type: object
|
|
required: [snapshotId, alreadyImported, addedServices, updatedServices, missingServices, preservedEditorialProfiles, preservedOverrides]
|
|
properties:
|
|
snapshotId:
|
|
type: string
|
|
format: uuid
|
|
alreadyImported:
|
|
type: boolean
|
|
addedServices:
|
|
type: integer
|
|
updatedServices:
|
|
type: integer
|
|
missingServices:
|
|
type: integer
|
|
preservedEditorialProfiles:
|
|
type: integer
|
|
preservedOverrides:
|
|
type: integer
|
|
CuratedServiceProfileRequest:
|
|
type: object
|
|
required: [sourceKey, reviewed, archived]
|
|
properties:
|
|
sourceKey:
|
|
type: string
|
|
displayName:
|
|
type: [string, "null"]
|
|
summary:
|
|
type: [string, "null"]
|
|
category:
|
|
type: [string, "null"]
|
|
purpose:
|
|
type: [string, "null"]
|
|
repositoryUrl:
|
|
type: [string, "null"]
|
|
liveUrl:
|
|
type: [string, "null"]
|
|
tags:
|
|
type: [array, "null"]
|
|
items:
|
|
type: string
|
|
reviewed:
|
|
type: boolean
|
|
archived:
|
|
type: boolean
|
|
ImportCuratedInventoryRequest:
|
|
type: object
|
|
required: [inventory]
|
|
properties:
|
|
inventory:
|
|
$ref: "#/components/schemas/SynchronizeInventoryRequest"
|
|
services:
|
|
type: [array, "null"]
|
|
items:
|
|
$ref: "#/components/schemas/CuratedServiceProfileRequest"
|
|
ImportCuratedInventoryResponse:
|
|
type: object
|
|
required: [inventory, createdEditorialProfiles, confirmedServices, archivedServices]
|
|
properties:
|
|
inventory:
|
|
$ref: "#/components/schemas/InventorySyncResponse"
|
|
createdEditorialProfiles:
|
|
type: integer
|
|
confirmedServices:
|
|
type: integer
|
|
archivedServices:
|
|
type: integer
|
|
ActivityItemResponse:
|
|
type: object
|
|
required: [id, actorKind, actorId, source, action, targetType, targetId, summary, occurredAt]
|
|
properties:
|
|
id:
|
|
type: integer
|
|
format: int64
|
|
actorKind:
|
|
type: string
|
|
enum: [System, User, ServiceAccount]
|
|
actorId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
source:
|
|
type: string
|
|
action:
|
|
type: string
|
|
targetType:
|
|
type: [string, "null"]
|
|
targetId:
|
|
type: [string, "null"]
|
|
summary:
|
|
type: string
|
|
occurredAt:
|
|
type: string
|
|
format: date-time
|
|
ActivityListResponse:
|
|
type: object
|
|
required: [items]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ActivityItemResponse"
|
|
WorkspaceMemberSummary:
|
|
type: object
|
|
required: [id, displayName, email]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
displayName:
|
|
type: string
|
|
email:
|
|
type: string
|
|
WorkspaceMemberListResponse:
|
|
type: object
|
|
required: [items]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/WorkspaceMemberSummary"
|
|
PreviewMarkdownRequest:
|
|
type: object
|
|
required: [markdown]
|
|
properties:
|
|
markdown:
|
|
type: string
|
|
PreviewMarkdownResponse:
|
|
type: object
|
|
required: [renderedHtml]
|
|
properties:
|
|
renderedHtml:
|
|
type: string
|
|
description: Sanitized HTML — safe to render directly, matching the same pipeline a published page revision uses.
|
|
StagingCleanupRunSummary:
|
|
type: object
|
|
required: [sessionsExpired, sessionsClaimed, sessionsLostRace, attachmentReferencesRemoved, blobsRemoved, warnings]
|
|
properties:
|
|
sessionsExpired:
|
|
type: integer
|
|
sessionsClaimed:
|
|
type: integer
|
|
sessionsLostRace:
|
|
type: integer
|
|
description: Expired sessions this run observed but a concurrent run claimed first — never lost, just processed elsewhere.
|
|
attachmentReferencesRemoved:
|
|
type: integer
|
|
blobsRemoved:
|
|
type: integer
|
|
warnings:
|
|
type: array
|
|
items:
|
|
type: string
|
|
StagingCleanupStatusResponse:
|
|
type: object
|
|
required: [activeSessionCount, expiredPendingCleanupCount, runInProgress, lastRunStartedAt, lastRunCompletedAt, lastRunTrigger, lastResult]
|
|
properties:
|
|
activeSessionCount:
|
|
type: integer
|
|
expiredPendingCleanupCount:
|
|
type: integer
|
|
runInProgress:
|
|
type: boolean
|
|
lastRunStartedAt:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
lastRunCompletedAt:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
lastRunTrigger:
|
|
type: [string, "null"]
|
|
enum: [scheduled, manual, null]
|
|
lastResult:
|
|
type: [object, "null"]
|
|
allOf:
|
|
- $ref: "#/components/schemas/StagingCleanupRunSummary"
|
|
RunStagingCleanupRequest:
|
|
type: object
|
|
required: [dryRun]
|
|
properties:
|
|
dryRun:
|
|
type: boolean
|
|
RunStagingCleanupResponse:
|
|
type: object
|
|
required: [dryRun, result]
|
|
properties:
|
|
dryRun:
|
|
type: boolean
|
|
result:
|
|
$ref: "#/components/schemas/StagingCleanupRunSummary"
|
|
PreflightImportRequest:
|
|
type: object
|
|
required: [sourceRelativePath, idempotencyKey]
|
|
properties:
|
|
sourceRelativePath:
|
|
type: string
|
|
description: Resolved beneath the operator-mounted KnowledgeHub:Paths:Imports directory — never an arbitrary filesystem path.
|
|
idempotencyKey:
|
|
type: string
|
|
ImportPlanItemResponse:
|
|
type: object
|
|
required: [sourceIdentity, kind, proposedTitle, proposedParentSourceIdentity, conflict, conflictDetail]
|
|
properties:
|
|
sourceIdentity:
|
|
type: string
|
|
kind:
|
|
type: string
|
|
enum: [Markdown, SqliteRow, Asset, Unsupported]
|
|
proposedTitle:
|
|
type: string
|
|
proposedParentSourceIdentity:
|
|
type: [string, "null"]
|
|
conflict:
|
|
type: string
|
|
enum: [NoConflict, ExactRerunDuplicate, SamePathDifferentContent, ExistingUserPage, AmbiguousLink, Unsupported]
|
|
conflictDetail:
|
|
type: [string, "null"]
|
|
ImportPreflightResponse:
|
|
type: object
|
|
required: [status, jobId, adapterName, discoveredCount, plannedCount, items, warnings, blockers]
|
|
properties:
|
|
status:
|
|
type: string
|
|
enum: [Success, NoApplicableAdapter, AlreadyPreflighted]
|
|
jobId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
adapterName:
|
|
type: [string, "null"]
|
|
discoveredCount:
|
|
type: integer
|
|
plannedCount:
|
|
type: integer
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ImportPlanItemResponse"
|
|
warnings:
|
|
type: array
|
|
items:
|
|
type: string
|
|
blockers:
|
|
type: array
|
|
items:
|
|
type: string
|
|
ExecuteImportResponse:
|
|
type: object
|
|
required: [status, imported, skipped, failed, sourceUnchangedAfterExecution, diagnostics]
|
|
properties:
|
|
status:
|
|
type: string
|
|
enum: [Success, JobNotFound, NotPlanReady, SourceChanged, PlanChanged]
|
|
imported:
|
|
type: integer
|
|
skipped:
|
|
type: integer
|
|
failed:
|
|
type: integer
|
|
sourceUnchangedAfterExecution:
|
|
type: boolean
|
|
description: A re-fingerprint comparison proving the source was never mutated during import (M4-A08).
|
|
diagnostics:
|
|
type: array
|
|
items:
|
|
type: string
|
|
RollbackImportResponse:
|
|
type: object
|
|
required: [status, removedPages, preservedPages, removedAttachments]
|
|
properties:
|
|
status:
|
|
type: string
|
|
enum: [Success, JobNotFound, NothingToRollBack]
|
|
removedPages:
|
|
type: integer
|
|
preservedPages:
|
|
type: integer
|
|
description: Pages this import created but a user has since edited — never deleted by rollback.
|
|
removedAttachments:
|
|
type: integer
|
|
ImportJobSummaryResponse:
|
|
type: object
|
|
required: [id, adapterName, status, discoveredCount, plannedCount, importedCount, skippedCount, failedCount, createdAt, startedAt, completedAt]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
adapterName:
|
|
type: string
|
|
status:
|
|
type: string
|
|
enum: [Discovered, PlanReady, Executing, Completed, Failed, RolledBack]
|
|
discoveredCount:
|
|
type: integer
|
|
plannedCount:
|
|
type: integer
|
|
importedCount:
|
|
type: integer
|
|
skippedCount:
|
|
type: integer
|
|
failedCount:
|
|
type: integer
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
startedAt:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
completedAt:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
SourceBatchPurgeRequest:
|
|
type: object
|
|
required: [batchId, source, pages]
|
|
properties:
|
|
batchId:
|
|
type: string
|
|
minLength: 1
|
|
source:
|
|
type: string
|
|
pattern: "^[a-z0-9-]{1,64}$"
|
|
pages:
|
|
type: array
|
|
minItems: 1
|
|
items:
|
|
$ref: "#/components/schemas/SourceBatchPurgePageEvidence"
|
|
SourceBatchPurgePageEvidence:
|
|
type: object
|
|
required: [pageId, contentHash]
|
|
properties:
|
|
pageId:
|
|
type: string
|
|
format: uuid
|
|
contentHash:
|
|
type: string
|
|
pattern: "^[a-fA-F0-9]{64}$"
|
|
ExecuteSourceBatchPurgeRequest:
|
|
allOf:
|
|
- $ref: "#/components/schemas/SourceBatchPurgeRequest"
|
|
- type: object
|
|
required: [confirmationHash]
|
|
properties:
|
|
confirmationHash:
|
|
type: string
|
|
pattern: "^[a-f0-9]{64}$"
|
|
SourceBatchPurgeDryRunResponse:
|
|
type: object
|
|
required: [safe, confirmationHash, pages, descendantPages, revisions, drafts, links, attachmentUsages, attachmentReferences, orphanedBlobs, sharedBlobs, importJobs, importItems, provenanceRecords, sourceTagAssignments, searchIndexRecords, generatedSourceDerivedPages, retainedPages, blockers]
|
|
properties:
|
|
safe:
|
|
type: boolean
|
|
confirmationHash:
|
|
type: string
|
|
pages:
|
|
type: integer
|
|
descendantPages:
|
|
type: integer
|
|
revisions:
|
|
type: integer
|
|
drafts:
|
|
type: integer
|
|
links:
|
|
type: integer
|
|
attachmentUsages:
|
|
type: integer
|
|
attachmentReferences:
|
|
type: integer
|
|
orphanedBlobs:
|
|
type: integer
|
|
sharedBlobs:
|
|
type: integer
|
|
importJobs:
|
|
type: integer
|
|
importItems:
|
|
type: integer
|
|
provenanceRecords:
|
|
type: integer
|
|
sourceTagAssignments:
|
|
type: integer
|
|
searchIndexRecords:
|
|
type: integer
|
|
generatedSourceDerivedPages:
|
|
type: integer
|
|
retainedPages:
|
|
type: integer
|
|
blockers:
|
|
type: array
|
|
items:
|
|
type: string
|
|
SourceBatchPurgeResponse:
|
|
type: object
|
|
required: [status, dryRun, removedPages, removedRevisions, removedDrafts, removedLinks, removedAttachmentUsages, removedAttachmentReferences, removedImportJobs, removedImportItems, removedProvenanceRecords, removedBlobs]
|
|
properties:
|
|
status:
|
|
type: string
|
|
enum: [purged]
|
|
dryRun:
|
|
$ref: "#/components/schemas/SourceBatchPurgeDryRunResponse"
|
|
removedPages:
|
|
type: integer
|
|
removedRevisions:
|
|
type: integer
|
|
removedDrafts:
|
|
type: integer
|
|
removedLinks:
|
|
type: integer
|
|
removedAttachmentUsages:
|
|
type: integer
|
|
removedAttachmentReferences:
|
|
type: integer
|
|
removedImportJobs:
|
|
type: integer
|
|
removedImportItems:
|
|
type: integer
|
|
removedProvenanceRecords:
|
|
type: integer
|
|
removedBlobs:
|
|
type: integer
|
|
SearchResultItem:
|
|
type: object
|
|
required: [id, parentPageId, title, slug, documentType, documentStatus, tags, updatedAt, snippet]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
parentPageId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
title:
|
|
type: string
|
|
slug:
|
|
type: string
|
|
documentType:
|
|
$ref: "#/components/schemas/DocumentType"
|
|
documentStatus:
|
|
$ref: "#/components/schemas/DocumentStatus"
|
|
tags:
|
|
type: array
|
|
items:
|
|
type: string
|
|
updatedAt:
|
|
type: string
|
|
format: date-time
|
|
snippet:
|
|
type: string
|
|
description: >-
|
|
May contain the safe server-generated highlight markers `**` around
|
|
matched terms. Never raw HTML — clients must escape this text before
|
|
rendering and only then interpret the `**` markers.
|
|
SearchResponse:
|
|
type: object
|
|
required: [items, nextCursor]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/SearchResultItem"
|
|
nextCursor:
|
|
type: [string, "null"]
|
|
UnifiedSearchResponse:
|
|
type: object
|
|
required: [items, counts, nextCursor]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/UnifiedSearchResultItem"
|
|
counts:
|
|
$ref: "#/components/schemas/UnifiedSearchCounts"
|
|
nextCursor:
|
|
type: [string, "null"]
|
|
UnifiedSearchCounts:
|
|
type: object
|
|
required: [knowledge, projects, services, runbooks, decisions, total]
|
|
properties:
|
|
knowledge: { type: integer, minimum: 0 }
|
|
projects: { type: integer, minimum: 0 }
|
|
services: { type: integer, minimum: 0 }
|
|
runbooks: { type: integer, minimum: 0 }
|
|
decisions: { type: integer, minimum: 0 }
|
|
total: { type: integer, minimum: 0 }
|
|
UnifiedSearchResultItem:
|
|
type: object
|
|
required: [kind, id, title, summary, snippet, updatedAt, href]
|
|
properties:
|
|
kind:
|
|
type: string
|
|
enum: [knowledge, projects, services, runbooks, decisions]
|
|
id: { type: string, format: uuid }
|
|
title: { type: string }
|
|
summary: { type: string }
|
|
snippet: { type: string }
|
|
updatedAt: { type: string, format: date-time }
|
|
href: { type: string, description: Same-origin relative route to the result. }
|
|
RagPublicationManifestItem:
|
|
type: object
|
|
description: >-
|
|
docs/32-rag-publication-convention.md. `markdown` and `checksum` are always
|
|
`null` when `retired` is `true`.
|
|
required:
|
|
- contentId
|
|
- documentType
|
|
- documentStatus
|
|
- title
|
|
- slug
|
|
- canonicalUrl
|
|
- markdown
|
|
- revision
|
|
- checksum
|
|
- locale
|
|
- space
|
|
- updatedAt
|
|
- publishedAt
|
|
- retired
|
|
- retiredReason
|
|
properties:
|
|
contentId:
|
|
type: string
|
|
format: uuid
|
|
documentType:
|
|
$ref: "#/components/schemas/DocumentType"
|
|
documentStatus:
|
|
$ref: "#/components/schemas/DocumentStatus"
|
|
title:
|
|
type: string
|
|
slug:
|
|
type: string
|
|
canonicalUrl:
|
|
type: string
|
|
markdown:
|
|
type: [string, "null"]
|
|
revision:
|
|
type: integer
|
|
format: int64
|
|
checksum:
|
|
type: [string, "null"]
|
|
description: Lowercase-hex SHA-256 of `markdown` (Page.ContentHash). Null when retired.
|
|
locale:
|
|
type: string
|
|
description: >-
|
|
The page's own `locale:` tag, or the configured
|
|
`KnowledgeHub:PrimaryLocale` when it declares none. Never silently `en`.
|
|
space:
|
|
type: [string, "null"]
|
|
description: The page's own `rag:space:` tag suffix, if any.
|
|
updatedAt:
|
|
type: string
|
|
format: date-time
|
|
publishedAt:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
retired:
|
|
type: boolean
|
|
retiredReason:
|
|
type: [string, "null"]
|
|
enum: [null, trashed, draft, needs-review, deprecated, archived]
|
|
RagPublicationManifestResponse:
|
|
type: object
|
|
required: [items, nextCursor]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/RagPublicationManifestItem"
|
|
nextCursor:
|
|
type: [string, "null"]
|
|
ProjectStatus:
|
|
type: string
|
|
description: docs/15-project-hub.md lifecycle diagram. Archived is terminal in v1 (ADR-014 decision 6).
|
|
enum: [Concept, Planned, Active, Blocked, Maintenance, Paused, Completed, Archived]
|
|
ProjectPriority:
|
|
type: string
|
|
enum: [Low, Medium, High, Critical]
|
|
ProjectDocumentRole:
|
|
type: string
|
|
enum:
|
|
- Overview
|
|
- CurrentState
|
|
- Architecture
|
|
- Deployment
|
|
- Infrastructure
|
|
- Security
|
|
- Testing
|
|
- Troubleshooting
|
|
- Release
|
|
- UserDocumentation
|
|
- Other
|
|
ProjectUpdateCategory:
|
|
type: string
|
|
enum: [Progress, Feature, Fix, Deployment, Validation, Release, Decision, Risk, Blocker, Documentation, Maintenance]
|
|
ProjectUpdateVerificationStatus:
|
|
type: string
|
|
description: The UI must distinguish reported claims from validated evidence (docs/15-project-hub.md).
|
|
enum: [Reported, Verified, Superseded]
|
|
ProjectMilestoneState:
|
|
type: string
|
|
enum: [Planned, InProgress, Completed, Cancelled]
|
|
DecisionStatus:
|
|
type: string
|
|
enum: [Proposed, Accepted, Rejected, Superseded, Withdrawn]
|
|
RunbookRiskLevel:
|
|
type: string
|
|
enum: [Low, Medium, High, Critical]
|
|
RunbookStatus:
|
|
type: string
|
|
enum: [Draft, Active, Deprecated]
|
|
ProjectSummary:
|
|
type: object
|
|
required: [id, name, slug, summary, status, priority, ownerId, repositoryUrl, liveUrl, updatedAt, iconAttachmentId]
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
name: { type: string }
|
|
slug: { type: string }
|
|
summary: { type: string }
|
|
status: { $ref: "#/components/schemas/ProjectStatus" }
|
|
priority: { $ref: "#/components/schemas/ProjectPriority" }
|
|
ownerId: { type: string, format: uuid }
|
|
repositoryUrl: { type: [string, "null"], format: uri }
|
|
liveUrl: { type: [string, "null"], format: uri }
|
|
updatedAt: { type: string, format: date-time }
|
|
iconAttachmentId: { type: [string, "null"], format: uuid, description: "Same-origin attachment content is served at /api/v1/attachments/{iconAttachmentId}/content." }
|
|
ProjectListResponse:
|
|
type: object
|
|
required: [items, nextCursor, totalCount]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ProjectSummary"
|
|
nextCursor:
|
|
type: [string, "null"]
|
|
totalCount:
|
|
type: integer
|
|
minimum: 0
|
|
description: >-
|
|
How many projects match the current filters in total (OUX-4), counted
|
|
server-side before cursor pagination. Not a substitute for
|
|
`/projects/totals`, which answers the unfiltered dashboard question.
|
|
ProjectCatalogTotalsResponse:
|
|
type: object
|
|
required: [totalCount, activeCount]
|
|
properties:
|
|
totalCount: { type: integer, minimum: 0 }
|
|
activeCount: { type: integer, minimum: 0 }
|
|
ProjectDocumentSummary:
|
|
type: object
|
|
required: [id, pageId, pageTitle, pageSlug, pageDocumentType, role]
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
pageId: { type: string, format: uuid }
|
|
pageTitle: { type: string }
|
|
pageSlug: { type: string }
|
|
pageDocumentType: { $ref: "#/components/schemas/DocumentType" }
|
|
role: { $ref: "#/components/schemas/ProjectDocumentRole" }
|
|
ProjectUpdateSummary:
|
|
type: object
|
|
required: [id, category, title, summaryMarkdown, verificationStatus, actorId, occurredAt]
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
category: { $ref: "#/components/schemas/ProjectUpdateCategory" }
|
|
title: { type: string }
|
|
summaryMarkdown: { type: string }
|
|
verificationStatus: { $ref: "#/components/schemas/ProjectUpdateVerificationStatus" }
|
|
actorId: { type: string, format: uuid }
|
|
occurredAt: { type: string, format: date-time }
|
|
commitSha: { type: [string, "null"] }
|
|
releaseTag: { type: [string, "null"] }
|
|
issueIdentifier: { type: [string, "null"] }
|
|
url: { type: [string, "null"] }
|
|
pageId: { type: [string, "null"], format: uuid }
|
|
decisionRecordId: { type: [string, "null"], format: uuid }
|
|
milestoneId: { type: [string, "null"], format: uuid }
|
|
attachmentReferenceId: { type: [string, "null"], format: uuid }
|
|
supersedesUpdateId: { type: [string, "null"], format: uuid }
|
|
ProjectUpdatesResponse:
|
|
type: object
|
|
required: [items, nextCursor]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ProjectUpdateSummary"
|
|
nextCursor:
|
|
type: [string, "null"]
|
|
ProjectMilestoneSummary:
|
|
type: object
|
|
required: [id, name, state, summary, successCriteria, eTag]
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
name: { type: string }
|
|
state: { $ref: "#/components/schemas/ProjectMilestoneState" }
|
|
targetDate: { type: [string, "null"], format: date }
|
|
completionDate: { type: [string, "null"], format: date }
|
|
summary: { type: string }
|
|
successCriteria: { type: string }
|
|
eTag:
|
|
type: string
|
|
description: Also present as the response ETag header.
|
|
ProjectDecisionSummary:
|
|
type: object
|
|
required: [id, decisionNumber, status, summary]
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
decisionNumber: { type: integer }
|
|
status: { $ref: "#/components/schemas/DecisionStatus" }
|
|
summary: { type: string }
|
|
ProjectDetail:
|
|
type: object
|
|
required:
|
|
- id
|
|
- name
|
|
- slug
|
|
- summary
|
|
- status
|
|
- priority
|
|
- currentStateMarkdown
|
|
- ownerId
|
|
- technologies
|
|
- documents
|
|
- recentUpdates
|
|
- milestones
|
|
- decisions
|
|
- createdAt
|
|
- updatedAt
|
|
- eTag
|
|
- iconAttachmentId
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
name: { type: string }
|
|
slug: { type: string }
|
|
summary: { type: string }
|
|
status: { $ref: "#/components/schemas/ProjectStatus" }
|
|
priority: { $ref: "#/components/schemas/ProjectPriority" }
|
|
currentStateMarkdown: { type: string }
|
|
ownerId: { type: string, format: uuid }
|
|
startDate: { type: [string, "null"], format: date }
|
|
targetDate: { type: [string, "null"], format: date }
|
|
completionDate: { type: [string, "null"], format: date }
|
|
repositoryUrl: { type: [string, "null"] }
|
|
liveUrl: { type: [string, "null"] }
|
|
documentationUrl: { type: [string, "null"] }
|
|
issueTrackerUrl: { type: [string, "null"] }
|
|
technologies:
|
|
type: array
|
|
items: { type: string }
|
|
documents:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ProjectDocumentSummary"
|
|
recentUpdates:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ProjectUpdateSummary"
|
|
milestones:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ProjectMilestoneSummary"
|
|
decisions:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ProjectDecisionSummary"
|
|
createdAt: { type: string, format: date-time }
|
|
updatedAt: { type: string, format: date-time }
|
|
eTag:
|
|
type: string
|
|
description: Also present as the response ETag header; included in the body so clients need not parse headers.
|
|
iconAttachmentId: { type: [string, "null"], format: uuid }
|
|
ProjectIconResponse:
|
|
type: object
|
|
required: [iconAttachmentId, eTag]
|
|
properties:
|
|
iconAttachmentId: { type: string, format: uuid }
|
|
eTag: { type: string, description: Current project ETag after the icon change. }
|
|
CreateProjectRequest:
|
|
type: object
|
|
required: [name, summary, priority, currentStateMarkdown, ownerId]
|
|
properties:
|
|
name: { type: string, minLength: 1, maxLength: 200 }
|
|
summary: { type: string }
|
|
priority: { $ref: "#/components/schemas/ProjectPriority" }
|
|
currentStateMarkdown: { type: string }
|
|
ownerId: { type: string, format: uuid }
|
|
UpdateProjectRequest:
|
|
type: object
|
|
required: [name, summary, priority, currentStateMarkdown, ownerId]
|
|
properties:
|
|
name: { type: string, minLength: 1, maxLength: 200 }
|
|
summary: { type: string }
|
|
priority: { $ref: "#/components/schemas/ProjectPriority" }
|
|
currentStateMarkdown: { type: string }
|
|
ownerId: { type: string, format: uuid }
|
|
startDate: { type: [string, "null"], format: date }
|
|
targetDate: { type: [string, "null"], format: date }
|
|
completionDate: { type: [string, "null"], format: date }
|
|
repositoryUrl: { type: [string, "null"] }
|
|
liveUrl: { type: [string, "null"] }
|
|
documentationUrl: { type: [string, "null"] }
|
|
issueTrackerUrl: { type: [string, "null"] }
|
|
technologies:
|
|
type: [array, "null"]
|
|
items: { type: string }
|
|
LinkProjectDocumentRequest:
|
|
type: object
|
|
required: [pageId, role]
|
|
properties:
|
|
pageId: { type: string, format: uuid }
|
|
role: { $ref: "#/components/schemas/ProjectDocumentRole" }
|
|
CreateProjectUpdateRequest:
|
|
type: object
|
|
required: [category, title, summaryMarkdown, verificationStatus]
|
|
properties:
|
|
category: { $ref: "#/components/schemas/ProjectUpdateCategory" }
|
|
title: { type: string, maxLength: 200 }
|
|
summaryMarkdown: { type: string }
|
|
verificationStatus: { $ref: "#/components/schemas/ProjectUpdateVerificationStatus" }
|
|
occurredAt: { type: [string, "null"], format: date-time }
|
|
commitSha: { type: [string, "null"] }
|
|
releaseTag: { type: [string, "null"] }
|
|
issueIdentifier: { type: [string, "null"] }
|
|
url: { type: [string, "null"] }
|
|
pageId: { type: [string, "null"], format: uuid }
|
|
decisionRecordId: { type: [string, "null"], format: uuid }
|
|
milestoneId: { type: [string, "null"], format: uuid }
|
|
attachmentReferenceId: { type: [string, "null"], format: uuid }
|
|
supersedesUpdateId: { type: [string, "null"], format: uuid }
|
|
CreateProjectMilestoneRequest:
|
|
type: object
|
|
required: [name, summary, successCriteria]
|
|
properties:
|
|
name: { type: string, minLength: 1 }
|
|
targetDate: { type: [string, "null"], format: date }
|
|
summary: { type: string }
|
|
successCriteria: { type: string }
|
|
UpdateProjectMilestoneRequest:
|
|
type: object
|
|
required: [name, summary, successCriteria, state]
|
|
properties:
|
|
name: { type: string, minLength: 1 }
|
|
targetDate: { type: [string, "null"], format: date }
|
|
summary: { type: string }
|
|
successCriteria: { type: string }
|
|
state: { $ref: "#/components/schemas/ProjectMilestoneState" }
|
|
DecisionLineageEntry:
|
|
type: object
|
|
required: [id, decisionNumber, status, summary]
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
decisionNumber: { type: integer }
|
|
status: { $ref: "#/components/schemas/DecisionStatus" }
|
|
summary: { type: string }
|
|
DecisionDetail:
|
|
type: object
|
|
required:
|
|
- id
|
|
- workspaceId
|
|
- pageId
|
|
- decisionNumber
|
|
- status
|
|
- decisionDate
|
|
- title
|
|
- markdown
|
|
- renderedHtml
|
|
- summary
|
|
- createdAt
|
|
- updatedAt
|
|
- eTag
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
workspaceId: { type: string, format: uuid }
|
|
projectId: { type: [string, "null"], format: uuid }
|
|
pageId: { type: string, format: uuid }
|
|
decisionNumber: { type: integer }
|
|
status: { $ref: "#/components/schemas/DecisionStatus" }
|
|
decisionDate: { type: string, format: date }
|
|
title: { type: string }
|
|
markdown: { type: string }
|
|
renderedHtml:
|
|
type: string
|
|
description: Server-sanitized HTML — the same rendering pipeline as an ordinary Page.
|
|
summary: { type: string }
|
|
supersedes:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/DecisionLineageEntry"
|
|
- type: "null"
|
|
supersededBy:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/DecisionLineageEntry"
|
|
- type: "null"
|
|
createdAt: { type: string, format: date-time }
|
|
updatedAt: { type: string, format: date-time }
|
|
eTag: { type: string }
|
|
DecisionSummary:
|
|
type: object
|
|
required: [id, decisionNumber, status, title, decisionDate, summary, updatedAt]
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
projectId: { type: [string, "null"], format: uuid }
|
|
decisionNumber: { type: integer }
|
|
status: { $ref: "#/components/schemas/DecisionStatus" }
|
|
title: { type: string }
|
|
decisionDate: { type: string, format: date }
|
|
summary: { type: string }
|
|
updatedAt: { type: string, format: date-time }
|
|
DecisionListResponse:
|
|
type: object
|
|
required: [items, nextCursor]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/DecisionSummary"
|
|
nextCursor:
|
|
type: [string, "null"]
|
|
CreateDecisionRequest:
|
|
type: object
|
|
required: [title, summary]
|
|
properties:
|
|
parentPageId: { type: [string, "null"], format: uuid }
|
|
projectId: { type: [string, "null"], format: uuid }
|
|
title: { type: string, minLength: 1 }
|
|
markdown: { type: [string, "null"] }
|
|
summary: { type: string }
|
|
supersedesDecisionId: { type: [string, "null"], format: uuid }
|
|
RunbookDetail:
|
|
type: object
|
|
required:
|
|
- id
|
|
- pageId
|
|
- owner
|
|
- riskLevel
|
|
- status
|
|
- requiresMaintenanceWindow
|
|
- isStale
|
|
- title
|
|
- markdown
|
|
- renderedHtml
|
|
- currentRevisionNumber
|
|
- createdAt
|
|
- updatedAt
|
|
- eTag
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
pageId: { type: string, format: uuid }
|
|
owner: { type: string, format: uuid }
|
|
riskLevel: { $ref: "#/components/schemas/RunbookRiskLevel" }
|
|
status: { $ref: "#/components/schemas/RunbookStatus" }
|
|
requiresMaintenanceWindow: { type: boolean }
|
|
estimatedDurationMinutes: { type: [integer, "null"] }
|
|
reviewDueAt: { type: [string, "null"], format: date-time }
|
|
lastValidatedAt: { type: [string, "null"], format: date-time }
|
|
validatedBy: { type: [string, "null"], format: uuid }
|
|
isStale:
|
|
type: boolean
|
|
description: True whenever the linked page has a newer revision than the last validation, or was never validated.
|
|
title: { type: string }
|
|
markdown: { type: string }
|
|
renderedHtml:
|
|
type: string
|
|
description: Server-sanitized HTML — the same rendering pipeline as an ordinary Page. Never executed.
|
|
currentRevisionNumber: { type: integer, format: int64 }
|
|
createdAt: { type: string, format: date-time }
|
|
updatedAt: { type: string, format: date-time }
|
|
eTag: { type: string }
|
|
RunbookSummary:
|
|
type: object
|
|
required: [id, pageId, title, riskLevel, status, isStale, updatedAt]
|
|
properties:
|
|
id: { type: string, format: uuid }
|
|
pageId: { type: string, format: uuid }
|
|
title: { type: string }
|
|
riskLevel: { $ref: "#/components/schemas/RunbookRiskLevel" }
|
|
status: { $ref: "#/components/schemas/RunbookStatus" }
|
|
isStale: { type: boolean }
|
|
lastValidatedAt: { type: [string, "null"], format: date-time }
|
|
updatedAt: { type: string, format: date-time }
|
|
RunbookListResponse:
|
|
type: object
|
|
required: [items, nextCursor]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/RunbookSummary"
|
|
nextCursor:
|
|
type: [string, "null"]
|
|
CreateRunbookRequest:
|
|
type: object
|
|
required: [title, riskLevel]
|
|
properties:
|
|
parentPageId: { type: [string, "null"], format: uuid }
|
|
title: { type: string, minLength: 1 }
|
|
markdown: { type: [string, "null"] }
|
|
templateKey: { type: [string, "null"] }
|
|
riskLevel: { $ref: "#/components/schemas/RunbookRiskLevel" }
|
|
requiresMaintenanceWindow: { type: boolean }
|
|
estimatedDurationMinutes: { type: [integer, "null"] }
|
|
UpdateRunbookProfileRequest:
|
|
type: object
|
|
required: [owner, riskLevel, status, requiresMaintenanceWindow]
|
|
properties:
|
|
owner: { type: string, format: uuid }
|
|
riskLevel: { $ref: "#/components/schemas/RunbookRiskLevel" }
|
|
status: { $ref: "#/components/schemas/RunbookStatus" }
|
|
requiresMaintenanceWindow: { type: boolean }
|
|
estimatedDurationMinutes: { type: [integer, "null"] }
|
|
reviewDueAt: { type: [string, "null"], format: date-time }
|
|
CreateServiceAccountRequest:
|
|
type: object
|
|
required: [name, description, scopes]
|
|
properties:
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
description:
|
|
type: string
|
|
maxLength: 1000
|
|
scopes:
|
|
type: array
|
|
minItems: 1
|
|
uniqueItems: true
|
|
items:
|
|
type: string
|
|
description: "One of the known scope constants, e.g. pages:read, pages:write, projects:read, runbooks:write (see docs/09-api-contract.md)."
|
|
IssueServiceAccountTokenRequest:
|
|
type: object
|
|
required: [scopes]
|
|
properties:
|
|
scopes:
|
|
type: array
|
|
minItems: 1
|
|
uniqueItems: true
|
|
items:
|
|
type: string
|
|
CreatedServiceAccountToken:
|
|
type: object
|
|
description: The raw token is only ever present in this one response — it is never persisted in the clear and is not retrievable again.
|
|
required: [serviceAccountId, tokenId, rawToken]
|
|
properties:
|
|
serviceAccountId:
|
|
type: string
|
|
format: uuid
|
|
tokenId:
|
|
type: string
|
|
format: uuid
|
|
rawToken:
|
|
type: string
|
|
description: "Format ikh_<publicId>_<secret>. Present exactly once."
|
|
ServiceAccountTokenSummary:
|
|
type: object
|
|
required: [id, publicId, scopes, createdAt]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
publicId:
|
|
type: string
|
|
description: Safe, non-secret identifier — never the raw secret.
|
|
scopes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
lastUsedAt:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
expiresAt:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
revokedAt:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
ServiceAccountSummary:
|
|
type: object
|
|
required: [id, name, description, enabled, createdAt, tokens]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
name:
|
|
type: string
|
|
description:
|
|
type: string
|
|
enabled:
|
|
type: boolean
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
tokens:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ServiceAccountTokenSummary"
|
|
ServiceAccountListResponse:
|
|
type: object
|
|
required: [items]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ServiceAccountSummary"
|
|
CitationResponse:
|
|
type: object
|
|
description: A stable, citable pointer to a document or decision included in a project context bundle.
|
|
required: [kind, id, title, canonicalPath, updatedAt]
|
|
properties:
|
|
kind:
|
|
type: string
|
|
enum: [Page, DecisionRecord, RunbookProfile]
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
revision:
|
|
type: [integer, "null"]
|
|
format: int64
|
|
title:
|
|
type: string
|
|
canonicalPath:
|
|
type: string
|
|
updatedAt:
|
|
type: string
|
|
format: date-time
|
|
ProjectContextProjectSummary:
|
|
type: object
|
|
required: [id, name, status, summary, currentStateMarkdown, updatedAt]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
name:
|
|
type: string
|
|
status:
|
|
type: string
|
|
summary:
|
|
type: string
|
|
currentStateMarkdown:
|
|
type: string
|
|
updatedAt:
|
|
type: string
|
|
format: date-time
|
|
ProjectContextTruncation:
|
|
type: object
|
|
required: [truncated, omittedCounts]
|
|
properties:
|
|
truncated:
|
|
type: boolean
|
|
omittedCounts:
|
|
type: object
|
|
additionalProperties:
|
|
type: integer
|
|
description: "Counts of items omitted per collection (e.g. updates, decisions), keyed by collection name."
|
|
ProjectContextResponse:
|
|
type: object
|
|
description: "A bounded, permission-filtered bundle of the current project state, suitable for a single-call context fetch (docs/09-api-contract.md 'Project context', M6-A04). Unlike the unbounded project overview, this response always reports what was truncated rather than growing unbounded."
|
|
required: [project, recentUpdates, activeMilestones, decisions, runbooks, documents, citations, truncation]
|
|
properties:
|
|
project:
|
|
$ref: "#/components/schemas/ProjectContextProjectSummary"
|
|
recentUpdates:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ProjectUpdateSummary"
|
|
activeMilestones:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ProjectMilestoneSummary"
|
|
decisions:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ProjectDecisionSummary"
|
|
runbooks:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ProjectDocumentSummary"
|
|
documents:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ProjectDocumentSummary"
|
|
citations:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/CitationResponse"
|
|
truncation:
|
|
$ref: "#/components/schemas/ProjectContextTruncation"
|
|
BackupSummaryResponse:
|
|
type: object
|
|
description: Never includes a filesystem path, connection string or other private detail.
|
|
required: [backupId, createdAt, totalSizeBytes, tableCount, totalRowCount, attachmentCount, schemaVersion, productVersion]
|
|
properties:
|
|
backupId:
|
|
type: string
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
totalSizeBytes:
|
|
type: integer
|
|
format: int64
|
|
tableCount:
|
|
type: integer
|
|
totalRowCount:
|
|
type: integer
|
|
format: int64
|
|
attachmentCount:
|
|
type: integer
|
|
schemaVersion:
|
|
type: string
|
|
productVersion:
|
|
type: string
|
|
BackupListResponse:
|
|
type: object
|
|
required: [items]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/BackupSummaryResponse"
|
|
ProblemDetails:
|
|
type: object
|
|
required: [type, title, status, correlationId]
|
|
properties:
|
|
type:
|
|
type: string
|
|
format: uri-reference
|
|
title:
|
|
type: string
|
|
status:
|
|
type: integer
|
|
detail:
|
|
type: string
|
|
instance:
|
|
type: string
|
|
format: uri-reference
|
|
correlationId:
|
|
type: string
|
|
errors:
|
|
type: object
|
|
additionalProperties:
|
|
type: array
|
|
items:
|
|
type: string
|
|
SetupStatus:
|
|
type: object
|
|
required: [setupRequired]
|
|
properties:
|
|
setupRequired:
|
|
type: boolean
|
|
CompleteSetupRequest:
|
|
type: object
|
|
required: [workspaceName, displayName, email, password]
|
|
properties:
|
|
workspaceName:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
displayName:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
email:
|
|
type: string
|
|
format: email
|
|
password:
|
|
type: string
|
|
minLength: 12
|
|
CompleteSetupResponse:
|
|
type: object
|
|
required: [workspaceId, userId]
|
|
properties:
|
|
workspaceId:
|
|
type: string
|
|
format: uuid
|
|
userId:
|
|
type: string
|
|
format: uuid
|
|
CsrfToken:
|
|
type: object
|
|
required: [token, headerName]
|
|
properties:
|
|
token:
|
|
type: string
|
|
headerName:
|
|
type: string
|
|
WorkspaceRole:
|
|
type: string
|
|
enum: [Reader, Editor, Administrator]
|
|
SessionResponse:
|
|
type: object
|
|
required: [authenticated, setupRequired]
|
|
properties:
|
|
authenticated:
|
|
type: boolean
|
|
setupRequired:
|
|
type: boolean
|
|
userId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
displayName:
|
|
type: [string, "null"]
|
|
email:
|
|
type: [string, "null"]
|
|
workspaceId:
|
|
type: [string, "null"]
|
|
format: uuid
|
|
role:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/WorkspaceRole"
|
|
- type: "null"
|
|
LoginRequest:
|
|
type: object
|
|
required: [email, password]
|
|
properties:
|
|
email:
|
|
type: string
|
|
format: email
|
|
password:
|
|
type: string
|
|
RecoveryRedeemRequest:
|
|
type: object
|
|
required: [token, newPassword]
|
|
properties:
|
|
token:
|
|
type: string
|
|
newPassword:
|
|
type: string
|
|
minLength: 12
|
|
UserSummary:
|
|
type: object
|
|
required: [id, displayName, email, role, enabled, createdAt]
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
displayName:
|
|
type: string
|
|
email:
|
|
type: string
|
|
role:
|
|
$ref: "#/components/schemas/WorkspaceRole"
|
|
enabled:
|
|
type: boolean
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
lastLoginAt:
|
|
type: [string, "null"]
|
|
format: date-time
|
|
CreateUserRequest:
|
|
type: object
|
|
required: [displayName, email, password, role]
|
|
properties:
|
|
displayName:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
email:
|
|
type: string
|
|
format: email
|
|
password:
|
|
type: string
|
|
minLength: 12
|
|
role:
|
|
$ref: "#/components/schemas/WorkspaceRole"
|
|
UpdateUserRequest:
|
|
type: object
|
|
properties:
|
|
role:
|
|
oneOf:
|
|
- $ref: "#/components/schemas/WorkspaceRole"
|
|
- type: "null"
|
|
enabled:
|
|
type: [boolean, "null"]
|
|
IssueRecoveryTokenResponse:
|
|
type: object
|
|
required: [token, expiresAt]
|
|
properties:
|
|
token:
|
|
type: string
|
|
description: Shown once, at issuance. Never retrievable again.
|
|
expiresAt:
|
|
type: string
|
|
format: date-time
|