test(portal): harden api contract validation
This commit is contained in:
@@ -9,28 +9,50 @@
|
|||||||
{
|
{
|
||||||
"url": "/api",
|
"url": "/api",
|
||||||
"description": "Gateway-relative API base"
|
"description": "Gateway-relative API base"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/dpd/api",
|
||||||
|
"description": "DPD mirror API base"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
{ "name": "contracts" },
|
{
|
||||||
{ "name": "portal" },
|
"name": "contracts"
|
||||||
{ "name": "reports" },
|
},
|
||||||
{ "name": "incidents" },
|
{
|
||||||
{ "name": "cases" },
|
"name": "portal"
|
||||||
{ "name": "readiness" },
|
},
|
||||||
{ "name": "telemetry" }
|
{
|
||||||
|
"name": "reports"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "incidents"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cases"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "readiness"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "telemetry"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"/contracts": {
|
"/contracts": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["contracts"],
|
"tags": [
|
||||||
|
"contracts"
|
||||||
|
],
|
||||||
"summary": "Contract index",
|
"summary": "Contract index",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Contract index",
|
"description": "Contract index",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/ContractIndex" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ContractIndex"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,14 +61,19 @@
|
|||||||
},
|
},
|
||||||
"/contracts/openapi.json": {
|
"/contracts/openapi.json": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["contracts"],
|
"tags": [
|
||||||
|
"contracts"
|
||||||
|
],
|
||||||
"summary": "OpenAPI document",
|
"summary": "OpenAPI document",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OpenAPI 3.1 document",
|
"description": "OpenAPI 3.1 document",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "type": "object", "additionalProperties": true }
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,14 +82,18 @@
|
|||||||
},
|
},
|
||||||
"/contracts/typescript.d.ts": {
|
"/contracts/typescript.d.ts": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["contracts"],
|
"tags": [
|
||||||
|
"contracts"
|
||||||
|
],
|
||||||
"summary": "TypeScript declarations",
|
"summary": "TypeScript declarations",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "TypeScript declaration file",
|
"description": "TypeScript declaration file",
|
||||||
"content": {
|
"content": {
|
||||||
"text/plain": {
|
"text/plain": {
|
||||||
"schema": { "type": "string" }
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,14 +102,18 @@
|
|||||||
},
|
},
|
||||||
"/health": {
|
"/health": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["portal"],
|
"tags": [
|
||||||
|
"portal"
|
||||||
|
],
|
||||||
"summary": "Light service health",
|
"summary": "Light service health",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Health payload",
|
"description": "Health payload",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,14 +122,18 @@
|
|||||||
},
|
},
|
||||||
"/operator": {
|
"/operator": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["portal"],
|
"tags": [
|
||||||
|
"portal"
|
||||||
|
],
|
||||||
"summary": "Operator overview payload",
|
"summary": "Operator overview payload",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Operator payload used by the HTML portal",
|
"description": "Operator payload used by the HTML portal",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,14 +142,18 @@
|
|||||||
},
|
},
|
||||||
"/manager": {
|
"/manager": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["portal"],
|
"tags": [
|
||||||
|
"portal"
|
||||||
|
],
|
||||||
"summary": "Manager payload",
|
"summary": "Manager payload",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Manager payload",
|
"description": "Manager payload",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,14 +162,18 @@
|
|||||||
},
|
},
|
||||||
"/owner": {
|
"/owner": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["portal"],
|
"tags": [
|
||||||
|
"portal"
|
||||||
|
],
|
||||||
"summary": "Owner/security payload",
|
"summary": "Owner/security payload",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Owner payload",
|
"description": "Owner payload",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,14 +182,18 @@
|
|||||||
},
|
},
|
||||||
"/reports": {
|
"/reports": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["reports"],
|
"tags": [
|
||||||
|
"reports"
|
||||||
|
],
|
||||||
"summary": "Management report payload",
|
"summary": "Management report payload",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "anonymize",
|
"name": "anonymize",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
"required": false,
|
"required": false,
|
||||||
"schema": { "type": "boolean" },
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"description": "Return anonymized values when supported"
|
"description": "Return anonymized values when supported"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -151,7 +202,9 @@
|
|||||||
"description": "Report payload",
|
"description": "Report payload",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/ReportPayload" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ReportPayload"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,14 +213,18 @@
|
|||||||
},
|
},
|
||||||
"/workforce/policy/explain": {
|
"/workforce/policy/explain": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["reports"],
|
"tags": [
|
||||||
|
"reports"
|
||||||
|
],
|
||||||
"summary": "Workforce scoring policy explanation",
|
"summary": "Workforce scoring policy explanation",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "anonymize",
|
"name": "anonymize",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
"required": false,
|
"required": false,
|
||||||
"schema": { "type": "boolean" }
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -175,7 +232,9 @@
|
|||||||
"description": "Policy explanation",
|
"description": "Policy explanation",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,14 +243,18 @@
|
|||||||
},
|
},
|
||||||
"/incidents": {
|
"/incidents": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["incidents"],
|
"tags": [
|
||||||
|
"incidents"
|
||||||
|
],
|
||||||
"summary": "Incident list and current state",
|
"summary": "Incident list and current state",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Incident payload",
|
"description": "Incident payload",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,13 +263,17 @@
|
|||||||
},
|
},
|
||||||
"/incident-review": {
|
"/incident-review": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": ["incidents"],
|
"tags": [
|
||||||
|
"incidents"
|
||||||
|
],
|
||||||
"summary": "Set manual review status for an incident candidate",
|
"summary": "Set manual review status for an incident candidate",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"required": true,
|
"required": true,
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/IncidentReviewRequest" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/IncidentReviewRequest"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -215,7 +282,9 @@
|
|||||||
"description": "Updated review state",
|
"description": "Updated review state",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,20 +293,30 @@
|
|||||||
},
|
},
|
||||||
"/investigation-pack/{candidate_id}": {
|
"/investigation-pack/{candidate_id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["incidents"],
|
"tags": [
|
||||||
|
"incidents"
|
||||||
|
],
|
||||||
"summary": "Export investigation pack for a candidate",
|
"summary": "Export investigation pack for a candidate",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "candidate_id",
|
"name": "candidate_id",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": { "type": "string" }
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "format",
|
"name": "format",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
"required": false,
|
"required": false,
|
||||||
"schema": { "type": "string", "enum": ["json", "markdown"] }
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"json",
|
||||||
|
"markdown"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -245,10 +324,14 @@
|
|||||||
"description": "Investigation pack",
|
"description": "Investigation pack",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"text/markdown": {
|
"text/markdown": {
|
||||||
"schema": { "type": "string" }
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,27 +340,35 @@
|
|||||||
},
|
},
|
||||||
"/cases": {
|
"/cases": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["cases"],
|
"tags": [
|
||||||
|
"cases"
|
||||||
|
],
|
||||||
"summary": "Case list",
|
"summary": "Case list",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Case list",
|
"description": "Case list",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/CaseListResponse" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CaseListResponse"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"tags": ["cases"],
|
"tags": [
|
||||||
|
"cases"
|
||||||
|
],
|
||||||
"summary": "Create a manual case from a confirmed candidate",
|
"summary": "Create a manual case from a confirmed candidate",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"required": true,
|
"required": true,
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/CreateCaseRequest" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CreateCaseRequest"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -286,7 +377,9 @@
|
|||||||
"description": "Created case",
|
"description": "Created case",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,20 +388,30 @@
|
|||||||
},
|
},
|
||||||
"/cases/{case_id}": {
|
"/cases/{case_id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["cases"],
|
"tags": [
|
||||||
|
"cases"
|
||||||
|
],
|
||||||
"summary": "Case details",
|
"summary": "Case details",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "case_id",
|
"name": "case_id",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": { "type": "string" }
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "format",
|
"name": "format",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
"required": false,
|
"required": false,
|
||||||
"schema": { "type": "string", "enum": ["json", "markdown"] }
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"json",
|
||||||
|
"markdown"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -316,10 +419,14 @@
|
|||||||
"description": "Case details",
|
"description": "Case details",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"text/markdown": {
|
"text/markdown": {
|
||||||
"schema": { "type": "string" }
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,21 +435,27 @@
|
|||||||
},
|
},
|
||||||
"/cases/{case_id}/status": {
|
"/cases/{case_id}/status": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": ["cases"],
|
"tags": [
|
||||||
|
"cases"
|
||||||
|
],
|
||||||
"summary": "Set manual case status",
|
"summary": "Set manual case status",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "case_id",
|
"name": "case_id",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": { "type": "string" }
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"required": true,
|
"required": true,
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/CaseStatusRequest" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CaseStatusRequest"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -351,7 +464,9 @@
|
|||||||
"description": "Updated case",
|
"description": "Updated case",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -360,14 +475,18 @@
|
|||||||
},
|
},
|
||||||
"/dlp/evidence": {
|
"/dlp/evidence": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["incidents"],
|
"tags": [
|
||||||
|
"incidents"
|
||||||
|
],
|
||||||
"summary": "DLP evidence list",
|
"summary": "DLP evidence list",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Evidence list",
|
"description": "Evidence list",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -376,14 +495,18 @@
|
|||||||
},
|
},
|
||||||
"/readiness/latest": {
|
"/readiness/latest": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["readiness"],
|
"tags": [
|
||||||
|
"readiness"
|
||||||
|
],
|
||||||
"summary": "Latest readiness bundle status",
|
"summary": "Latest readiness bundle status",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Readiness status",
|
"description": "Readiness status",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -392,14 +515,18 @@
|
|||||||
},
|
},
|
||||||
"/readiness/bundle": {
|
"/readiness/bundle": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["readiness"],
|
"tags": [
|
||||||
|
"readiness"
|
||||||
|
],
|
||||||
"summary": "Readiness bundle artifact list",
|
"summary": "Readiness bundle artifact list",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Readiness bundle",
|
"description": "Readiness bundle",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -408,14 +535,18 @@
|
|||||||
},
|
},
|
||||||
"/readiness/verify": {
|
"/readiness/verify": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["readiness"],
|
"tags": [
|
||||||
|
"readiness"
|
||||||
|
],
|
||||||
"summary": "Verify readiness checksums and signature",
|
"summary": "Verify readiness checksums and signature",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Verification result",
|
"description": "Verification result",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -424,14 +555,18 @@
|
|||||||
},
|
},
|
||||||
"/links": {
|
"/links": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["portal"],
|
"tags": [
|
||||||
|
"portal"
|
||||||
|
],
|
||||||
"summary": "Gateway-relative portal links",
|
"summary": "Gateway-relative portal links",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Link map",
|
"description": "Link map",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -440,13 +575,17 @@
|
|||||||
},
|
},
|
||||||
"/telemetry": {
|
"/telemetry": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": ["telemetry"],
|
"tags": [
|
||||||
|
"telemetry"
|
||||||
|
],
|
||||||
"summary": "Agent telemetry ingest",
|
"summary": "Agent telemetry ingest",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"required": true,
|
"required": true,
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -455,7 +594,9 @@
|
|||||||
"description": "Accepted telemetry",
|
"description": "Accepted telemetry",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": { "$ref": "#/components/schemas/JsonObject" }
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -474,107 +615,207 @@
|
|||||||
},
|
},
|
||||||
"ContractIndex": {
|
"ContractIndex": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["ok", "contract_version", "api_base", "artifacts", "stable_endpoints"],
|
"required": [
|
||||||
|
"ok",
|
||||||
|
"contract_version",
|
||||||
|
"api_base",
|
||||||
|
"artifacts",
|
||||||
|
"stable_endpoints"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"ok": { "type": "boolean" },
|
"ok": {
|
||||||
"contract_version": { "type": "string" },
|
"type": "boolean"
|
||||||
"generated_by": { "type": "string" },
|
},
|
||||||
"api_base": { "type": "string" },
|
"contract_version": {
|
||||||
"compatibility": { "$ref": "#/components/schemas/JsonObject" },
|
"type": "string"
|
||||||
"targets": { "type": "array", "items": { "type": "string" } },
|
},
|
||||||
"artifacts": { "$ref": "#/components/schemas/JsonObject" },
|
"generated_by": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"api_base": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"compatibility": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
},
|
||||||
|
"targets": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"artifacts": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
},
|
||||||
"stable_endpoints": {
|
"stable_endpoints": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": { "$ref": "#/components/schemas/EndpointDescriptor" }
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/EndpointDescriptor"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": true
|
"additionalProperties": true
|
||||||
},
|
},
|
||||||
"EndpointDescriptor": {
|
"EndpointDescriptor": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["method", "path"],
|
"required": [
|
||||||
|
"method",
|
||||||
|
"path"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"method": { "type": "string" },
|
"method": {
|
||||||
"path": { "type": "string" },
|
"type": "string"
|
||||||
"purpose": { "type": "string" }
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"purpose": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": true
|
"additionalProperties": true
|
||||||
},
|
},
|
||||||
"ReportPayload": {
|
"ReportPayload": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["ok"],
|
"required": [
|
||||||
|
"ok"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"ok": { "type": "boolean" },
|
"ok": {
|
||||||
"generated_at_utc": { "type": "string" },
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"generated_at_utc": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"executive_points": {
|
"executive_points": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": { "type": "string" }
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"executive_dashboard": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
},
|
||||||
|
"risk_narrative": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
},
|
||||||
|
"agent_quality": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
},
|
||||||
|
"agent_coverage_sla": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
},
|
},
|
||||||
"executive_dashboard": { "$ref": "#/components/schemas/JsonObject" },
|
|
||||||
"risk_narrative": { "$ref": "#/components/schemas/JsonObject" },
|
|
||||||
"agent_quality": { "$ref": "#/components/schemas/JsonObject" },
|
|
||||||
"agent_coverage_sla": { "$ref": "#/components/schemas/JsonObject" },
|
|
||||||
"business_risk": {
|
"business_risk": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": { "$ref": "#/components/schemas/JsonObject" }
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"risk_incident_candidates": {
|
"risk_incident_candidates": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": { "$ref": "#/components/schemas/JsonObject" }
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"cases": {
|
"cases": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": { "$ref": "#/components/schemas/JsonObject" }
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": true
|
"additionalProperties": true
|
||||||
},
|
},
|
||||||
"IncidentReviewRequest": {
|
"IncidentReviewRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["candidate_id", "status"],
|
"required": [
|
||||||
|
"candidate_id",
|
||||||
|
"status"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"candidate_id": { "type": "string" },
|
"candidate_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["NEW", "IN_REVIEW", "CONFIRMED", "FALSE_POSITIVE", "POSTPONED"]
|
"enum": [
|
||||||
|
"NEW",
|
||||||
|
"IN_REVIEW",
|
||||||
|
"CONFIRMED",
|
||||||
|
"FALSE_POSITIVE",
|
||||||
|
"POSTPONED"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"reviewer": { "type": "string" },
|
"reviewer": {
|
||||||
"comment": { "type": "string" }
|
"type": "string"
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": true
|
"additionalProperties": true
|
||||||
},
|
},
|
||||||
"CreateCaseRequest": {
|
"CreateCaseRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["candidate_id"],
|
"required": [
|
||||||
|
"candidate_id"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"candidate_id": { "type": "string" },
|
"candidate_id": {
|
||||||
"title": { "type": "string" },
|
"type": "string"
|
||||||
"owner": { "type": "string" },
|
},
|
||||||
"summary": { "type": "string" },
|
"title": {
|
||||||
"decision": { "type": "string" }
|
"type": "string"
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"decision": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": true
|
"additionalProperties": true
|
||||||
},
|
},
|
||||||
"CaseStatusRequest": {
|
"CaseStatusRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["status"],
|
"required": [
|
||||||
|
"status"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["OPEN", "IN_PROGRESS", "RESOLVED", "REJECTED", "ARCHIVED"]
|
"enum": [
|
||||||
|
"OPEN",
|
||||||
|
"IN_PROGRESS",
|
||||||
|
"RESOLVED",
|
||||||
|
"REJECTED",
|
||||||
|
"ARCHIVED"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"decision": { "type": "string" }
|
"decision": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": true
|
"additionalProperties": true
|
||||||
},
|
},
|
||||||
"CaseListResponse": {
|
"CaseListResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["ok", "cases"],
|
"required": [
|
||||||
|
"ok",
|
||||||
|
"cases"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"ok": { "type": "boolean" },
|
"ok": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"cases": {
|
"cases": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": { "$ref": "#/components/schemas/JsonObject" }
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": true
|
"additionalProperties": true
|
||||||
|
|||||||
+10
-2
@@ -1,7 +1,12 @@
|
|||||||
export type ISODateTime = string;
|
export type ISODateTime = string;
|
||||||
|
|
||||||
export type RiskLevel = "LOW" | "MEDIUM" | "HIGH" | "CRITICAL" | "UNKNOWN";
|
export type RiskLevel = "LOW" | "MEDIUM" | "HIGH" | "CRITICAL" | "UNKNOWN";
|
||||||
export type ReviewStatus = "NEW" | "IN_REVIEW" | "CONFIRMED" | "FALSE_POSITIVE" | "POSTPONED";
|
export type ReviewStatus =
|
||||||
|
| "NEW"
|
||||||
|
| "IN_REVIEW"
|
||||||
|
| "CONFIRMED"
|
||||||
|
| "FALSE_POSITIVE"
|
||||||
|
| "POSTPONED";
|
||||||
export type CaseStatus = "OPEN" | "IN_PROGRESS" | "RESOLVED" | "REJECTED" | "ARCHIVED";
|
export type CaseStatus = "OPEN" | "IN_PROGRESS" | "RESOLVED" | "REJECTED" | "ARCHIVED";
|
||||||
|
|
||||||
export interface JsonObject {
|
export interface JsonObject {
|
||||||
@@ -179,7 +184,10 @@ export interface DetMirPortalApi {
|
|||||||
createCase(request: CreateCaseRequest): Promise<JsonObject>;
|
createCase(request: CreateCaseRequest): Promise<JsonObject>;
|
||||||
setCaseStatus(caseId: string, request: CaseStatusRequest): Promise<JsonObject>;
|
setCaseStatus(caseId: string, request: CaseStatusRequest): Promise<JsonObject>;
|
||||||
setIncidentReview(request: IncidentReviewRequest): Promise<JsonObject>;
|
setIncidentReview(request: IncidentReviewRequest): Promise<JsonObject>;
|
||||||
getInvestigationPack(candidateId: string, options?: { format?: "json" | "markdown" }): Promise<JsonObject | string>;
|
getInvestigationPack(
|
||||||
|
candidateId: string,
|
||||||
|
options?: { format?: "json" | "markdown" },
|
||||||
|
): Promise<JsonObject | string>;
|
||||||
getDlpEvidence(): Promise<JsonObject>;
|
getDlpEvidence(): Promise<JsonObject>;
|
||||||
getReadinessLatest(): Promise<JsonObject>;
|
getReadinessLatest(): Promise<JsonObject>;
|
||||||
getReadinessBundle(): Promise<JsonObject>;
|
getReadinessBundle(): Promise<JsonObject>;
|
||||||
|
|||||||
@@ -9213,12 +9213,45 @@ mod tests {
|
|||||||
let openapi: Value =
|
let openapi: Value =
|
||||||
serde_json::from_str(API_CONTRACT_OPENAPI).expect("OpenAPI contract must be JSON");
|
serde_json::from_str(API_CONTRACT_OPENAPI).expect("OpenAPI contract must be JSON");
|
||||||
assert_eq!(openapi["openapi"], "3.1.0");
|
assert_eq!(openapi["openapi"], "3.1.0");
|
||||||
assert!(openapi["paths"]["/reports"].is_object());
|
assert!(
|
||||||
|
openapi["info"]["version"]
|
||||||
|
.as_str()
|
||||||
|
.is_some_and(|value| !value.trim().is_empty())
|
||||||
|
);
|
||||||
|
for path in [
|
||||||
|
"/contracts",
|
||||||
|
"/reports",
|
||||||
|
"/incidents",
|
||||||
|
"/cases",
|
||||||
|
"/readiness/latest",
|
||||||
|
] {
|
||||||
|
assert!(
|
||||||
|
openapi["paths"][path].is_object(),
|
||||||
|
"OpenAPI path missing: {path}"
|
||||||
|
);
|
||||||
|
}
|
||||||
assert!(openapi["paths"]["/incident-review"].is_object());
|
assert!(openapi["paths"]["/incident-review"].is_object());
|
||||||
assert!(openapi["paths"]["/cases"].is_object());
|
for required_type in [
|
||||||
assert!(API_CONTRACT_TYPESCRIPT.contains("export interface DetMirPortalApi"));
|
"ContractIndex",
|
||||||
assert!(API_CONTRACT_TYPESCRIPT.contains("ReportsResponse"));
|
"ReportsResponse",
|
||||||
assert!(API_CONTRACT_TYPESCRIPT.contains("IncidentReviewRequest"));
|
"CaseListResponse",
|
||||||
|
"IncidentReviewRequest",
|
||||||
|
"export interface DetMirPortalApi",
|
||||||
|
] {
|
||||||
|
assert!(
|
||||||
|
API_CONTRACT_TYPESCRIPT.contains(required_type),
|
||||||
|
"TypeScript declaration missing {required_type}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (idx, _) in API_CONTRACT_TYPESCRIPT.match_indices("Promise") {
|
||||||
|
assert_eq!(
|
||||||
|
API_CONTRACT_TYPESCRIPT[idx + "Promise".len()..]
|
||||||
|
.chars()
|
||||||
|
.next(),
|
||||||
|
Some('<'),
|
||||||
|
"TypeScript declarations must not contain bare Promise return types"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let summary = api_contract_summary();
|
let summary = api_contract_summary();
|
||||||
assert_eq!(summary["ok"], true);
|
assert_eq!(summary["ok"], true);
|
||||||
|
|||||||
+10
-3
@@ -12,6 +12,7 @@ DPD нужен для безопасной эволюции интерфейса
|
|||||||
- `/dpd/` работает как полный mirror текущего портала;
|
- `/dpd/` работает как полный mirror текущего портала;
|
||||||
- будущий React/Tauri UI сможет использовать те же API-контракты;
|
- будущий React/Tauri UI сможет использовать те же API-контракты;
|
||||||
- новые UI-решения проверяются без cutover и без дублирования бизнес-логики.
|
- новые UI-решения проверяются без cutover и без дублирования бизнес-логики.
|
||||||
|
- будущий UI не парсит HTML текущего портала как источник данных.
|
||||||
|
|
||||||
DPD проксирует:
|
DPD проксирует:
|
||||||
|
|
||||||
@@ -77,9 +78,15 @@ detmir-dpd-portal \
|
|||||||
- `/dpd/api/contracts/openapi.json`;
|
- `/dpd/api/contracts/openapi.json`;
|
||||||
- `/dpd/api/contracts/typescript.d.ts`.
|
- `/dpd/api/contracts/typescript.d.ts`.
|
||||||
|
|
||||||
Правило совместимости: изменения API должны быть additive. Клиенты React/Tauri
|
Правила совместимости:
|
||||||
обязаны игнорировать неизвестные поля и корректно обрабатывать отсутствующие
|
|
||||||
optional-поля.
|
- изменения API должны быть additive;
|
||||||
|
- клиенты React/Tauri обязаны игнорировать неизвестные поля;
|
||||||
|
- optional-поля могут отсутствовать;
|
||||||
|
- `null` не должен ломать UI;
|
||||||
|
- breaking changes требуют version bump контракта и отдельного architecture
|
||||||
|
decision;
|
||||||
|
- будущий React/Tauri UI не должен парсить HTML текущего портала.
|
||||||
|
|
||||||
## Проверка симметрии
|
## Проверка симметрии
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
React/Tauri-интерфейса без переписывания backend-логики.
|
React/Tauri-интерфейса без переписывания backend-логики.
|
||||||
|
|
||||||
Текущий HTML-портал остаётся рабочим и основным. Новый UI должен подключаться к
|
Текущий HTML-портал остаётся рабочим и основным. Новый UI должен подключаться к
|
||||||
тем же endpoint-ам через documented contract.
|
тем же endpoint-ам через documented contract и не должен парсить HTML.
|
||||||
|
|
||||||
## Контрактные endpoint-ы
|
## Контрактные endpoint-ы
|
||||||
|
|
||||||
@@ -30,6 +30,12 @@ DPD mirror:
|
|||||||
- Клиенты обязаны корректно обрабатывать отсутствующие optional-поля.
|
- Клиенты обязаны корректно обрабатывать отсутствующие optional-поля.
|
||||||
- Поля с `null` не должны ломать UI.
|
- Поля с `null` не должны ломать UI.
|
||||||
- Ошибки API должны отображаться пользователю на русском языке.
|
- Ошибки API должны отображаться пользователю на русском языке.
|
||||||
|
- Breaking changes требуют отдельного version bump контракта и architecture
|
||||||
|
decision.
|
||||||
|
- Будущий React/Tauri UI не должен парсить HTML-страницы, CSS или встроенный
|
||||||
|
JavaScript текущего портала как источник данных.
|
||||||
|
- Бизнес-логика остаётся на стороне Rust backend/API; frontend только
|
||||||
|
отображает и отправляет пользовательские действия через контрактные endpoint-ы.
|
||||||
|
|
||||||
## Минимальный набор для будущего React/Tauri UI
|
## Минимальный набор для будущего React/Tauri UI
|
||||||
|
|
||||||
@@ -52,6 +58,7 @@ DPD mirror:
|
|||||||
- Маршрут `/portal/` остаётся стабильным.
|
- Маршрут `/portal/` остаётся стабильным.
|
||||||
- DPD `/dpd/` остаётся параллельным mirror.
|
- DPD `/dpd/` остаётся параллельным mirror.
|
||||||
- Backend-расчёты, JSON-хранилища и workflow не дублируются во frontend.
|
- Backend-расчёты, JSON-хранилища и workflow не дублируются во frontend.
|
||||||
|
- Публичные JSON-поля не переименовываются без новой версии контракта.
|
||||||
|
|
||||||
## Проверка
|
## Проверка
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
```text
|
```text
|
||||||
Backend/API: Rust
|
Backend/API: Rust
|
||||||
Agent: Rust
|
Agent: Rust
|
||||||
Current Portal: Rust server-rendered HTML + HTMX-ready static UI
|
Current Portal: Rust server-rendered HTML + HTMX
|
||||||
Future Enterprise UI: React + TypeScript
|
Future Enterprise UI: React + TypeScript
|
||||||
Future Desktop Forensics: Tauri + React + Rust core
|
Future Desktop Forensics: Tauri + React + Rust core
|
||||||
Dioxus: out of scope / не рассматривается
|
Dioxus: out of scope / не рассматривается
|
||||||
@@ -15,14 +15,15 @@ Dioxus: out of scope / не рассматривается
|
|||||||
|
|
||||||
## Правила
|
## Правила
|
||||||
|
|
||||||
- Текущий HTML/HTMX-ready портал остаётся основным pilot/production
|
- Текущий HTML/HTMX портал остаётся основным pilot/production интерфейсом.
|
||||||
интерфейсом.
|
|
||||||
- JSON API является контрактным слоем для будущих UI.
|
- JSON API является контрактным слоем для будущих UI.
|
||||||
- Бизнес-логика не должна зависеть от HTML.
|
- Бизнес-логика не должна зависеть от HTML.
|
||||||
- Будущий React/Tauri UI не должен ломать текущий портал.
|
- Будущий React/Tauri UI не должен ломать текущий портал.
|
||||||
- Agent и backend остаются Rust-first.
|
- Agent и backend остаются Rust-first.
|
||||||
- Новые UI-фреймворки не добавлять без отдельного architecture decision.
|
- Новые UI-фреймворки не добавлять без отдельного architecture decision.
|
||||||
- Dioxus не добавлять и не рассматривать.
|
- Dioxus не добавлять и не рассматривать.
|
||||||
|
- Будущий React/Tauri UI не должен парсить HTML текущего портала как источник
|
||||||
|
данных.
|
||||||
|
|
||||||
## Практическое следствие
|
## Практическое следствие
|
||||||
|
|
||||||
@@ -32,6 +33,8 @@ Dioxus: out of scope / не рассматривается
|
|||||||
2. Зафиксировать стабильные JSON API-контракты.
|
2. Зафиксировать стабильные JSON API-контракты.
|
||||||
3. Поддерживать DPD `/dpd/` как параллельный mirror для проверки совместимости.
|
3. Поддерживать DPD `/dpd/` как параллельный mirror для проверки совместимости.
|
||||||
4. Готовить будущий React/Tauri UI только поверх опубликованных контрактов.
|
4. Готовить будущий React/Tauri UI только поверх опубликованных контрактов.
|
||||||
|
5. Любые breaking changes проводить только через version bump контракта и
|
||||||
|
отдельное architecture decision.
|
||||||
|
|
||||||
## Контрактный слой
|
## Контрактный слой
|
||||||
|
|
||||||
@@ -48,5 +51,5 @@ DPD mirror endpoints:
|
|||||||
- `/dpd/api/contracts/typescript.d.ts`.
|
- `/dpd/api/contracts/typescript.d.ts`.
|
||||||
|
|
||||||
Правило совместимости: изменения API должны быть additive. Клиенты обязаны
|
Правило совместимости: изменения API должны быть additive. Клиенты обязаны
|
||||||
игнорировать неизвестные поля и корректно обрабатывать отсутствующие optional
|
игнорировать неизвестные поля, корректно обрабатывать отсутствующие optional
|
||||||
поля.
|
поля и не падать на `null`.
|
||||||
|
|||||||
Reference in New Issue
Block a user