openapi.yaml
OpenAPI / YAML · 395 lines · 46,791 bytes · wiki path 10 Architecture/contracts/openapi.yaml · download the raw file · cited from Stages S5–S7 — Application, Verification, Reversal · Case journeys — testing the architecture with real work · Case protocol — requests, records, prompts and results · Contracts · Developer implementation guide · Software Architecture · UI
Same folder: Connectors.Abstractions.cs · configuration-change.schema.json · configuration-input.schema.json · connector-capability.schema.json · eval-set.schema.json · execution-obligations.schema.json · ledger-record.schema.json · message-envelope.schema.json · model-turn.schema.json · module-manifest.schema.json · realtime-events.schema.json · runtime-state.schema.json · validate_case_journeys.py · validate_contracts.py · validate_runtime_contracts.py · write-shape.schema.json
openapi: 3.1.0
info:
title: AISA v2.0 — Ablera.Serdica.AI.Support.Webservice HTTP API
version: 1.0.0-draft
description: |
The HTTP contract the administration UI (serdica-ui `ai-support` section) and the client app consume — Delivery § 1.3
track 3 "the UI needs the service's contracts, not its implementation". Realtime is SignalR (realtime-events.schema.json);
this file is the request/response side. Every mutating call is a ledger event; every list is a projection over
SRD_SUPPORT. Authentication: Authority JWT (audience SerdicaAPI); roles are ClaimTypes.Role claims (D134).
Budgets are minutes (D135). No payload here ever carries an identifier — handles only (Trust and Data § 4).
Base path on the QA host: /ai-support/api/v1 behind nginx.
x-status: draft — stable shape, field-level extension expected during wave 1 (D139)
servers:
- url: /ai-support/api/v1
security:
- authorityJwt: []
tags:
- { name: inbox, description: Arrivals re-briefed from their source (UI › Inbox) }
- { name: cases, description: Cases, sessions, the case chat (UI › Cases) }
- { name: decisions, description: Gates, decisions, held batches, write shapes (UI › Decisions) }
- { name: papers, description: The token-gated paper service }
- { name: studio, description: Profiles, prompts, skills, eval sets — draft → test → publish (UI › Studio) }
- { name: knowledge, description: The memory tree, proposals, articles (UI › Knowledge) }
- { name: metrics, description: Ledger projections — hours and gates, never money (UI › Metrics) }
- { name: control, description: Connectors, roles and policies, exceptions, kill switch, audit tail (UI › Control) }
- { name: client, description: The customer's four screens }
paths:
# ------------------------------------------------------------------ inbox
/inbox/arrivals:
get:
tags: [inbox]
summary: List arrivals (state new / awaiting_authorisation by default)
parameters: [ { $ref: '#/components/parameters/state' }, { $ref: '#/components/parameters/page' } ]
responses: { '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Page_Arrival' } } } } }
/inbox/arrivals/{arrivalId}:
get: { tags: [inbox], summary: "One arrival with its re-brief", parameters: [ { $ref: '#/components/parameters/arrivalId' } ], responses: { '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Arrival' } } } } } }
/inbox/arrivals/{arrivalId}/decide:
post:
tags: [inbox]
summary: One operator action — open, correct the route, merge into a case, dismiss
description: Agents § 5c — one action corrects intake; the re-routing rate is a metric. Requires operator.
parameters: [{$ref: '#/components/parameters/arrivalId'}, {$ref: '#/components/parameters/requestId'}]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ArrivalDecision' } } } }
responses: {'200': {description: Decided, content: {application/json: {schema: {$ref: '#/components/schemas/Arrival'}}}}, '403': {$ref: '#/components/responses/Forbidden'}, '409': {$ref: '#/components/responses/Conflict'}}
# ------------------------------------------------------------------ cases
/cases:
get:
tags: [cases]
summary: Case list — state, where, controller, waiting on, clocks
parameters: [ { $ref: '#/components/parameters/state' }, { name: module, in: query, schema: { type: string, enum: [configuration, support, source] } }, { $ref: '#/components/parameters/page' } ]
responses: { '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Page_CaseRow' } } } } }
post:
tags: [cases]
summary: Open a case from the administration screen
description: Intake still runs (routing, duplicate verdict, initiation). Requires operator; the module must be one the caller may start (intake.initiation).
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/OpenCaseRequest' } } } }
responses: {'201': {description: Opened, content: {application/json: {schema: {$ref: '#/components/schemas/Case'}}}}, '403': {$ref: '#/components/responses/Forbidden'}, '409': {$ref: '#/components/responses/Conflict'}}
parameters: [{$ref: '#/components/parameters/requestId'}]
/cases/{caseId}:
get: { tags: [cases], summary: "One case with its tasks, grants, clocks and papers index", parameters: [ { $ref: '#/components/parameters/caseId' } ], responses: { '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Case' } } } } } }
/cases/{caseId}/cards:
get:
tags: [cases]
summary: The case chat — one thread of cards, paged (ui.transcript page size 200)
parameters: [ { $ref: '#/components/parameters/caseId' }, { name: after, in: query, schema: { type: integer, description: ledger SEQ_NO cursor } }, { name: limit, in: query, schema: { type: integer, maximum: 200, default: 200 } } ]
responses: { '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Page_Card' } } } } }
/cases/{caseId}/where:
get: { tags: [cases], summary: "The Where rail — everything the case touched, by environment · system · object · mode", parameters: [ { $ref: '#/components/parameters/caseId' } ], responses: { '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/WhereChip' } } } } } } }
/cases/{caseId}/control:
post:
tags: [cases]
summary: Take control of the session (operator); the previous controller is notified
parameters: [{$ref: '#/components/parameters/caseId'}, {$ref: '#/components/parameters/requestId'}]
responses: {'200': {description: Controller changed, content: {application/json: {schema: {$ref: '#/components/schemas/Session'}}}}, '403': {$ref: '#/components/responses/Forbidden'}, '409': {$ref: '#/components/responses/Conflict'}}
/cases/{caseId}/commands:
post:
tags: [cases]
summary: A composer command — /steer /answer /stop /resume /handover /consult /park /try
description: Only the controller may post; viewers have no composer (UI § 7). /handover raises Hd; /answer answers a parked question.
parameters: [{$ref: '#/components/parameters/caseId'}, {$ref: '#/components/parameters/requestId'}]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/Command' } } } }
responses: {'202': {description: Accepted — the effect arrives as cards over SignalR}, '403': {$ref: '#/components/responses/Forbidden'}, '409': {$ref: '#/components/responses/Conflict'}}
/cases/{caseId}/session:
delete: {tags: [cases], summary: "Delete the live session (the case and its papers remain)", parameters: [{$ref: '#/components/parameters/caseId'}, {$ref: '#/components/parameters/requestId'}], responses: {'204': {description: Deleted}, '409': {$ref: '#/components/responses/Conflict'}}}
# ------------------------------------------------------------------ decisions
/decisions/gates:
get:
tags: [decisions]
summary: Every waiting gate the caller may see, with its packet reference and age
description: A gate the caller may decide shows actions; one they may not shows who can (Trust and Data § 3).
parameters: [ { name: mine, in: query, schema: { type: boolean, default: true, description: only gates the caller's roles can decide } }, { $ref: '#/components/parameters/page' } ]
responses: { '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Page_Gate' } } } } }
/decisions/gates/{gateId}:
get: { tags: [decisions], summary: "One gate with its decision packet (mechanism, verifier verdict, exact change with counts, revert, uncertainties, auditor verdict, the failed auto-confirm check)", parameters: [ { $ref: '#/components/parameters/gateId' } ], responses: { '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/GateDetail' } } } } } }
/decisions/gates/{gateId}/decide:
post:
tags: [decisions]
summary: Decide a gate — approve, reject, amend, defer
description: |
The caller must hold the gate's REQUIRED_ROLE for its target (approval is a permission, not a second person).
Approving binds a grant to the shown artefact hash; a material change re-opens the gate. Round-trip target < 2 s.
parameters: [{$ref: '#/components/parameters/gateId'}, {$ref: '#/components/parameters/requestId'}]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/GateDecisionRequest' } } } }
responses: {'200': {description: Decided, content: {application/json: {schema: {$ref: '#/components/schemas/GateDecision'}}}}, '403': {$ref: '#/components/responses/Forbidden'}, '409': {$ref: '#/components/responses/Conflict'}}
/decisions/held-batches:
get: { tags: [decisions], summary: "Approved-but-unapplied work with its age", parameters: [ { $ref: '#/components/parameters/page' } ], responses: { '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Page_HeldBatch' } } } } } }
/decisions/held-batches/{batchId}/apply:
post: {tags: [decisions], summary: "Apply a held batch now (re-preflights if older than held_batches.repreflight_after)", parameters: [{name: batchId, in: path, required: true, schema: {type: string}}, {$ref: '#/components/parameters/requestId'}], responses: {'202': {description: Applying}, '403': {$ref: '#/components/responses/Forbidden'}, '409': {$ref: '#/components/responses/Conflict'}}}
/decisions/shapes:
get: { tags: [decisions], summary: "Write shapes being trained, approved, suspended — with clean-instance counts and expiry", parameters: [ { $ref: '#/components/parameters/state' } ], responses: { '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/WriteShapeRow' } } } } } } }
/decisions/shapes/{shapeId}/extend:
post:
tags: [decisions]
summary: Extend an approved shape's expiry — once, by at most one day (D133)
parameters: [{name: shapeId, in: path, required: true, schema: {type: string}}, {$ref: '#/components/parameters/requestId'}]
requestBody: { required: true, content: { application/json: { schema: { type: object, required: [until], properties: { until: { type: string, format: date-time }, reason: { type: string } } } } } }
responses: {'200': {description: Extended}, '403': {$ref: '#/components/responses/Forbidden'}, '422': {description: Beyond one day, or already extended — re-approval at HW-approve is the path: null}, '409': {$ref: '#/components/responses/Conflict'}}
# ------------------------------------------------------------------ papers
/papers/{caseId}:
get: { tags: [papers], summary: "List the case's papers with revisions", parameters: [ { $ref: '#/components/parameters/caseId' } ], responses: { '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/PaperRef' } } } } } } }
/papers/{caseId}/{kind}/{name}:
get:
tags: [papers]
summary: Read a paper (latest or ?revision=n); every read is a PAPER_ACCESS ledger event
parameters: [ { $ref: '#/components/parameters/caseId' }, { name: kind, in: path, required: true, schema: { type: string } }, { name: name, in: path, required: true, schema: { type: string } }, { name: revision, in: query, schema: { type: integer } } ]
responses: { '200': { description: OK, content: { text/markdown: { schema: { type: string } }, application/json: { schema: {} }, application/octet-stream: { schema: { type: string, format: binary } } } } }
# ------------------------------------------------------------------ studio
/studio/{subject}:
get:
tags: [studio]
summary: Versions of profiles | prompts | skills | eval-sets | routes with state and eval result
parameters: [ { $ref: '#/components/parameters/subject' }, { name: key, in: query, schema: { type: string } } ]
responses: { '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/GovernedVersion' } } } } } }
post:
tags: [studio]
summary: Create a draft version (new, or "from template" — whole profiles, no inheritance, D50)
parameters: [{$ref: '#/components/parameters/subject'}, {$ref: '#/components/parameters/requestId'}]
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
responses: {'201': {description: Draft created, content: {application/json: {schema: {$ref: '#/components/schemas/GovernedVersion'}}}}, '409': {$ref: '#/components/responses/Conflict'}}
/studio/{subject}/{key}/{version}/try:
post: {tags: [studio], summary: "Try a draft on one case (canary) — no publish", parameters: [{$ref: '#/components/parameters/subject'}, {$ref: '#/components/parameters/key'}, {$ref: '#/components/parameters/version'}, {$ref: '#/components/parameters/requestId'}], requestBody: {content: {application/json: {schema: {type: object, properties: {case_id: {type: string}}}}}}, responses: {'202': {description: Running}, '409': {$ref: '#/components/responses/Conflict'}}}
/studio/{subject}/{key}/{version}/evaluate:
post: {tags: [studio], summary: "Run the eval set; the run is an EVAL_RUNS row and a GOVERNANCE_EVENTS evaluated/PASSED|FAILED event", parameters: [{$ref: '#/components/parameters/subject'}, {$ref: '#/components/parameters/key'}, {$ref: '#/components/parameters/version'}, {$ref: '#/components/parameters/requestId'}], responses: {'202': {description: Running, content: {application/json: {schema: {type: object, properties: {eval_run_id: {type: string}}}}}}, '409': {$ref: '#/components/responses/Conflict'}}}
/studio/{subject}/{key}/{version}/approve:
post: {tags: [studio], summary: "Approve (prompt_publisher for the module/stage) — a GOVERNANCE_EVENTS approved event", parameters: [{$ref: '#/components/parameters/subject'}, {$ref: '#/components/parameters/key'}, {$ref: '#/components/parameters/version'}, {$ref: '#/components/parameters/requestId'}], responses: {'200': {description: Approved}, '403': {$ref: '#/components/responses/Forbidden'}, '409': {$ref: '#/components/responses/Conflict'}}}
/studio/{subject}/{key}/{version}/publish:
post: {tags: [studio], summary: "Publish — refused by the database without a PASSED evaluation and an approval, or with a red eval set (008 guards)", parameters: [{$ref: '#/components/parameters/subject'}, {$ref: '#/components/parameters/key'}, {$ref: '#/components/parameters/version'}, {$ref: '#/components/parameters/requestId'}], responses: {'200': {description: Published}, '403': {$ref: '#/components/responses/Forbidden'}, '422': {description: Guard refused — the body names the failing condition}, '409': {$ref: '#/components/responses/Conflict'}}}
/studio/{subject}/{key}/rollback:
post: {tags: [studio], summary: "Roll back = re-publish the previous version; nothing is edited in place", parameters: [{$ref: '#/components/parameters/subject'}, {$ref: '#/components/parameters/key'}, {$ref: '#/components/parameters/requestId'}], requestBody: {content: {application/json: {schema: {type: object, required: [to_version], properties: {to_version: {type: integer}}}}}}, responses: {'200': {description: Rolled back}, '409': {$ref: '#/components/responses/Conflict'}}}
# ------------------------------------------------------------------ knowledge
/knowledge/tree:
get: { tags: [knowledge], summary: "The memory tree — nodes, owners, article counts", responses: { '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/MemoryNode' } } } } } } }
/knowledge/proposals:
get: { tags: [knowledge], summary: "The proposals queue (state proposed) with provenance and the cases that used each", responses: { '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Article' } } } } } } }
/knowledge/articles/{articleId}/confirm:
post: {tags: [knowledge], summary: "Confirm a proposal into active (human review) — the owning profile's curator or an operator of that domain", parameters: [{name: articleId, in: path, required: true, schema: {type: string}}, {$ref: '#/components/parameters/requestId'}], responses: {'200': {description: Active}, '403': {$ref: '#/components/responses/Forbidden'}, '409': {$ref: '#/components/responses/Conflict'}}}
/knowledge/articles/{articleId}/retire:
post: {tags: [knowledge], summary: "Retire an article; the response lists the cases that pinned it", parameters: [{name: articleId, in: path, required: true, schema: {type: string}}, {$ref: '#/components/parameters/requestId'}], requestBody: {content: {application/json: {schema: {type: object, required: [reason], properties: {reason: {type: string}}}}}}, responses: {'200': {description: Retired, content: {application/json: {schema: {type: object, properties: {pinned_by_cases: {type: array, items: {type: string}}}}}}}, '409': {$ref: '#/components/responses/Conflict'}}}
/knowledge/experience/search:
get: { tags: [knowledge], summary: "Symptom-signature lookup over the experience index (the triage precedent query)", parameters: [ { name: q, in: query, required: true, schema: { type: string } }, { name: node, in: query, schema: { type: string, enum: [support/experience, platform/experience] } } ], responses: { '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ExperienceHit' } } } } } } }
# ------------------------------------------------------------------ metrics
/metrics/{metric}:
get:
tags: [metrics]
summary: One metric of Metrics § targets, parsed from the ledger by a tool — hours and gates, never money
parameters: [ { name: metric, in: path, required: true, schema: { type: string } }, { name: from, in: query, schema: { type: string, format: date } }, { name: to, in: query, schema: { type: string, format: date } }, { name: module, in: query, schema: { type: string } } ]
responses: { '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/MetricSeries' } } } } }
# ------------------------------------------------------------------ control
/control/connectors:
get: { tags: [control], summary: "Connector scopes — reach, operations, ceilings — with health per environment", responses: { '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ConnectorScopeRow' } } } } } } }
/control/connectors/{scopeId}/probe:
post: {tags: [control], summary: "Probe a reach row now; the result updates VERIFIED_ON_UTC and health (administrator)", parameters: [{name: scopeId, in: path, required: true, schema: {type: string}}, {$ref: '#/components/parameters/requestId'}], responses: {'200': {description: Probed}, '403': {$ref: '#/components/responses/Forbidden'}, '409': {$ref: '#/components/responses/Conflict'}}}
/control/policies:
get: { tags: [control], summary: "Policy values by scope and key (the Whitelabel Catalogue as rows)", parameters: [ { name: scope, in: query, schema: { type: string, enum: [platform, customer, case_type, profile] } }, { name: key, in: query, schema: { type: string } } ], responses: { '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/PolicyValue' } } } } } } }
post: {tags: [control], summary: "Publish a new version of a policy value (administrator; customer scope = a governed change of the plug-in)", requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/PolicyValue'}}}}, responses: {'201': {description: Published}, '403': {$ref: '#/components/responses/Forbidden'}, '409': {$ref: '#/components/responses/Conflict'}}, parameters: [{$ref: '#/components/parameters/requestId'}]}
/control/roles:
get: { tags: [control], summary: "Live role assignments (token_claim · directory_group · platform_row) per subject", responses: { '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/RoleAssignment' } } } } } } }
/control/risk-acceptances:
get: { tags: [control], summary: "The N-12 register — accepted risk classes per case type × gate × target", responses: { '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/RiskAcceptance' } } } } } } }
post: {tags: [control], summary: "Record an organisational acceptance (administrator, with the decision reference) — never a policy row, never an agent", requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/RiskAcceptance'}}}}, responses: {'201': {description: Recorded}, '403': {$ref: '#/components/responses/Forbidden'}, '409': {$ref: '#/components/responses/Conflict'}}, parameters: [{$ref: '#/components/parameters/requestId'}]}
/control/exceptions:
get: { tags: [control], summary: "Parked cases by reason with age, suspended shapes, red connectors, budget stops", responses: { '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Exceptions' } } } } } }
/control/kill-switch:
get: {tags: [control], summary: 'Current live mode, checked at case admission and every effect dispatch.', responses: {'200': {description: OK, content: {application/json: {schema: {type: object, properties: {mode: {type: string, enum: ['off', soft, hard]}, since: {type: string, format: date-time}}}}}}}}
post: {tags: [control], summary: "Set live off/soft/hard mode (Administrator); publish control event after the state commit.", parameters: [{$ref: '#/components/parameters/requestId'}], requestBody: {required: true, content: {application/json: {schema: {type: object, additionalProperties: false, required: [mode, expected_version, reason], properties: {mode: {type: string, enum: ['off', soft, hard]}, expected_version: {type: string}, reason: {type: string, minLength: 1}}}}}}, responses: {'200': {description: Live state changed and recorded.}, '403': {$ref: '#/components/responses/Forbidden'}, '409': {$ref: '#/components/responses/Conflict'}}}
/control/audit:
get: { tags: [control], summary: "The audit tail — ledger records, filterable by case, kind, actor, time", parameters: [ { name: case_id, in: query, schema: { type: string } }, { name: kind, in: query, schema: { type: string } }, { name: after_seq, in: query, schema: { type: integer } }, { name: limit, in: query, schema: { type: integer, maximum: 500 } } ], responses: { '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: 'ledger-record.schema.json' } } } } } } }
/control/ledger/verify:
post: {tags: [control], summary: "Run verify-ledger (chain continuity, seal integrity, export currency) and return the result", responses: {'200': {description: OK, content: {application/json: {schema: {type: object, properties: {ok: {type: boolean}, head_seq_no: {type: integer}, head_hash: {type: string}, last_exported_seal: {type: integer}, findings: {type: array, items: {type: string}}}}}}}, '409': {$ref: '#/components/responses/Conflict'}}, parameters: [{$ref: '#/components/parameters/requestId'}]}
# ------------------------------------------------------------------ client (the customer's four screens)
/client/requests:
get: { tags: [client], summary: "My requests — is anyone waiting on me (customer_representative)", responses: { '200': { description: OK, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ClientRequest' } } } } } } }
post: {tags: [client], summary: "Start an authorised intake case automatically; classification and destination entitlement checks follow.", requestBody: {required: true, content: {application/json: {schema: {type: object, required: [module, description], properties: {module: {type: string, enum: [support, configuration]}, description: {type: string}, attachments: {type: array, items: {type: string}}}}}}}, responses: {'201': {description: Opened}, '202': {description: Waiting for your organisation's approval}, '403': {description: Not granted — with the contract clause and who to talk to}, '409': {$ref: '#/components/responses/Conflict'}}, parameters: [{$ref: '#/components/parameters/requestId'}]}
/client/requests/{caseId}:
get: { tags: [client], summary: "One request — what was asked, what has been established, the single open question; no transcript, no system names, no handles", parameters: [ { $ref: '#/components/parameters/caseId' } ], responses: { '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/ClientRequest' } } } } } }
/client/requests/{caseId}/accept:
post: {tags: [client], summary: "Customer-owned H1/H3 business confirmation or CG-11 preview/delivery acceptance, recorded with the person's name and the artefact hash", parameters: [{$ref: '#/components/parameters/caseId'}, {$ref: '#/components/parameters/requestId'}], requestBody: {required: true, content: {application/json: {schema: {type: object, required: [gate_id, artefact_hash, decision, scope, expected_version], properties: {gate_id: {type: string}, artefact_hash: {type: string}, decision: {type: string, enum: [accept, reject]}, note: {type: string}, scope: {type: string, enum: [scope, stage_result, preview, delivery]}, expected_version: {type: string}}}}}}, responses: {'200': {description: Recorded}, '409': {$ref: '#/components/responses/Conflict'}}}
/client/requests/{caseId}/responses:
post:
tags: [client]
summary: Answer a question or report an assigned action; queue verification, never accept delivery implicitly
description: Authenticate the designated respondent and customer/case ownership. Compare the current question/step, request reference and shown version. Scan attachments and substitute handles. Commit the response, INBOX_MESSAGES dedup record and verification outbox message atomically. Same key/body returns the same acknowledgement; stale scope or a response for another actor is rejected. No platform WRITE_LOG is invented for external work.
parameters: [{$ref: '#/components/parameters/caseId'}, {$ref: '#/components/parameters/requestId'}]
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/CaseResponse'}}}}
responses:
'202': {description: Response recorded; verification or eligible question work queued.}
'403': {$ref: '#/components/responses/Forbidden'}
'409': {$ref: '#/components/responses/Conflict'}
/cases/{caseId}/responses:
post:
tags: [cases]
summary: Record an operator answer or attributable external evidence through the same response service
description: Require case control or the assigned response role. Imported channel evidence retains its source actor and request reference; the operator is recorded as recorder, not as executor. Scope, attachment and dedup checks are identical to client responses.
parameters: [{$ref: '#/components/parameters/caseId'}, {$ref: '#/components/parameters/requestId'}]
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/CaseResponse'}}}}
responses:
'202': {description: Response recorded and eligible work queued.}
'403': {$ref: '#/components/responses/Forbidden'}
'409': {$ref: '#/components/responses/Conflict'}
components:
securitySchemes:
authorityJwt: { type: http, scheme: bearer, bearerFormat: JWT, description: Authority (OIDC) access token, audience SerdicaAPI; roles as ClaimTypes.Role claims }
parameters:
page: { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
state: { name: state, in: query, schema: { type: string } }
caseId: { name: caseId, in: path, required: true, schema: { type: string } }
arrivalId: { name: arrivalId, in: path, required: true, schema: { type: string } }
gateId: { name: gateId, in: path, required: true, schema: { type: string } }
subject: { name: subject, in: path, required: true, schema: { type: string, enum: [profiles, prompts, skills, eval-sets, routes] } }
key: { name: key, in: path, required: true, schema: { type: string } }
version: { name: version, in: path, required: true, schema: { type: integer } }
requestId: {name: Idempotency-Key, in: header, required: true, schema: {type: string, minLength: 8, maxLength: 128}, description: 'Unique logical command id, scoped server-side to authenticated caller and operation. Same key/different body is a conflict.'}
responses:
Forbidden: { description: The caller does not hold the role for this action and target; the body names who does, content: { application/json: { schema: { type: object, properties: { required_role: { type: string }, holders: { type: array, items: { type: string } } } } } } }
Conflict: {description: 'Stale version, changed packet, already decided resource or idempotency-key/body mismatch; reload the resource before a new command.'}
schemas:
WhereChip:
type: object
required: [environment, system, object, mode]
properties: { environment: { type: string }, system: { type: string }, object: { type: string }, mode: { type: string, enum: [read, write, send, ddl, source] }, resolved: { type: boolean } }
Clock:
type: object
properties: { kind: { type: string, enum: [response, resolution, offer] }, due: { type: string, format: date-time }, paused_since: { type: string, format: date-time }, breach_warning: { type: boolean } }
Budget:
type: object
description: minutes only (D135)
properties: { predicted_minutes: { type: number }, used_minutes: { type: number }, soft_warning_at_pct: { type: integer, default: 80 }, state: { type: string, enum: [under, warned, stopped] } }
Arrival:
type: object
properties: { id: { type: string }, channel: { type: string }, origin_key: { type: string }, received_on: { type: string, format: date-time }, subject: { type: string }, brief_paper_ref: { type: string }, identifiers: { type: array, items: { $ref: '#/components/schemas/WhereChip' } }, duplicate_of_case_id: { type: string }, proposed_module: { type: string }, proposed_case_type: { type: string }, route_confidence: { type: string, enum: [high, low] }, initiation_outcome: { type: string }, clocks: { type: array, items: { $ref: '#/components/schemas/Clock' } }, precedents: { type: array, items: { type: string } }, state: { type: string } }
ArrivalDecision:
type: object
required: [action]
properties: { action: { type: string, enum: [open, reroute, merge, dismiss] }, module: { type: string }, case_type: { type: string }, merge_into_case_id: { type: string }, reason: { type: string } }
OpenCaseRequest:
type: object
required: [module, objective]
properties: { module: { type: string, enum: [configuration, support, source] }, case_type: { type: string }, objective: { type: string }, customer_code: { type: string }, attachments: { type: array, items: { type: string } }, links: { type: array, items: { type: object, properties: { system: { type: string }, ref: { type: string } } } } }
CaseRow:
type: object
properties: {id: {type: string}, case_no: {type: string}, module: {type: string}, case_type: {type: string}, state: {type: string}, park_reason: {type: string}, where: {type: array, items: {$ref: '#/components/schemas/WhereChip'}}, controller: {type: string}, viewers: {type: integer}, waiting_on: {type: string}, clocks: {type: array, items: {$ref: '#/components/schemas/Clock'}}, budget: {$ref: '#/components/schemas/Budget'}, sub_case_age_minutes: {type: number}, opened_on: {type: string, format: date-time}, row_version: {type: string, description: 'Opaque decimal concurrency token; compare on commands, do not increment in the browser.'}, working_module: {type: string, description: Business graph id; data is embedded in persisted module support.}}
Case:
allOf:
- $ref: '#/components/schemas/CaseRow'
- type: object
properties: { objective: { type: string }, session: { $ref: '#/components/schemas/Session' }, tasks: { type: array, items: { $ref: '#/components/schemas/TaskRow' } }, grants: { type: array, items: { $ref: '#/components/schemas/GrantRow' } }, papers: { type: array, items: { $ref: '#/components/schemas/PaperRef' } }, links: { type: array, items: { type: object, properties: { kind: { type: string }, system: { type: string }, ref: { type: string }, authoritative: { type: boolean } } } }, parent_case_id: { type: string }, sub_cases: { type: array, items: { type: string } } }
Session:
type: object
properties: {id: {type: string}, state: {type: string, enum: [live, stopped, resumed, deleted]}, controller: {type: string}, viewers: {type: integer}, worker_id: {type: string}, last_checkpoint: {type: string, format: date-time}, row_version: {type: string, description: 'Opaque decimal concurrency token; compare on commands, do not increment in the browser.'}}
TaskRow:
type: object
properties: {id: {type: string}, task_path: {type: string}, kind: {type: string}, profile_key: {type: string}, profile_version: {type: integer}, stage_id: {type: string}, state: {type: string}, park_reason: {type: string}, budget: {$ref: '#/components/schemas/Budget'}, row_version: {type: string, description: 'Opaque decimal concurrency token; compare on commands, do not increment in the browser.'}}
GrantRow:
type: object
properties: { id: { type: string }, environment: { type: string }, system: { type: string }, target: { type: string }, mode: { type: string }, bound_artefact_hash: { type: string }, expires_on: { type: string, format: date-time }, state: { type: string } }
Card:
type: object
description: UI § 2 — one card of the case chat; every card says where it is about
required: [seq_no, kind, occurred_on, actor]
properties:
seq_no: { type: integer, description: ledger SEQ_NO — the cursor }
kind: { type: string, enum: [brief, agent, tool, question, plan, decision, verdict, write, handover, consult, status, human, memory] }
occurred_on: { type: string, format: date-time }
actor: { type: object, properties: { kind: { type: string }, id: { type: string }, profile_key: { type: string }, profile_version: { type: integer } } }
task_path: { type: string }
where: { $ref: '#/components/schemas/WhereChip' }
title: { type: string }
stated_reasoning: { type: string, description: the agent's declared reasoning for a step — never raw model chain-of-thought }
summary: { type: string }
paper_refs: { type: array, items: { type: string } }
gate_id: { type: string }
trust_class: { type: string }
ledger_record_id: { type: string }
Command:
type: object
required: [command]
properties: { command: { type: string, enum: [steer, answer, stop, resume, handover, consult, park, try, where, papers] }, text: { type: string }, question_id: { type: string }, to_module: { type: string }, domain: { type: string } }
Gate:
type: object
properties: {id: {type: string}, case_id: {type: string}, case_no: {type: string}, kind: {type: string}, target_class: {type: string}, where: {$ref: '#/components/schemas/WhereChip'}, required_role: {type: string}, can_decide: {type: boolean}, holders: {type: array, items: {type: string}}, opened_on: {type: string, format: date-time}, age_minutes: {type: number}, escalation_level: {type: integer}, auto_confirm_failed_check: {type: integer}, auditor_verdict: {type: string}, row_version: {type: string, description: 'Opaque decimal concurrency token; compare on commands, do not increment in the browser.'}}
GateDetail:
allOf:
- $ref: '#/components/schemas/Gate'
- type: object
properties: { gate_id: { type: string }, artefact_hash: { type: string }, packet_paper_ref: { type: string }, packet: { description: 'the decision packet (solution packet / write packet / brief / sign-out) — read from the paper', type: object }, auditor_verdict_paper_ref: { type: string }, revert_path_ref: { type: string } }
GateDecisionRequest:
type: object
required: [decision, artefact_hash, expected_version]
properties: {decision: {type: string, enum: [approve, reject, amend, defer, accept]}, artefact_hash: {type: string, description: must equal the packet shown — otherwise 409}, scope_approved: {type: object}, reason: {type: string}, expected_version: {type: string}}
GateDecision:
type: object
properties: { id: { type: string }, gate_id: { type: string }, actor_kind: { type: string, enum: [person, policy] }, actor_id: { type: string }, decision: { type: string }, decided_on: { type: string, format: date-time }, grant_ids: { type: array, items: { type: string } }, ledger_record_id: { type: string } }
HeldBatch:
type: object
properties: { id: { type: string }, case_id: { type: string }, items_total: { type: integer }, items_applied: { type: integer }, state: { type: string }, held_since: { type: string, format: date-time }, age_minutes: { type: number }, needs_repreflight: { type: boolean } }
WriteShapeRow:
type: object
properties: { id: { type: string }, shape_key: { type: string }, version: { type: integer }, module: { type: string }, write_class: { type: string }, target_class: { type: string }, state: { type: string }, clean_instances: { type: integer }, approved_on: { type: string, format: date-time }, expires_on: { type: string, format: date-time }, extended_until: { type: string, format: date-time }, suspended_reason: { type: string } }
PaperRef:
type: object
properties: { id: { type: string }, kind: { type: string }, name: { type: string }, revision_no: { type: integer }, content_hash: { type: string }, media_type: { type: string }, byte_length: { type: integer }, created_on: { type: string, format: date-time } }
GovernedVersion:
type: object
properties: {subject: {type: string}, key: {type: string}, version: {type: integer}, state: {type: string, enum: [draft, testing, published, retired]}, owner: {type: string}, eval_result: {type: string, enum: [green, red, unknown]}, last_eval_run_id: {type: string}, published_on: {type: string, format: date-time}, events: {type: array, items: {type: object, properties: {event: {type: string}, result: {type: string}, actor: {type: string}, true: {type: string, format: date-time}}}}, row_version: {type: string, description: 'Opaque decimal concurrency token; compare on commands, do not increment in the browser.'}}
MemoryNode:
type: object
properties: { node_path: { type: string }, parent_path: { type: string }, branch: { type: string }, owner_profile_key: { type: string }, article_count: { type: integer }, index_commit_sha: { type: string }, refreshed_on: { type: string, format: date-time } }
Article:
type: object
properties: { id: { type: string }, node_path: { type: string }, article_key: { type: string }, version: { type: integer }, title: { type: string }, state: { type: string }, provenance: { type: object }, reaches_untrusted: { type: boolean }, used_by_cases: { type: array, items: { type: string } }, contradicted: { type: boolean }, git_sha: { type: string } }
ExperienceHit:
type: object
properties: { id: { type: string }, symptom_signature: { type: string }, mechanism: { type: string }, fix_shape: { type: string }, skill_key: { type: string }, case_id: { type: string }, observed_version: { type: string }, score: { type: number } }
MetricSeries:
type: object
properties: { metric: { type: string }, unit: { type: string, enum: [minutes, hours, count, percent, days] }, basis: { type: string, description: the ledger query that produced it }, target: { type: number }, baseline: { type: number }, points: { type: array, items: { type: object, properties: { period: { type: string }, value: { type: number } } } } }
ConnectorScopeRow:
allOf:
- $ref: 'connector-capability.schema.json'
- type: object
properties: { id: { type: string }, health: { type: string }, verified_on: { type: string, format: date-time } }
PolicyValue:
type: object
required: [scope, key, value]
properties: { id: { type: string }, scope: { type: string, enum: [platform, customer, case_type, profile] }, customer_code: { type: string }, module: { type: string }, case_type: { type: string }, profile_key: { type: string }, key: { type: string }, version: { type: integer }, value: {}, value_type: { type: string }, state: { type: string } }
RoleAssignment:
type: object
properties: { subject_id: { type: string }, role: { type: string, enum: [viewer, operator, approver, prompt_publisher, administrator, customer_representative] }, scope_module: { type: string }, scope_stage: { type: string }, scope_target_class: { type: string }, source: { type: string, enum: [token_claim, directory_group, platform_row] }, granted_on: { type: string, format: date-time } }
RiskAcceptance:
type: object
required: [case_type, gate_kind, target_class, write_class, decision_ref]
properties: {id: {type: string}, customer_code: {type: string}, module: {type: string}, case_type: {type: string}, gate_kind: {type: string}, target_class: {type: string}, write_class: {type: string, enum: [W1, W2, W3, W4, W5, W6, W7]}, accepted_by: {type: string}, decision_ref: {type: string}, accepted_on: {type: string, format: date-time}, withdrawn_on: {type: string, format: date-time}}
Exceptions:
type: object
properties: { parked: { type: array, items: { type: object, properties: { case_id: { type: string }, reason: { type: string }, age_minutes: { type: number } } } }, suspended_shapes: { type: array, items: { $ref: '#/components/schemas/WriteShapeRow' } }, red_connectors: { type: array, items: { type: object, properties: { connector: { type: string }, environment: { type: string }, since: { type: string, format: date-time } } } }, budget_stops: { type: array, items: { type: string } }, ledger_export_lag_minutes: { type: number } }
CaseResponse:
type: object
additionalProperties: false
required: [request_ref, expected_version, response_kind, text, evidence_refs]
properties:
request_ref: {type: string, minLength: 1, description: Exact outstanding question or external instruction paper.}
expected_version: {type: string, minLength: 1}
response_kind: {type: string, enum: [answer, report_completed, cannot_perform]}
question_id: {type: string, minLength: 1}
step_id: {type: string, minLength: 1}
plan_revision: {type: integer, minimum: 1}
text: {type: string, minLength: 1}
evidence_refs: {type: array, items: {type: string, minLength: 1}, uniqueItems: true, description: Existing case-scoped scanned evidence; presence is not a verification verdict.}
oneOf:
- properties: {response_kind: {const: answer}}
required: [question_id]
- properties: {response_kind: {enum: [report_completed, cannot_perform]}}
required: [step_id, plan_revision]
PendingAction:
type: object
required: [step_id, plan_revision, request_ref, instruction, waiting_on, requested_on, evidence_needed]
properties:
step_id: {type: string}
plan_revision: {type: integer}
request_ref: {type: string}
instruction: {type: string}
waiting_on: {type: string, description: Customer-facing actor or organisation label.}
requested_on: {type: string, format: date-time}
evidence_needed: {type: array, items: {type: string}}
ClientRequest:
type: object
description: the customer's vocabulary only — no transcript, no environment/system/agent names, no handles, no figures about cost
properties: {row_version: {type: string}, pending_action: {$ref: '#/components/schemas/PendingAction'}, id: {type: string}, title: {type: string}, state: {type: string, enum: [received, in_progress, waiting_for_you, ready_for_acceptance, done, declined]}, what_was_asked: {type: string}, what_is_established: {type: string}, open_question: {type: string}, waiting_on_you: {type: boolean}, acceptance: {type: object, properties: {gate_id: {type: string}, artefact_hash: {type: string}, summary: {type: string}, scope: {type: string, enum: [scope, stage_result, preview, delivery]}, target_label: {type: string, description: Customer-facing label for the exact preview or delivered target.}}}, updated_on: {type: string, format: date-time}}
Page_Arrival: { type: object, properties: { items: { type: array, items: { $ref: '#/components/schemas/Arrival' } }, page: { type: integer }, total: { type: integer } } }
Page_CaseRow: { type: object, properties: { items: { type: array, items: { $ref: '#/components/schemas/CaseRow' } }, page: { type: integer }, total: { type: integer } } }
Page_Card: { type: object, properties: { items: { type: array, items: { $ref: '#/components/schemas/Card' } }, next_after: { type: integer } } }
Page_Gate: { type: object, properties: { items: { type: array, items: { $ref: '#/components/schemas/Gate' } }, page: { type: integer }, total: { type: integer } } }
Page_HeldBatch: { type: object, properties: { items: { type: array, items: { $ref: '#/components/schemas/HeldBatch' } }, page: { type: integer }, total: { type: integer } } }
x-command-protocol: {authority: Reload roles and resource ownership before applying or replaying a command result., idempotency: 'Commit INBOX_MESSAGES command key/hash, state transition and OUTBOX_MESSAGES together.', concurrency: Existing-resource decisions compare the shown row_version and artefact hash; preview acceptance does not complete a requested release.}