From cfb70e6679aee843492280c3fea134e0846230cf Mon Sep 17 00:00:00 2001 From: Shaurya K Sharma Date: Mon, 27 Jul 2026 20:42:27 +0100 Subject: [PATCH 1/4] feat: generate frontend API contracts from OpenAPI --- apps/backend/app/schemas/ai.py | 4 +- apps/frontend/README.md | 8 + apps/frontend/package-lock.json | 267 + apps/frontend/package.json | 2 + .../explorer/components/CodePreview.tsx | 2 +- .../explorer/components/FileTreeView.tsx | 2 +- .../src/shared/services/api/architecture.ts | 14 +- .../src/shared/services/api/dependencies.ts | 4 +- .../src/shared/services/api/errors.test.ts | 7 + .../src/shared/services/api/errors.ts | 22 +- .../services/api/generated-contract.test.ts | 41 + .../src/shared/services/api/generated.ts | 6839 +++++++++++++++++ .../frontend/src/shared/services/api/index.ts | 3 +- .../src/shared/services/api/insights.ts | 13 +- .../src/shared/services/api/repositories.ts | 17 +- .../src/shared/services/api/review.ts | 4 +- .../frontend/src/shared/services/api/types.ts | 575 +- apps/frontend/src/shared/services/backend.ts | 10 +- .../frontend/src/shared/types/architecture.ts | 139 +- apps/frontend/src/shared/types/index.ts | 46 +- apps/frontend/src/shared/types/insights.ts | 74 +- apps/frontend/src/shared/types/review.ts | 128 +- package.json | 1 + scripts/generate-api-contract.mjs | 79 + 24 files changed, 7453 insertions(+), 848 deletions(-) create mode 100644 apps/frontend/src/shared/services/api/generated-contract.test.ts create mode 100644 apps/frontend/src/shared/services/api/generated.ts create mode 100644 scripts/generate-api-contract.mjs diff --git a/apps/backend/app/schemas/ai.py b/apps/backend/app/schemas/ai.py index daad490..bb74db3 100644 --- a/apps/backend/app/schemas/ai.py +++ b/apps/backend/app/schemas/ai.py @@ -1,6 +1,8 @@ from datetime import UTC, datetime from typing import Literal +from pydantic import Field + from app.schemas.base import CamelModel AiProvider = Literal["openai", "anthropic", "gemini", "openrouter", "ollama"] @@ -65,4 +67,4 @@ class AiProviderTestRequest(CamelModel): class AiProviderTestResponse(CamelModel): ok: bool message: str - checked_at: datetime = datetime.now(UTC) + checked_at: datetime = Field(default_factory=lambda: datetime.now(UTC)) diff --git a/apps/frontend/README.md b/apps/frontend/README.md index d04707e..6aa2d1d 100644 --- a/apps/frontend/README.md +++ b/apps/frontend/README.md @@ -37,9 +37,17 @@ npm --prefix apps/frontend run lint # eslint npm --prefix apps/frontend run test # vitest, with coverage npm --prefix apps/frontend run test:watch npm --prefix apps/frontend run build # tsc -b && vite build +npm run generate:api-contract # regenerate DTOs from FastAPI OpenAPI +npm --prefix apps/frontend run generate:api-contract -- --check # fail on drift npm run test:prototype # disposable fixtures + Playwright journeys ``` +The generated contract lives at +`src/shared/services/api/generated.ts` and must not be edited directly. The +generator imports backend module `app.main:app` from `apps/backend`, strips only import-time dynamic +datetime defaults, and runs the pinned `openapi-typescript@7.13.0` tool. The CI +API Contract Drift job installs both app lockfiles and runs the check command. + From the repository root: `npm run dev:frontend`, `npm run lint:frontend`, `npm run build:frontend`, and `npm run test:prototype`. There is no root alias for the Vitest suite — run `npm --prefix apps/frontend run test`. diff --git a/apps/frontend/package-lock.json b/apps/frontend/package-lock.json index 4dfe440..1d1c2b5 100644 --- a/apps/frontend/package-lock.json +++ b/apps/frontend/package-lock.json @@ -40,6 +40,7 @@ "eslint-plugin-react-refresh": "^0.5.3", "globals": "^17.8.0", "jsdom": "^29.1.1", + "openapi-typescript": "7.13.0", "postcss": "^8.5.23", "tailwindcss": "^3.4.10", "typescript": "^5.5.4", @@ -941,6 +942,72 @@ "node": ">=20" } }, + "node_modules/@redocly/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js-replace": "^1.0.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@redocly/ajv/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/config": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.0.tgz", + "integrity": "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/openapi-core": { + "version": "1.34.17", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.17.tgz", + "integrity": "sha512-wsV2keCt6B806XpSdezbWZ9aFJYf14YVh+XQf0ESt7M90yqVuxH9//PxvtC70sgj9OCkRM3nRaLfu4MsGQZRig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/ajv": "8.11.2", + "@redocly/config": "0.22.0", + "colorette": "1.4.0", + "https-proxy-agent": "7.0.6", + "js-levenshtein": "1.1.6", + "js-yaml": "4.2.0", + "minimatch": "5.1.9", + "pluralize": "8.0.0", + "yaml-ast-parser": "0.0.43" + }, + "engines": { + "node": ">=18.17.0", + "npm": ">=9.5.0" + } + }, + "node_modules/@redocly/openapi-core/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", @@ -1937,6 +2004,16 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", @@ -1954,6 +2031,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2004,6 +2091,13 @@ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "license": "MIT" }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -2243,6 +2337,13 @@ "node": ">=18" } }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "dev": true, + "license": "MIT" + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -2294,6 +2395,13 @@ "node": ">=6" } }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true, + "license": "MIT" + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -3137,6 +3245,20 @@ "integrity": "sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==", "license": "MIT" }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3167,6 +3289,19 @@ "node": ">=8" } }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -3286,12 +3421,45 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsdom": { "version": "29.1.1", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", @@ -3990,6 +4158,40 @@ "node": ">=12.20.0" } }, + "node_modules/openapi-typescript": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.13.0.tgz", + "integrity": "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/openapi-core": "^1.34.6", + "ansi-colors": "^4.1.3", + "change-case": "^5.4.4", + "parse-json": "^8.3.0", + "supports-color": "^10.2.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "openapi-typescript": "bin/cli.js" + }, + "peerDependencies": { + "typescript": "^5.x" + } + }, + "node_modules/openapi-typescript/node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4040,6 +4242,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", @@ -4169,6 +4389,16 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/postcss": { "version": "8.5.23", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", @@ -5026,6 +5256,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -5115,6 +5358,13 @@ "punycode": "^2.1.0" } }, + "node_modules/uri-js-replace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz", + "integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==", + "dev": true, + "license": "MIT" + }, "node_modules/use-sync-external-store": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", @@ -5439,6 +5689,23 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/apps/frontend/package.json b/apps/frontend/package.json index c613f2b..399a27d 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -6,6 +6,7 @@ "scripts": { "dev": "vite", "build": "tsc -b && vite build", + "generate:api-contract": "node ../../scripts/generate-api-contract.mjs", "preview": "vite preview", "lint": "eslint .", "test": "vitest run --coverage", @@ -45,6 +46,7 @@ "eslint-plugin-react-refresh": "^0.5.3", "globals": "^17.8.0", "jsdom": "^29.1.1", + "openapi-typescript": "7.13.0", "postcss": "^8.5.23", "tailwindcss": "^3.4.10", "typescript": "^5.5.4", diff --git a/apps/frontend/src/features/explorer/components/CodePreview.tsx b/apps/frontend/src/features/explorer/components/CodePreview.tsx index d8e1818..caa5e2b 100644 --- a/apps/frontend/src/features/explorer/components/CodePreview.tsx +++ b/apps/frontend/src/features/explorer/components/CodePreview.tsx @@ -108,7 +108,7 @@ export function CodePreview({ node, repositoryId, citation }: CodePreviewProps) setContent(file.content); setIsBinary(file.isBinary); setIsImage(file.isImage); - setMediaType(file.mediaType); + setMediaType(file.mediaType ?? null); setTruncated(file.truncated); }) .catch((caught) => { diff --git a/apps/frontend/src/features/explorer/components/FileTreeView.tsx b/apps/frontend/src/features/explorer/components/FileTreeView.tsx index 34407a9..4ecf4c9 100644 --- a/apps/frontend/src/features/explorer/components/FileTreeView.tsx +++ b/apps/frontend/src/features/explorer/components/FileTreeView.tsx @@ -151,7 +151,7 @@ function TreeItem({ node, depth, searchQuery }: TreeItemProps) { )}> {node.name} - {node.size !== undefined && node.type === 'file' && ( + {node.size != null && node.type === 'file' && ( {formatSize(node.size)} diff --git a/apps/frontend/src/shared/services/api/architecture.ts b/apps/frontend/src/shared/services/api/architecture.ts index 40efa4d..8f21c6e 100644 --- a/apps/frontend/src/shared/services/api/architecture.ts +++ b/apps/frontend/src/shared/services/api/architecture.ts @@ -1,5 +1,6 @@ import { api } from './client'; import type { RequestConfig } from './client'; +import type { components } from './generated'; import type { ArchitectureResponse, AuthenticationExplanationResponse, @@ -9,14 +10,23 @@ import type { export const architectureService = { getArchitecture(repositoryId: string, config?: RequestConfig): Promise { - return api.get(`/analysis/${repositoryId}/architecture`, config); + return api.get(`/analysis/${repositoryId}/architecture`, config) + .then((response) => ({ ...response, diagnostics: response.diagnostics ?? [] })); }, getAuthenticationExplanation( repositoryId: string, config?: RequestConfig, ): Promise { - return api.get(`/analysis/${repositoryId}/architecture/authentication`, config); + return api.get( + `/analysis/${repositoryId}/architecture/authentication`, config, + ).then((response) => ({ + ...response, + claims: response.claims ?? [], + relationships: response.relationships ?? [], + chains: response.chains ?? [], + diagnostics: response.diagnostics ?? [], + })); }, getRevisionManifest(repositoryId: string, config?: RequestConfig): Promise { diff --git a/apps/frontend/src/shared/services/api/dependencies.ts b/apps/frontend/src/shared/services/api/dependencies.ts index 4e9fb97..48d1321 100644 --- a/apps/frontend/src/shared/services/api/dependencies.ts +++ b/apps/frontend/src/shared/services/api/dependencies.ts @@ -1,9 +1,11 @@ import { api } from './client'; import type { RequestConfig } from './client'; +import type { components } from './generated'; import type { DependencyGraphResponse } from './types'; export const dependencyService = { getDependencyGraph(repositoryId: string, config?: RequestConfig): Promise { - return api.get(`/analysis/${repositoryId}/dependencies`, config); + return api.get(`/analysis/${repositoryId}/dependencies`, config) + .then((response) => ({ ...response, diagnostics: response.diagnostics ?? [] })); }, }; diff --git a/apps/frontend/src/shared/services/api/errors.test.ts b/apps/frontend/src/shared/services/api/errors.test.ts index 837769c..3310bfb 100644 --- a/apps/frontend/src/shared/services/api/errors.test.ts +++ b/apps/frontend/src/shared/services/api/errors.test.ts @@ -5,6 +5,7 @@ import { NetworkError, TimeoutError, isApiError, + isErrorResponse, isNetworkError, isTimeoutError, parseRetryAfter, @@ -96,4 +97,10 @@ describe('type guards', () => { expect(isApiError(undefined)).toBe(false); expect(isNetworkError('nope')).toBe(false); }); + + it('validates the generated error envelope at the untrusted response boundary', () => { + expect(isErrorResponse({ code: 'unauthorized', message: 'Sign in required' })).toBe(true); + expect(isErrorResponse({ code: 'unauthorized' })).toBe(false); + expect(isErrorResponse(null)).toBe(false); + }); }); diff --git a/apps/frontend/src/shared/services/api/errors.ts b/apps/frontend/src/shared/services/api/errors.ts index c1781c2..9723a76 100644 --- a/apps/frontend/src/shared/services/api/errors.ts +++ b/apps/frontend/src/shared/services/api/errors.ts @@ -1,3 +1,7 @@ +import type { components } from './generated'; + +export type ApiErrorResponse = components['schemas']['ErrorResponse']; + export class ApiError extends Error { constructor( public status: number, @@ -145,8 +149,8 @@ export function getErrorDetail(error: unknown): { message: string; details: stri } function getBackendMessage(body: unknown): string | null { - if (!body || typeof body !== 'object') return null; - const maybeMessage = (body as { message?: unknown }).message; + if (!isErrorResponse(body)) return null; + const maybeMessage = body.message; return typeof maybeMessage === 'string' && maybeMessage.trim() ? maybeMessage : null; } @@ -172,7 +176,17 @@ function getBackendDetails(body: unknown): string[] { } function getBackendRequestId(body: unknown): string | null { - if (!body || typeof body !== 'object') return null; - const maybeRequestId = (body as { request_id?: unknown }).request_id; + if (!isErrorResponse(body)) return null; + const maybeRequestId = body.request_id; return typeof maybeRequestId === 'string' && maybeRequestId.trim() ? maybeRequestId : null; } + +/** Runtime boundary for untrusted backend error responses. */ +export function isErrorResponse(body: unknown): body is ApiErrorResponse { + return Boolean( + body && + typeof body === 'object' && + typeof (body as { code?: unknown }).code === 'string' && + typeof (body as { message?: unknown }).message === 'string', + ); +} diff --git a/apps/frontend/src/shared/services/api/generated-contract.test.ts b/apps/frontend/src/shared/services/api/generated-contract.test.ts new file mode 100644 index 0000000..cabb875 --- /dev/null +++ b/apps/frontend/src/shared/services/api/generated-contract.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from 'vitest'; +import type { components, paths } from './generated'; +import generated from './generated.ts?raw'; + +// These type aliases make the proof-workflow fields compile against the +// generated document, so a schema rename/removal fails the frontend build. +type ProofWorkflowContract = Pick< + components['schemas']['RiSnapshotMetadataResponse'], + 'schemaVersion' | 'snapshotId' | 'revisionValue' | 'canonicalGraphHash' | 'sealedAt' +> & Pick< + components['schemas']['EvidenceSourceResponse'], + 'factId' | 'path' | 'startLine' | 'endLine' | 'status' | 'content' +> & Pick< + components['schemas']['ArchitectureDiagnostic'], + 'code' | 'severity' | 'message' | 'path' | 'startLine' | 'endLine' +>; + +type ErrorEnvelope = Pick; +type ReviewOperation = paths['/analysis/{repository_id}/review']['get']; +type ReviewResponse = NonNullable['application/json']; + +// Exported solely so the compiler checks the selected generated component +// fields and the response operation above under strict TypeScript settings. +export type GeneratedContractCoverage = ProofWorkflowContract & ErrorEnvelope & ReviewResponse; + +describe('generated frontend API contract', () => { + it('contains proof-workflow, diagnostics, and error-envelope schemas', () => { + expect(generated).toContain('RiSnapshotMetadataResponse'); + expect(generated).toContain('EvidenceSourceResponse'); + expect(generated).toContain('ArchitectureDiagnostic'); + expect(generated).toContain('EngineeringReviewResponse'); + expect(generated).toContain('ErrorResponse'); + expect(generated).toContain('canonicalGraphHash'); + expect(generated).toContain('request_id'); + }); + + it('contains no environment URLs or embedded secret-like values', () => { + expect(generated).not.toMatch(/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|host\.docker\.internal)/i); + expect(generated).not.toMatch(/(?:api[_-]?key|password|secret|token)\s*[:=]\s*["'`][^"'`]+["'`]/i); + }); +}); diff --git a/apps/frontend/src/shared/services/api/generated.ts b/apps/frontend/src/shared/services/api/generated.ts new file mode 100644 index 0000000..9c6e48b --- /dev/null +++ b/apps/frontend/src/shared/services/api/generated.ts @@ -0,0 +1,6839 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +// Source: apps/backend/app.main:app.openapi() +// Generator: openapi-typescript@7.13.0 + +export interface paths { + "/ai/config": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Ai Config */ + get: operations["get_ai_config_ai_config_get"]; + /** Save Ai Config */ + put: operations["save_ai_config_ai_config_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ai/query": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Query Ai */ + post: operations["query_ai_ai_query_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ai/test": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Test Ai Config */ + post: operations["test_ai_config_ai_test_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/analysis/{repository_id}/architecture": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Architecture */ + get: operations["get_architecture_analysis__repository_id__architecture_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/analysis/{repository_id}/architecture/authentication": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Authentication Explanation */ + get: operations["get_authentication_explanation_analysis__repository_id__architecture_authentication_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/analysis/{repository_id}/cancel": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Cancel Analysis */ + post: operations["cancel_analysis_analysis__repository_id__cancel_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/analysis/{repository_id}/dependencies": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Dependencies */ + get: operations["get_dependencies_analysis__repository_id__dependencies_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/analysis/{repository_id}/evidence": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Evidence Source */ + get: operations["get_evidence_source_analysis__repository_id__evidence_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/analysis/{repository_id}/insights": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Insights */ + get: operations["get_insights_analysis__repository_id__insights_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/analysis/{repository_id}/review": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Review */ + get: operations["get_review_analysis__repository_id__review_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/analysis/{repository_id}/revision-manifest": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Revision Manifest */ + get: operations["get_revision_manifest_analysis__repository_id__revision_manifest_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/analysis/{repository_id}/revision-manifest/verify": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Verify Revision Manifest */ + post: operations["verify_revision_manifest_analysis__repository_id__revision_manifest_verify_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/analysis/{repository_id}/start": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Start Analysis */ + post: operations["start_analysis_analysis__repository_id__start_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/analysis/{repository_id}/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Analysis Status */ + get: operations["get_analysis_status_analysis__repository_id__status_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/login": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Login */ + post: operations["login_auth_login_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/logout": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Logout */ + post: operations["logout_auth_logout_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/me": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Me */ + get: operations["me_auth_me_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/refresh": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Refresh */ + post: operations["refresh_auth_refresh_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/register": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Register */ + post: operations["register_auth_register_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/documentation/generate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Generate Documentation */ + post: operations["generate_documentation_documentation_generate_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/export": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Export Report */ + post: operations["export_report_export_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Health */ + get: operations["health_health_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/intelligence/v1/snapshots/{snapshot_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Snapshot */ + get: operations["get_snapshot_intelligence_v1_snapshots__snapshot_id__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/intelligence/v1/snapshots/{snapshot_id}/assertions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Assertions */ + get: operations["list_assertions_intelligence_v1_snapshots__snapshot_id__assertions_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/intelligence/v1/snapshots/{snapshot_id}/evidence": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Evidence */ + get: operations["list_evidence_intelligence_v1_snapshots__snapshot_id__evidence_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/intelligence/v1/snapshots/{snapshot_id}/neighbours": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Neighbours */ + get: operations["list_neighbours_intelligence_v1_snapshots__snapshot_id__neighbours_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/intelligence/v1/snapshots/{snapshot_id}/paths": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Paths */ + get: operations["list_paths_intelligence_v1_snapshots__snapshot_id__paths_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/intelligence/v1/snapshots/{snapshot_id}/references": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List References */ + get: operations["list_references_intelligence_v1_snapshots__snapshot_id__references_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/intelligence/v1/snapshots/{snapshot_id}/symbols": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Symbols */ + get: operations["list_symbols_intelligence_v1_snapshots__snapshot_id__symbols_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/metrics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Metrics */ + get: operations["metrics_metrics_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ready": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Readiness */ + get: operations["readiness_ready_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/repositories": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Repositories */ + get: operations["list_repositories_repositories_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/repositories/{repository_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Repository */ + get: operations["get_repository_repositories__repository_id__get"]; + put?: never; + post?: never; + /** Delete Repository */ + delete: operations["delete_repository_repositories__repository_id__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/repositories/{repository_id}/file": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Repository File */ + get: operations["get_repository_file_repositories__repository_id__file_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/repositories/github": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Import Github Repository */ + post: operations["import_github_repository_repositories_github_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/repositories/upload": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Upload Repository */ + post: operations["upload_repository_repositories_upload_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + /** AiCitation */ + AiCitation: { + /** Content */ + content: string; + /** + * Endline + * @default 1 + */ + endLine: number; + /** File */ + file: string; + /** + * Startline + * @default 1 + */ + startLine: number; + }; + /** AiMessage */ + AiMessage: { + /** Citations */ + citations?: components["schemas"]["AiCitation"][] | null; + /** Content */ + content: string; + /** + * Role + * @enum {string} + */ + role: "user" | "assistant"; + /** + * Timestamp + * Format: date-time + */ + timestamp: string; + }; + /** AiProviderConfig */ + AiProviderConfig: { + /** Apikey */ + apiKey?: string | null; + /** Baseurl */ + baseUrl?: string | null; + /** Model */ + model?: string | null; + /** + * Provider + * @enum {string} + */ + provider: "openai" | "anthropic" | "gemini" | "openrouter" | "ollama"; + }; + /** AiProviderPublicConfig */ + AiProviderPublicConfig: { + /** Apikeylast4 */ + apiKeyLast4?: string | null; + /** Baseurl */ + baseUrl?: string | null; + /** + * Hasapikey + * @default false + */ + hasApiKey: boolean; + /** Model */ + model?: string | null; + /** Provider */ + provider?: ("openai" | "anthropic" | "gemini" | "openrouter" | "ollama") | null; + }; + /** AiProviderTestRequest */ + AiProviderTestRequest: { + /** Apikey */ + apiKey?: string | null; + /** Baseurl */ + baseUrl?: string | null; + /** Model */ + model?: string | null; + /** Provider */ + provider?: ("openai" | "anthropic" | "gemini" | "openrouter" | "ollama") | null; + }; + /** AiProviderTestResponse */ + AiProviderTestResponse: { + /** + * Checkedat + * Format: date-time + */ + checkedAt?: string; + /** Message */ + message: string; + /** Ok */ + ok: boolean; + }; + /** AiQueryContext */ + AiQueryContext: { + /** Conversationhistory */ + conversationHistory?: components["schemas"]["AiMessage"][] | null; + /** Selectedfile */ + selectedFile?: string | null; + /** Selectednodeid */ + selectedNodeId?: string | null; + }; + /** AiQueryRequest */ + AiQueryRequest: { + context?: components["schemas"]["AiQueryContext"] | null; + /** Query */ + query: string; + /** Repositoryid */ + repositoryId: string; + }; + /** AiQueryResponse */ + AiQueryResponse: { + message: components["schemas"]["AiMessage"]; + /** + * Suggestions + * @default [] + */ + suggestions: string[]; + }; + /** AnalysisStartResponse */ + AnalysisStartResponse: { + /** Jobid */ + jobId?: string | null; + /** Repositoryid */ + repositoryId: string; + /** + * Status + * @enum {string} + */ + status: "queued" | "running" | "completed" | "failed" | "cancelled"; + }; + /** AnalysisStatusResponse */ + AnalysisStatusResponse: { + /** Completedat */ + completedAt?: string | null; + /** Error */ + error?: string | null; + /** Jobid */ + jobId?: string | null; + /** Progress */ + progress: number; + /** Repositoryid */ + repositoryId: string; + /** Stage */ + stage?: ("uploading" | "extracting" | "reading-structure" | "detecting-languages" | "detecting-framework" | "building-file-tree" | "extracting-modules" | "building-dependency-graph" | "preparing-architecture" | "completed") | null; + /** Startedat */ + startedAt?: string | null; + /** + * Status + * @enum {string} + */ + status: "queued" | "running" | "completed" | "failed" | "cancelled"; + }; + /** ArchEdge */ + ArchEdge: { + /** Evidence */ + evidence: components["schemas"]["ArchEvidence"][]; + /** Id */ + id: string; + /** Label */ + label?: string | null; + /** Predicate */ + predicate: string; + /** Source */ + source: string; + /** Target */ + target: string; + /** + * Truthclass + * @enum {string} + */ + truthClass: "resolved" | "inferred"; + /** + * Type + * @enum {string} + */ + type: "dependency" | "import" | "api-call" | "data-flow" | "event" | "reads" | "writes" | "calls" | "config-usage"; + }; + /** ArchEvidence */ + ArchEvidence: { + /** Endline */ + endLine: number; + /** Factid */ + factId: string; + /** Path */ + path: string; + /** Snapshotid */ + snapshotId: string; + /** Startline */ + startLine: number; + }; + /** ArchitectureDiagnostic */ + ArchitectureDiagnostic: { + /** Category */ + category: string; + /** Code */ + code: string; + /** Details */ + details?: { + [key: string]: unknown; + } | null; + /** Endline */ + endLine?: number | null; + /** Message */ + message: string; + /** Nodeids */ + nodeIds?: string[] | null; + /** Objectkey */ + objectKey?: string | null; + /** Path */ + path?: string | null; + /** + * Severity + * @enum {string} + */ + severity: "fatal" | "error" | "warning" | "info"; + /** Startline */ + startLine?: number | null; + /** Subjectkey */ + subjectKey?: string | null; + }; + /** ArchitectureResponse */ + ArchitectureResponse: { + /** Architecturetype */ + architectureType: string; + /** Detectedlayers */ + detectedLayers: components["schemas"]["ArchLayer"][]; + /** Diagnostics */ + diagnostics?: components["schemas"]["ArchitectureDiagnostic"][]; + /** Edges */ + edges: components["schemas"]["ArchEdge"][]; + /** Modules */ + modules: components["schemas"]["ArchModule"][]; + /** Nodes */ + nodes: components["schemas"]["ArchNode"][]; + /** Relationshipsnapshotid */ + relationshipSnapshotId?: string | null; + /** Repositoryid */ + repositoryId: string; + /** Repositoryname */ + repositoryName: string; + /** Requestflow */ + requestFlow: components["schemas"]["RequestFlowStep"][]; + summary: components["schemas"]["ArchitectureSummary"]; + }; + /** ArchitectureSummary */ + ArchitectureSummary: { + /** Architecturepattern */ + architecturePattern: string; + /** Entrypoint */ + entryPoint: string; + /** Framework */ + framework: string; + /** Language */ + language: string; + /** Totalmodules */ + totalModules: number; + /** Totalnodes */ + totalNodes: number; + }; + /** ArchLayer */ + ArchLayer: { + /** Id */ + id: string; + /** Name */ + name: string; + /** Nodes */ + nodes: string[]; + /** Order */ + order: number; + }; + /** ArchModule */ + ArchModule: { + /** Description */ + description: string; + /** Filecount */ + fileCount: number; + /** Id */ + id: string; + /** Layer */ + layer: string; + /** Name */ + name: string; + /** Nodeids */ + nodeIds: string[]; + }; + /** ArchNode */ + ArchNode: { + /** Dependencies */ + dependencies: string[]; + /** Dependents */ + dependents: string[]; + /** Description */ + description: string; + /** + * Estimatedcomplexity + * @enum {string} + */ + estimatedComplexity: "low" | "medium" | "high"; + /** Estimatedlines */ + estimatedLines: number; + /** Files */ + files: string[]; + /** Id */ + id: string; + /** Layer */ + layer: string; + /** Name */ + name: string; + /** Parentmodule */ + parentModule?: string | null; + /** + * Relationshipstate + * @default not-extracted + * @enum {string} + */ + relationshipState: "connected" | "no-observed-relationships" | "unresolved" | "not-extracted"; + /** Responsibilities */ + responsibilities: string[]; + /** Tags */ + tags: string[]; + /** + * Type + * @enum {string} + */ + type: "frontend" | "backend" | "controller" | "route" | "service" | "repository" | "database" | "configuration" | "authentication" | "middleware" | "utilities" | "models" | "external-api" | "shared-library" | "environment" | "queue" | "cache"; + }; + /** + * AuthChain + * @description One ordered, evidence-backed path from a route to everything it reaches. + * + * ``hops`` is ordered route -> handler -> guard -> (service/model/dependency + * calls, in traversal order) so a consumer can render a single readable + * chain instead of reconstructing one from the flat ``relationships`` list. + */ + AuthChain: { + /** Hops */ + hops: components["schemas"]["AuthRelationship"][]; + /** Route */ + route: string; + }; + /** AuthClaim */ + AuthClaim: { + /** + * Confidence + * @enum {string} + */ + confidence: "observed" | "heuristic"; + /** Evidence */ + evidence: components["schemas"]["AuthEvidenceRef"][]; + /** + * Kind + * @enum {string} + */ + kind: "route" | "middleware" | "service" | "model" | "dependency"; + /** Name */ + name: string; + }; + /** AuthenticationDiagnostic */ + AuthenticationDiagnostic: { + /** Category */ + category: string; + /** Code */ + code: string; + /** Endline */ + endLine?: number | null; + /** Message */ + message: string; + /** Path */ + path?: string | null; + /** + * Severity + * @enum {string} + */ + severity: "fatal" | "error" | "warning" | "info"; + /** Startline */ + startLine?: number | null; + }; + /** AuthenticationExplanationResponse */ + AuthenticationExplanationResponse: { + /** Chains */ + chains?: components["schemas"]["AuthChain"][]; + /** Claims */ + claims?: components["schemas"]["AuthClaim"][]; + /** Diagnostics */ + diagnostics?: components["schemas"]["AuthenticationDiagnostic"][]; + /** Relationships */ + relationships?: components["schemas"]["AuthRelationship"][]; + /** Repositoryid */ + repositoryId: string; + /** Repositoryname */ + repositoryName: string; + /** Revisionkind */ + revisionKind: string | null; + /** Revisionvalue */ + revisionValue: string | null; + /** + * Schemaversion + * @constant + */ + schemaVersion: "auth-explanation.v1"; + /** Snapshotid */ + snapshotId: string | null; + /** + * Status + * @enum {string} + */ + status: "ready" | "missing_snapshot"; + /** Summary */ + summary: string; + }; + /** AuthEvidenceRef */ + AuthEvidenceRef: { + /** Endline */ + endLine: number; + /** Factid */ + factId: string; + /** Path */ + path: string; + /** Snapshotid */ + snapshotId: string; + /** Startline */ + startLine: number; + }; + /** AuthRelationship */ + AuthRelationship: { + /** Evidence */ + evidence: components["schemas"]["AuthEvidenceRef"][]; + /** Object */ + object: string; + /** + * Objectkind + * @enum {string} + */ + objectKind: "route" | "handler" | "middleware" | "service" | "model" | "dependency"; + /** Predicate */ + predicate: string; + /** Subject */ + subject: string; + /** + * Subjectkind + * @enum {string} + */ + subjectKind: "route" | "handler" | "middleware" | "service" | "model" | "dependency"; + }; + /** AuthResponse */ + AuthResponse: { + /** Accesstoken */ + accessToken: string; + /** + * Tokentype + * @default bearer + * @constant + */ + tokenType: "bearer"; + user: components["schemas"]["UserResponse"]; + }; + /** Body_upload_repository_repositories_upload_post */ + Body_upload_repository_repositories_upload_post: { + /** File */ + file: string; + }; + /** ChangeOverTimeAssessment */ + ChangeOverTimeAssessment: { + /** + * Assessmentstate + * @default not_assessed + * @constant + */ + assessmentState: "not_assessed"; + /** + * Message + * @default Change-over-time insights are not available yet. + */ + message: string; + }; + /** DependencyAssessment */ + DependencyAssessment: { + /** + * Status + * @constant + */ + status: "not_computed"; + }; + /** DependencyDeclaration */ + DependencyDeclaration: { + /** Ecosystem */ + ecosystem: string; + /** Endline */ + endLine: number; + /** Extractor */ + extractor: string; + /** Extractorversion */ + extractorVersion: string; + /** Manifestpath */ + manifestPath: string; + /** Name */ + name: string; + /** Startline */ + startLine: number; + /** + * Type + * @enum {string} + */ + type: "production" | "development" | "peer" | "optional"; + /** Version */ + version: string | null; + /** Workspacepath */ + workspacePath: string; + }; + /** DependencyDiagnostic */ + DependencyDiagnostic: { + /** Category */ + category: string; + /** Code */ + code: string; + /** Details */ + details?: { + [key: string]: unknown; + } | null; + /** Message */ + message: string; + /** Path */ + path?: string | null; + /** Producer */ + producer: string; + /** + * Severity + * @enum {string} + */ + severity: "fatal" | "error" | "warning" | "info"; + }; + /** DependencyEdge */ + DependencyEdge: { + /** Id */ + id: string; + /** Source */ + source: string; + /** Target */ + target: string; + /** + * Type + * @constant + */ + type: "depends-on"; + }; + /** DependencyGraphResponse */ + DependencyGraphResponse: { + /** Canonicalgraphhash */ + canonicalGraphHash: string; + /** Diagnostics */ + diagnostics?: components["schemas"]["DependencyDiagnostic"][]; + /** Edges */ + edges: components["schemas"]["DependencyEdge"][]; + /** + * Generatedat + * Format: date-time + */ + generatedAt: string; + /** + * Manifestcount + * @default 0 + */ + manifestCount: number; + /** Manifestdigest */ + manifestDigest: string; + /** Nodes */ + nodes: components["schemas"]["DependencyNode"][]; + outdatedAssessment: components["schemas"]["DependencyAssessment"]; + provenance: components["schemas"]["DependencyProvenance"]; + /** Repositoryid */ + repositoryId: string; + /** Repositoryname */ + repositoryName: string; + /** + * Revisionkind + * @enum {string} + */ + revisionKind: "git" | "upload"; + /** Revisionvalue */ + revisionValue: string; + /** + * Schemaversion + * @default dependency-graph.v2 + * @constant + */ + schemaVersion: "dependency-graph.v2"; + /** Snapshotid */ + snapshotId: string; + /** Snapshotschemaversion */ + snapshotSchemaVersion: string; + /** Totaldependencies */ + totalDependencies: number; + vulnerabilityAssessment: components["schemas"]["DependencyAssessment"]; + }; + /** DependencyNode */ + DependencyNode: { + /** Declarations */ + declarations: components["schemas"]["DependencyDeclaration"][]; + /** Ecosystem */ + ecosystem: string; + /** Id */ + id: string; + /** Name */ + name: string; + /** + * Type + * @enum {string} + */ + type: "production" | "development" | "peer" | "optional" | "multiple"; + /** Version */ + version: string | null; + }; + /** DependencyProvenance */ + DependencyProvenance: { + /** Canonicalgraphhash */ + canonicalGraphHash: string; + /** Snapshotid */ + snapshotId: string; + /** Snapshotschemaversion */ + snapshotSchemaVersion: string; + /** + * Source + * @default ri.v1 + * @constant + */ + source: "ri.v1"; + }; + /** EngineeringReviewResponse */ + EngineeringReviewResponse: { + /** + * Assessmentstatus + * @enum {string} + */ + assessmentStatus: "assessed" | "partially_assessed" | "not_assessed" | "insufficient_evidence"; + /** Canonicalgraphhash */ + canonicalGraphHash: string; + /** Categories */ + categories?: components["schemas"]["ReviewCategoryAssessment"][]; + /** Findings */ + findings?: components["schemas"]["ReviewFinding"][]; + /** + * Generatedat + * Format: date-time + */ + generatedAt: string; + /** Manifestdigest */ + manifestDigest: string; + provenance: components["schemas"]["ReviewProvenance"]; + /** Repositoryid */ + repositoryId: string; + /** Repositoryname */ + repositoryName: string; + /** + * Revisionkind + * @enum {string} + */ + revisionKind: "git" | "upload"; + /** Revisionvalue */ + revisionValue: string; + /** + * Schemaversion + * @default engineering-review.v2 + * @constant + */ + schemaVersion: "engineering-review.v2"; + /** Snapshotid */ + snapshotId: string; + /** Snapshotschemaversion */ + snapshotSchemaVersion: string; + summary: components["schemas"]["ReviewSummary"]; + }; + /** ErrorResponse */ + ErrorResponse: { + /** Code */ + code: string; + /** Details */ + details?: { + [key: string]: unknown; + } | null; + /** Message */ + message: string; + /** Request Id */ + request_id?: string | null; + }; + /** + * EvidenceSourceResponse + * @description The exact source text a citation points at, bound to its cited snapshot. + * + * ``status="unavailable"`` covers every case where the exact cited fact and + * revision cannot be honestly displayed (fact/span mismatch, source-hash + * mismatch, missing source, out-of-range span, binary content): ``content`` + * is always ``None`` in that case rather than falling back to different + * content under a trusted-looking snapshot/revision identity. + */ + EvidenceSourceResponse: { + /** Content */ + content?: string | null; + /** Endline */ + endLine: number; + /** Factid */ + factId: string; + /** Path */ + path: string; + /** Reason */ + reason?: string | null; + /** Repositoryid */ + repositoryId: string; + /** Revisionkind */ + revisionKind: string | null; + /** Revisionvalue */ + revisionValue: string | null; + /** + * Schemaversion + * @constant + */ + schemaVersion: "evidence-source.v1"; + /** + * Size + * @default 0 + */ + size: number; + /** Snapshotid */ + snapshotId: string; + /** Startline */ + startLine: number; + /** + * Status + * @enum {string} + */ + status: "ready" | "unavailable"; + /** + * Truncated + * @default false + */ + truncated: boolean; + }; + /** ExportRequest */ + ExportRequest: { + /** + * Format + * @enum {string} + */ + format: "json" | "markdown" | "html" | "pdf"; + /** Repositoryid */ + repositoryId: string; + /** + * Target + * @enum {string} + */ + target: "review" | "documentation" | "architecture" | "dependencies"; + }; + /** ExportResponse */ + ExportResponse: { + /** Content */ + content: string; + /** + * Encoding + * @enum {string} + */ + encoding: "utf-8" | "base64"; + /** Filename */ + filename: string; + /** Mediatype */ + mediaType: string; + }; + /** FileTreeNode */ + FileTreeNode: { + /** Children */ + children?: components["schemas"]["FileTreeNode"][] | null; + /** Extension */ + extension?: string | null; + /** Id */ + id: string; + /** Language */ + language?: string | null; + /** Name */ + name: string; + /** Path */ + path: string; + /** Size */ + size?: number | null; + /** + * Type + * @enum {string} + */ + type: "file" | "folder"; + }; + /** GenerateDocRequest */ + GenerateDocRequest: { + /** + * Format + * @default markdown + * @enum {string} + */ + format: "markdown" | "html"; + /** Repositoryid */ + repositoryId: string; + /** Sections */ + sections?: string[] | null; + }; + /** GenerateDocResponse */ + GenerateDocResponse: { + /** Content */ + content: string; + /** + * Format + * @enum {string} + */ + format: "markdown" | "html"; + /** + * Generatedat + * Format: date-time + */ + generatedAt: string; + /** + * Revisionkind + * @enum {string} + */ + revisionKind: "git" | "upload"; + /** Revisionref */ + revisionRef?: string | null; + /** Revisionvalue */ + revisionValue: string; + /** Snapshotid */ + snapshotId: string; + /** + * Snapshotschemaversion + * @constant + */ + snapshotSchemaVersion: "ri.v1"; + /** + * Source + * @constant + */ + source: "ri.v1"; + }; + /** GitHubImportRequest */ + GitHubImportRequest: { + /** Branch */ + branch?: string | null; + /** + * Url + * Format: uri + */ + url: string; + }; + /** HTTPValidationError */ + HTTPValidationError: { + /** Detail */ + detail?: components["schemas"]["ValidationError"][]; + }; + /** InsightBreakdown */ + InsightBreakdown: { + /** Key */ + key: string; + /** Label */ + label: string; + /** Value */ + value: number; + }; + /** InsightExtractor */ + InsightExtractor: { + /** Evidencerecordcount */ + evidenceRecordCount: number; + /** Name */ + name: string; + /** Version */ + version: string; + }; + /** InsightMetric */ + InsightMetric: { + /** + * Assessmentstate + * @enum {string} + */ + assessmentState: "assessed" | "not_assessed" | "insufficient_evidence"; + /** Definition */ + definition: string; + /** Denominator */ + denominator?: number | null; + /** Id */ + id: string; + /** Label */ + label: string; + /** Numerator */ + numerator?: number | null; + provenance: components["schemas"]["InsightProvenance"]; + /** Snapshotid */ + snapshotId: string; + /** Unit */ + unit: string; + /** Value */ + value: number | string | null; + }; + /** InsightProvenance */ + InsightProvenance: { + /** Canonicalgraphhash */ + canonicalGraphHash: string; + /** Snapshotid */ + snapshotId: string; + /** Snapshotschemaversion */ + snapshotSchemaVersion: string; + /** + * Source + * @default ri.v1 + * @constant + */ + source: "ri.v1"; + }; + /** LoginRequest */ + LoginRequest: { + /** + * Email + * Format: email + */ + email: string; + /** Password */ + password: string; + }; + /** + * ManifestExtractor + * @description One producer that contributed facts to the snapshot. + */ + ManifestExtractor: { + /** Name */ + name: string; + /** Version */ + version: string; + }; + /** RegisterRequest */ + RegisterRequest: { + /** + * Email + * Format: email + */ + email: string; + /** Password */ + password: string; + }; + /** RepositoryFileResponse */ + RepositoryFileResponse: { + /** Content */ + content: string; + /** + * Isbinary + * @default false + */ + isBinary: boolean; + /** + * Isimage + * @default false + */ + isImage: boolean; + /** Mediatype */ + mediaType?: string | null; + /** Path */ + path: string; + /** Size */ + size: number; + /** + * Truncated + * @default false + */ + truncated: boolean; + }; + /** RepositoryInsightsResponse */ + RepositoryInsightsResponse: { + /** Canonicalgraphhash */ + canonicalGraphHash: string; + changeOverTime?: components["schemas"]["ChangeOverTimeAssessment"]; + /** + * Computedat + * Format: date-time + */ + computedAt: string; + /** Diagnosticsbycode */ + diagnosticsByCode?: components["schemas"]["InsightBreakdown"][]; + /** Diagnosticsbyseverity */ + diagnosticsBySeverity?: components["schemas"]["InsightBreakdown"][]; + /** Extractorset */ + extractorSet?: components["schemas"]["InsightExtractor"][]; + /** Languages */ + languages?: components["schemas"]["InsightBreakdown"][]; + /** Manifestdigest */ + manifestDigest: string; + /** Metrics */ + metrics?: components["schemas"]["InsightMetric"][]; + provenance: components["schemas"]["InsightProvenance"]; + /** Relationshipsbypredicate */ + relationshipsByPredicate?: components["schemas"]["InsightBreakdown"][]; + /** Repositoryid */ + repositoryId: string; + /** Repositoryname */ + repositoryName: string; + /** + * Revisionkind + * @enum {string} + */ + revisionKind: "git" | "upload"; + /** Revisionvalue */ + revisionValue: string; + /** + * Schemaversion + * @default repository-insights.v1 + * @constant + */ + schemaVersion: "repository-insights.v1"; + /** + * Snapshotcreatedat + * Format: date-time + */ + snapshotCreatedAt: string; + /** Snapshotid */ + snapshotId: string; + /** Snapshotschemaversion */ + snapshotSchemaVersion: string; + /** + * Snapshotsealedat + * Format: date-time + */ + snapshotSealedAt: string; + }; + /** RepositoryListResponse */ + RepositoryListResponse: { + /** Data */ + data: components["schemas"]["RepositoryResponse"][]; + /** Total */ + total: number; + }; + /** RepositoryMeta */ + RepositoryMeta: { + /** Configfiles */ + configFiles: string[]; + /** Entrypoint */ + entryPoint: string | null; + /** Framework */ + framework: string; + /** Haslicense */ + hasLicense: boolean; + /** Hasreadme */ + hasReadme: boolean; + /** Language */ + language: string; + /** Licensename */ + licenseName: string | null; + /** Packagemanager */ + packageManager: string | null; + /** Totalfiles */ + totalFiles: number; + /** Totalfolders */ + totalFolders: number; + }; + /** RepositoryResponse */ + RepositoryResponse: { + /** Analysedat */ + analysedAt?: string | null; + /** Analysisprogress */ + analysisProgress: number; + /** Analysisstage */ + analysisStage?: ("uploading" | "extracting" | "reading-structure" | "detecting-languages" | "detecting-framework" | "building-file-tree" | "extracting-modules" | "building-dependency-graph" | "preparing-architecture" | "completed") | null; + /** Branch */ + branch?: string | null; + /** Commitsha */ + commitSha?: string | null; + /** Description */ + description?: string | null; + /** Errormessage */ + errorMessage?: string | null; + /** Filecount */ + fileCount: number; + /** Filetree */ + fileTree?: components["schemas"]["FileTreeNode"][]; + /** Id */ + id: string; + meta?: components["schemas"]["RepositoryMeta"] | null; + /** Name */ + name: string; + revision?: components["schemas"]["RepositoryRevision"] | null; + /** Size */ + size: number; + /** + * Source + * @enum {string} + */ + source: "upload" | "github"; + /** Sourceurl */ + sourceUrl?: string | null; + /** + * Status + * @enum {string} + */ + status: "uploading" | "analysing" | "completed" | "cancelled" | "error"; + /** + * Uploadedat + * Format: date-time + */ + uploadedAt: string; + }; + /** + * RepositoryRevision + * @description First-class repository revision identity (#87, RFC §3.2). + * + * ``value`` is the immutable identity: a 40-char lowercase git commit SHA for + * GitHub imports, or a ``sha256:`` archive content hash for uploads. + * ``ref`` (e.g. ``refs/heads/main``) is a moving pointer — descriptive + * metadata only, never identity, and always ``null`` for uploads. + */ + RepositoryRevision: { + /** + * Kind + * @enum {string} + */ + kind: "git" | "upload"; + /** Ref */ + ref?: string | null; + /** Value */ + value: string; + }; + /** RequestFlowStep */ + RequestFlowStep: { + /** Description */ + description: string; + /** Details */ + details: string[]; + /** Id */ + id: string; + /** Name */ + name: string; + /** + * Type + * @enum {string} + */ + type: "frontend" | "backend" | "controller" | "route" | "service" | "repository" | "database" | "configuration" | "authentication" | "middleware" | "utilities" | "models" | "external-api" | "shared-library" | "environment" | "queue" | "cache"; + }; + /** ReviewCategoryAssessment */ + ReviewCategoryAssessment: { + /** Explanation */ + explanation: string; + /** + * Findingcount + * @default 0 + */ + findingCount: number; + /** + * Id + * @enum {string} + */ + id: "architecture_boundaries" | "relationship_resolution" | "source_extraction" | "dependency_declarations" | "security_vulnerability_scanning" | "authentication_evidence" | "repository_structure" | "analysis_integrity"; + /** Label */ + label: string; + /** + * State + * @enum {string} + */ + state: "assessed" | "partially_assessed" | "not_assessed" | "insufficient_evidence"; + }; + /** ReviewEvidenceReference */ + ReviewEvidenceReference: { + /** Endline */ + endLine: number; + /** Evidenceid */ + evidenceId: string; + /** Extractorname */ + extractorName: string; + /** Extractorversion */ + extractorVersion: string; + /** Factid */ + factId: string; + /** Path */ + path: string; + /** Snapshotid */ + snapshotId: string; + /** Startline */ + startLine: number; + }; + /** ReviewFinding */ + ReviewFinding: { + /** + * Category + * @enum {string} + */ + category: "architecture_boundaries" | "relationship_resolution" | "source_extraction" | "dependency_declarations" | "security_vulnerability_scanning" | "authentication_evidence" | "repository_structure" | "analysis_integrity"; + /** Diagnosticcode */ + diagnosticCode: string; + /** Endline */ + endLine: number; + evidence: components["schemas"]["ReviewEvidenceReference"]; + /** Evidenceid */ + evidenceId: string; + /** Explanation */ + explanation: string; + /** Extractorname */ + extractorName: string; + /** Extractorversion */ + extractorVersion: string; + /** Factid */ + factId: string; + /** Id */ + id: string; + /** Path */ + path: string; + provenance: components["schemas"]["ReviewProvenance"]; + /** Remediationguidance */ + remediationGuidance: string; + /** Ruleid */ + ruleId: string; + /** + * Severity + * @enum {string} + */ + severity: "info" | "low" | "medium" | "high" | "critical"; + /** Snapshotid */ + snapshotId: string; + /** Startline */ + startLine: number; + /** + * Supportstatus + * @default supported + * @enum {string} + */ + supportStatus: "supported" | "file_scoped"; + /** Title */ + title: string; + }; + /** ReviewProvenance */ + ReviewProvenance: { + /** Canonicalgraphhash */ + canonicalGraphHash: string; + /** Snapshotid */ + snapshotId: string; + /** Snapshotschemaversion */ + snapshotSchemaVersion: string; + /** + * Source + * @default ri.v1 + * @constant + */ + source: "ri.v1"; + }; + /** ReviewSeverityCounts */ + ReviewSeverityCounts: { + /** + * Critical + * @default 0 + */ + critical: number; + /** + * High + * @default 0 + */ + high: number; + /** + * Info + * @default 0 + */ + info: number; + /** + * Low + * @default 0 + */ + low: number; + /** + * Medium + * @default 0 + */ + medium: number; + }; + /** ReviewSummary */ + ReviewSummary: { + /** Assessedcategories */ + assessedCategories: number; + /** Evidencebackedfindingcount */ + evidenceBackedFindingCount: number; + /** + * Filescopedfindingcount + * @default 0 + */ + fileScopedFindingCount: number; + findingsBySeverity: components["schemas"]["ReviewSeverityCounts"]; + /** Insufficientevidencecategories */ + insufficientEvidenceCategories: number; + /** Message */ + message: string; + /** Notassessedcategories */ + notAssessedCategories: number; + /** + * Omittedunsupporteddiagnosticcount + * @default 0 + */ + omittedUnsupportedDiagnosticCount: number; + /** Partiallyassessedcategories */ + partiallyAssessedCategories: number; + /** + * Vulnerabilityscanning + * @default not_assessed + * @constant + */ + vulnerabilityScanning: "not_assessed"; + }; + /** + * RevisionManifest + * @description The manifest body. Every field is copied from the sealed snapshot. + * + * This model is what gets hashed into ``manifestDigest``; the digest is + * therefore a function of exactly these fields, in canonical JSON form. + */ + RevisionManifest: { + /** Canonicalgraphhash */ + canonicalGraphHash: string | null; + /** Confighash */ + configHash: string; + /** + * Createdat + * Format: date-time + */ + createdAt: string; + /** Extractors */ + extractors: components["schemas"]["ManifestExtractor"][]; + /** Producersethash */ + producerSetHash: string; + /** Repositoryid */ + repositoryId: string; + /** + * Revisionkind + * @enum {string} + */ + revisionKind: "git" | "upload"; + /** Revisionref */ + revisionRef?: string | null; + /** Revisionvalue */ + revisionValue: string; + /** + * Schemaversion + * @default revision-manifest.v1 + * @constant + */ + schemaVersion: "revision-manifest.v1"; + /** Sealedat */ + sealedAt: string | null; + /** Snapshotid */ + snapshotId: string; + /** Snapshotschemaversion */ + snapshotSchemaVersion: string; + }; + /** RevisionManifestResponse */ + RevisionManifestResponse: { + manifest: components["schemas"]["RevisionManifest"]; + /** Manifestdigest */ + manifestDigest: string; + /** + * Verificationmethod + * @default sha256-canonical-json + */ + verificationMethod: string; + /** Verificationnote */ + verificationNote: string; + /** + * Verificationstate + * @enum {string} + */ + verificationState: "verified" | "superseded" | "mismatch" | "unverifiable"; + }; + /** + * RevisionManifestVerificationRequest + * @description A manifest a user previously exported, submitted for re-checking. + */ + RevisionManifestVerificationRequest: { + manifest: components["schemas"]["RevisionManifest"]; + /** Manifestdigest */ + manifestDigest: string; + }; + /** RevisionManifestVerificationResponse */ + RevisionManifestVerificationResponse: { + /** Detail */ + detail: string; + /** Matchesstoredsnapshot */ + matchesStoredSnapshot: boolean; + /** Mismatchedfields */ + mismatchedFields: string[]; + /** + * Verificationmethod + * @default sha256-canonical-json + */ + verificationMethod: string; + /** + * Verificationstate + * @enum {string} + */ + verificationState: "verified" | "superseded" | "mismatch" | "unverifiable"; + }; + /** RiAssertionResponse */ + RiAssertionResponse: { + /** Assertionid */ + assertionId: string; + /** Derivedfrom */ + derivedFrom?: { + [key: string]: string; + }[]; + /** Predicate */ + predicate: string; + /** Producer */ + producer: string; + /** Producerversion */ + producerVersion: string; + /** Subjectkey */ + subjectKey: string; + /** Subjectkind */ + subjectKind: string; + /** + * Truthclass + * @constant + */ + truthClass: "inferred"; + /** Value */ + value: { + [key: string]: unknown; + }; + }; + /** RiAssertionsResponse */ + RiAssertionsResponse: { + /** Data */ + data: components["schemas"]["RiAssertionResponse"][]; + pagination: components["schemas"]["RiPagination"]; + /** + * Schemaversion + * @constant + */ + schemaVersion: "ri.v1"; + }; + /** RiEdgeResponse */ + RiEdgeResponse: { + /** Derivedfrom */ + derivedFrom?: { + [key: string]: string; + }[]; + /** Edgeid */ + edgeId: string; + /** Evidence */ + evidence?: components["schemas"]["RiEvidenceResponse"][]; + /** Objectkey */ + objectKey: string; + /** Objectkind */ + objectKind: string; + /** Predicate */ + predicate: string; + /** Producer */ + producer: string; + /** Producerversion */ + producerVersion: string; + /** Subjectkey */ + subjectKey: string; + /** Subjectkind */ + subjectKind: string; + /** + * Truthclass + * @constant + */ + truthClass: "resolved"; + }; + /** RiEvidenceResponse */ + RiEvidenceResponse: { + /** Endline */ + endLine: number; + /** Extractor */ + extractor: string; + /** Extractorversion */ + extractorVersion: string; + /** Factid */ + factId: string; + /** + * Factkind + * @enum {string} + */ + factKind: "node" | "edge" | "observation"; + /** + * Granularity + * @enum {string} + */ + granularity: "span" | "file"; + /** Path */ + path: string; + /** + * Schemaversion + * @constant + */ + schemaVersion: "ri.v1"; + /** Startline */ + startLine: number; + }; + /** RiEvidenceResponsePage */ + RiEvidenceResponsePage: { + /** Data */ + data: components["schemas"]["RiEvidenceResponse"][]; + pagination: components["schemas"]["RiPagination"]; + /** + * Schemaversion + * @constant + */ + schemaVersion: "ri.v1"; + }; + /** RiNeighboursResponse */ + RiNeighboursResponse: { + /** Data */ + data: components["schemas"]["RiEdgeResponse"][]; + /** Nodekey */ + nodeKey: string; + pagination: components["schemas"]["RiPagination"]; + /** + * Schemaversion + * @constant + */ + schemaVersion: "ri.v1"; + }; + /** RiNodeResponse */ + RiNodeResponse: { + /** Evidence */ + evidence?: components["schemas"]["RiEvidenceResponse"][]; + /** Language */ + language?: string | null; + /** Name */ + name?: string | null; + /** Nodekind */ + nodeKind: string; + /** Properties */ + properties?: { + [key: string]: unknown; + } | null; + /** Stablekey */ + stableKey: string; + /** + * Truthclass + * @constant + */ + truthClass: "observed"; + }; + /** RiPagination */ + RiPagination: { + /** Limit */ + limit: number; + /** Offset */ + offset: number; + /** Total */ + total: number; + }; + /** RiPathResponse */ + RiPathResponse: { + node: components["schemas"]["RiNodeResponse"]; + /** Path */ + path: string; + }; + /** RiPathsResponse */ + RiPathsResponse: { + /** Data */ + data: components["schemas"]["RiPathResponse"][]; + pagination: components["schemas"]["RiPagination"]; + /** + * Schemaversion + * @constant + */ + schemaVersion: "ri.v1"; + }; + /** RiReferencesResponse */ + RiReferencesResponse: { + /** Data */ + data: components["schemas"]["RiEdgeResponse"][]; + pagination: components["schemas"]["RiPagination"]; + /** + * Schemaversion + * @constant + */ + schemaVersion: "ri.v1"; + }; + /** RiSnapshotMetadataResponse */ + RiSnapshotMetadataResponse: { + /** Canonicalgraphhash */ + canonicalGraphHash: string; + /** Confighash */ + configHash: string; + /** + * Createdat + * Format: date-time + */ + createdAt: string; + /** Producersethash */ + producerSetHash: string; + /** Producerversionset */ + producerVersionSet: string[]; + /** Repositoryid */ + repositoryId: string; + /** + * Revisionkind + * @enum {string} + */ + revisionKind: "git" | "upload"; + /** Revisionref */ + revisionRef?: string | null; + /** Revisionvalue */ + revisionValue: string; + /** + * Schemaversion + * @constant + */ + schemaVersion: "ri.v1"; + /** + * Sealedat + * Format: date-time + */ + sealedAt: string; + /** Snapshotid */ + snapshotId: string; + /** + * State + * @constant + */ + state: "completed"; + /** + * Updatedat + * Format: date-time + */ + updatedAt: string; + }; + /** RiSymbolsResponse */ + RiSymbolsResponse: { + /** Data */ + data: components["schemas"]["RiNodeResponse"][]; + pagination: components["schemas"]["RiPagination"]; + /** + * Schemaversion + * @constant + */ + schemaVersion: "ri.v1"; + }; + /** UserResponse */ + UserResponse: { + /** + * Createdat + * Format: date-time + */ + createdAt: string; + /** Email */ + email: string; + /** Id */ + id: string; + }; + /** ValidationError */ + ValidationError: { + /** Context */ + ctx?: Record; + /** Input */ + input?: unknown; + /** Location */ + loc: (string | number)[]; + /** Message */ + msg: string; + /** Error Type */ + type: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + get_ai_config_ai_config_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Saved provider configuration without the full API key. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "provider": "openai", + * "model": "gpt-4.1-mini", + * "hasApiKey": true, + * "apiKeyLast4": "1234" + * } + */ + "application/json": components["schemas"]["AiProviderPublicConfig"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + save_ai_config_ai_config_put: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AiProviderConfig"]; + }; + }; + responses: { + /** @description Provider configuration saved without returning the full API key. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "provider": "openai", + * "model": "gpt-4.1-mini", + * "hasApiKey": true, + * "apiKeyLast4": "1234" + * } + */ + "application/json": components["schemas"]["AiProviderPublicConfig"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + query_ai_ai_query_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AiQueryRequest"]; + }; + }; + responses: { + /** @description Repository-aware AI response. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "message": { + * "role": "assistant", + * "content": "Authentication is handled by the auth module.", + * "timestamp": "2026-07-17T00:00:00Z", + * "citations": [] + * }, + * "suggestions": [] + * } + */ + "application/json": components["schemas"]["AiQueryResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An upstream service could not complete the request. */ + 502: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "external_service_error", + * "message": "AI provider request failed.", + * "details": { + * "provider": "openai" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + test_ai_config_ai_test_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AiProviderTestRequest"]; + }; + }; + responses: { + /** @description Provider connectivity result. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "ok": true, + * "message": "Provider connection succeeded.", + * "checkedAt": "2026-07-17T00:00:00Z" + * } + */ + "application/json": components["schemas"]["AiProviderTestResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An upstream service could not complete the request. */ + 502: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "external_service_error", + * "message": "AI provider request failed.", + * "details": { + * "provider": "openai" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + get_architecture_analysis__repository_id__architecture_get: { + parameters: { + query?: never; + header?: never; + path: { + repository_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Architecture model derived from repository intelligence. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "repositoryId": "11111111-1111-1111-1111-111111111111", + * "repositoryName": "example-service", + * "architectureType": "Layered application", + * "detectedLayers": [], + * "nodes": [], + * "edges": [], + * "modules": [], + * "requestFlow": [], + * "diagnostics": [ + * { + * "code": "ARCH-REL-NOT-EXTRACTED", + * "category": "relationship extraction", + * "severity": "info", + * "message": "No sealed repository-intelligence snapshot is available for relationship analysis." + * } + * ], + * "summary": { + * "language": "Python", + * "framework": "FastAPI", + * "totalModules": 0, + * "totalNodes": 0, + * "entryPoint": "/app/main.py", + * "architecturePattern": "Layered application" + * } + * } + */ + "application/json": components["schemas"]["ArchitectureResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + get_authentication_explanation_analysis__repository_id__architecture_authentication_get: { + parameters: { + query?: never; + header?: never; + path: { + repository_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Evidence-backed explanation of how authentication works, read exclusively from the sealed Repository Intelligence snapshot query layer. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "schemaVersion": "auth-explanation.v1", + * "repositoryId": "11111111-1111-1111-1111-111111111111", + * "repositoryName": "example-service", + * "revisionKind": "upload", + * "revisionValue": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + * "snapshotId": "snap_example", + * "status": "ready", + * "summary": "Found 1 authentication-relevant route(s), 1 middleware/guard dependency(ies), 1 service(s), and 1 model(s), each backed by a citation to its exact stored source span.", + * "claims": [ + * { + * "kind": "route", + * "name": "/me", + * "confidence": "observed", + * "evidence": [ + * { + * "snapshotId": "snap_example", + * "factId": "src/routes.py::(anonymous:route#1)", + * "path": "src/routes.py", + * "startLine": 10, + * "endLine": 10 + * } + * ] + * } + * ], + * "relationships": [], + * "diagnostics": [] + * } + */ + "application/json": components["schemas"]["AuthenticationExplanationResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + cancel_analysis_analysis__repository_id__cancel_post: { + parameters: { + query?: never; + header?: never; + path: { + repository_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Analysis cancellation was accepted; a running job is cancelled cooperatively. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "repositoryId": "11111111-1111-1111-1111-111111111111", + * "status": "cancelled", + * "jobId": "22222222-2222-2222-2222-222222222222", + * "progress": 0, + * "completedAt": "2026-07-17T00:00:01Z" + * } + */ + "application/json": components["schemas"]["AnalysisStatusResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request conflicts with existing state. */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "conflict_error", + * "message": "Repository has already been imported.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + get_dependencies_analysis__repository_id__dependencies_get: { + parameters: { + query?: never; + header?: never; + path: { + repository_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Dependency inventory and resolved depends_on relationships from the selected sealed ri.v1 snapshot. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "schemaVersion": "dependency-graph.v2", + * "repositoryId": "11111111-1111-1111-1111-111111111111", + * "repositoryName": "example-service", + * "revisionKind": "upload", + * "revisionValue": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + * "snapshotId": "snap_example", + * "snapshotSchemaVersion": "ri.v1", + * "canonicalGraphHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111", + * "manifestDigest": "sha256:2222222222222222222222222222222222222222222222222222222222222222", + * "provenance": { + * "source": "ri.v1", + * "snapshotId": "snap_example", + * "snapshotSchemaVersion": "ri.v1", + * "canonicalGraphHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111" + * }, + * "generatedAt": "2026-07-17T00:00:00Z", + * "nodes": [ + * { + * "id": "dep:npm:react", + * "name": "react", + * "version": "^18.3.0", + * "type": "production", + * "ecosystem": "npm", + * "declarations": [ + * { + * "name": "react", + * "manifestPath": "apps/frontend/package.json", + * "workspacePath": "apps/frontend", + * "startLine": 3, + * "endLine": 3, + * "extractor": "dependency-manifest", + * "extractorVersion": "1.2.0", + * "ecosystem": "npm", + * "version": "^18.3.0", + * "type": "production" + * } + * ] + * } + * ], + * "edges": [ + * { + * "id": "edge_example", + * "source": "repo:root", + * "target": "dep:npm:react", + * "type": "depends-on" + * } + * ], + * "totalDependencies": 1, + * "manifestCount": 1, + * "diagnostics": [], + * "vulnerabilityAssessment": { + * "status": "not_computed" + * }, + * "outdatedAssessment": { + * "status": "not_computed" + * } + * } + */ + "application/json": components["schemas"]["DependencyGraphResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + get_evidence_source_analysis__repository_id__evidence_get: { + parameters: { + query: { + endLine: number; + factId: string; + path: string; + snapshotId: string; + startLine: number; + }; + header?: never; + path: { + repository_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Source text for one evidence citation, verified against the exact snapshot revision it was cited from. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "schemaVersion": "evidence-source.v1", + * "repositoryId": "11111111-1111-1111-1111-111111111111", + * "snapshotId": "snap_example", + * "factId": "src/routes.py::(anonymous:route#1)", + * "revisionKind": "upload", + * "revisionValue": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + * "path": "src/routes.py", + * "startLine": 10, + * "endLine": 10, + * "status": "ready", + * "content": "@app.get(\"/me\")\n", + * "truncated": false, + * "size": 16 + * } + */ + "application/json": components["schemas"]["EvidenceSourceResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + get_insights_analysis__repository_id__insights_get: { + parameters: { + query?: never; + header?: never; + path: { + repository_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Defined repository metrics computed only from the selected sealed ri.v1 snapshot. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "schemaVersion": "repository-insights.v1", + * "repositoryId": "11111111-1111-1111-1111-111111111111", + * "repositoryName": "example-service", + * "revisionKind": "upload", + * "revisionValue": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + * "snapshotId": "snap_example", + * "snapshotSchemaVersion": "ri.v1", + * "canonicalGraphHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111", + * "manifestDigest": "sha256:2222222222222222222222222222222222222222222222222222222222222222", + * "provenance": { + * "source": "ri.v1", + * "snapshotId": "snap_example", + * "snapshotSchemaVersion": "ri.v1", + * "canonicalGraphHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111" + * }, + * "computedAt": "2026-07-17T00:00:00Z", + * "snapshotCreatedAt": "2026-07-17T00:00:00Z", + * "snapshotSealedAt": "2026-07-17T00:00:00Z", + * "extractorSet": [], + * "metrics": [], + * "relationshipsByPredicate": [], + * "diagnosticsBySeverity": [], + * "diagnosticsByCode": [], + * "languages": [], + * "changeOverTime": { + * "assessmentState": "not_assessed", + * "message": "Change-over-time insights are not available yet." + * } + * } + */ + "application/json": components["schemas"]["RepositoryInsightsResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + get_review_analysis__repository_id__review_get: { + parameters: { + query?: never; + header?: never; + path: { + repository_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Deterministic evidence-backed findings for the selected sealed ri.v1 snapshot. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "schemaVersion": "engineering-review.v2", + * "repositoryId": "11111111-1111-1111-1111-111111111111", + * "repositoryName": "example-service", + * "revisionKind": "upload", + * "revisionValue": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + * "snapshotId": "snap_example", + * "snapshotSchemaVersion": "ri.v1", + * "canonicalGraphHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111", + * "manifestDigest": "sha256:2222222222222222222222222222222222222222222222222222222222222222", + * "provenance": { + * "source": "ri.v1", + * "snapshotId": "snap_example", + * "snapshotSchemaVersion": "ri.v1", + * "canonicalGraphHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111" + * }, + * "generatedAt": "2026-07-17T00:00:00Z", + * "assessmentStatus": "partially_assessed", + * "categories": [], + * "findings": [], + * "summary": { + * "message": "0 evidence-backed findings were identified in this revision. Security vulnerability scanning was not performed.", + * "findingsBySeverity": { + * "info": 0, + * "low": 0, + * "medium": 0, + * "high": 0, + * "critical": 0 + * }, + * "assessedCategories": 2, + * "partiallyAssessedCategories": 4, + * "notAssessedCategories": 0, + * "insufficientEvidenceCategories": 1, + * "evidenceBackedFindingCount": 0, + * "fileScopedFindingCount": 0, + * "omittedUnsupportedDiagnosticCount": 0, + * "vulnerabilityScanning": "not_assessed" + * } + * } + */ + "application/json": components["schemas"]["EngineeringReviewResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + get_revision_manifest_analysis__repository_id__revision_manifest_get: { + parameters: { + query?: never; + header?: never; + path: { + repository_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Verifiable identity of the sealed snapshot backing this repository's evidence: revision, snapshot, extractor versions and canonical digest. The digest is a canonical content hash, not a digital signature. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "manifest": { + * "schemaVersion": "revision-manifest.v1", + * "repositoryId": "11111111-1111-1111-1111-111111111111", + * "revisionKind": "upload", + * "revisionValue": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + * "snapshotId": "snap_example", + * "snapshotSchemaVersion": "ri.v1", + * "extractors": [ + * { + * "name": "typescript-extractor", + * "version": "1.0.0" + * }, + * { + * "name": "relationship-resolver", + * "version": "1.0.0" + * } + * ], + * "producerSetHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111", + * "configHash": "sha256:2222222222222222222222222222222222222222222222222222222222222222", + * "canonicalGraphHash": "sha256:3333333333333333333333333333333333333333333333333333333333333333", + * "createdAt": "2026-07-25T00:00:00Z", + * "sealedAt": "2026-07-25T00:00:05Z" + * }, + * "manifestDigest": "sha256:4444444444444444444444444444444444444444444444444444444444444444", + * "verificationMethod": "sha256-canonical-json", + * "verificationState": "verified", + * "verificationNote": "This digest is a SHA-256 over the canonical JSON encoding of the manifest fields above. It is a content hash, not a digital signature." + * } + */ + "application/json": components["schemas"]["RevisionManifestResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + verify_revision_manifest_analysis__repository_id__revision_manifest_verify_post: { + parameters: { + query?: never; + header?: never; + path: { + repository_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RevisionManifestVerificationRequest"]; + }; + }; + responses: { + /** @description Re-check a previously exported manifest against the stored snapshot. Reports a mismatch when the supplied digest does not match the supplied manifest, or when the manifest does not match stored facts. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "verificationState": "verified", + * "verificationMethod": "sha256-canonical-json", + * "matchesStoredSnapshot": true, + * "mismatchedFields": [], + * "detail": "The manifest matches the sealed snapshot stored for this repository." + * } + */ + "application/json": components["schemas"]["RevisionManifestVerificationResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + start_analysis_analysis__repository_id__start_post: { + parameters: { + query?: never; + header?: never; + path: { + repository_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Analysis was durably enqueued (or already complete); the request never blocks on the worker. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "repositoryId": "11111111-1111-1111-1111-111111111111", + * "status": "queued", + * "jobId": "22222222-2222-2222-2222-222222222222" + * } + */ + "application/json": components["schemas"]["AnalysisStartResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + get_analysis_status_analysis__repository_id__status_get: { + parameters: { + query?: never; + header?: never; + path: { + repository_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Current durable analysis-job status. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "repositoryId": "11111111-1111-1111-1111-111111111111", + * "status": "completed", + * "jobId": "22222222-2222-2222-2222-222222222222", + * "stage": "completed", + * "progress": 100, + * "startedAt": "2026-07-17T00:00:00Z", + * "completedAt": "2026-07-17T00:00:02Z" + * } + */ + "application/json": components["schemas"]["AnalysisStatusResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + login_auth_login_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["LoginRequest"]; + }; + }; + responses: { + /** @description Authenticated session with an access token and refresh cookie. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "accessToken": "example-access-token", + * "tokenType": "bearer", + * "user": { + * "id": "11111111-1111-1111-1111-111111111111", + * "email": "developer@example.com", + * "createdAt": "2026-07-17T00:00:00Z" + * } + * } + */ + "application/json": components["schemas"]["AuthResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + logout_auth_logout_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: { + partha_refresh?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + me_auth_me_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The authenticated user. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "id": "11111111-1111-1111-1111-111111111111", + * "email": "developer@example.com", + * "createdAt": "2026-07-17T00:00:00Z" + * } + */ + "application/json": components["schemas"]["UserResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + refresh_auth_refresh_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: { + partha_refresh?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Access token renewed and the refresh token rotated. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "accessToken": "example-access-token", + * "tokenType": "bearer", + * "user": { + * "id": "11111111-1111-1111-1111-111111111111", + * "email": "developer@example.com", + * "createdAt": "2026-07-17T00:00:00Z" + * } + * } + */ + "application/json": components["schemas"]["AuthResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + register_auth_register_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RegisterRequest"]; + }; + }; + responses: { + /** @description Account created and a refresh-cookie session started. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "accessToken": "example-access-token", + * "tokenType": "bearer", + * "user": { + * "id": "11111111-1111-1111-1111-111111111111", + * "email": "developer@example.com", + * "createdAt": "2026-07-17T00:00:00Z" + * } + * } + */ + "application/json": components["schemas"]["AuthResponse"]; + }; + }; + /** @description The request conflicts with existing state. */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "conflict_error", + * "message": "Repository has already been imported.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + generate_documentation_documentation_generate_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GenerateDocRequest"]; + }; + }; + responses: { + /** @description Generated documentation for the requested repository. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "content": "# Example service\\n\\n## Overview\\n\\nGenerated documentation.", + * "format": "markdown", + * "generatedAt": "2026-07-17T00:00:00Z", + * "source": "ri.v1", + * "snapshotId": "snap_example", + * "snapshotSchemaVersion": "ri.v1", + * "revisionKind": "git", + * "revisionValue": "0123456789abcdef0123456789abcdef01234567", + * "revisionRef": "refs/heads/main" + * } + */ + "application/json": components["schemas"]["GenerateDocResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + export_report_export_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExportRequest"]; + }; + }; + responses: { + /** @description Repository report rendered in the requested format; an uncomputed review returns 409. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "filename": "engineering-review.json", + * "mediaType": "application/json", + * "encoding": "utf-8", + * "content": "{\\n \\\"repositoryId\\\": \\\"11111111-1111-1111-1111-111111111111\\\"\\n}" + * } + */ + "application/json": components["schemas"]["ExportResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request conflicts with existing state. */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "conflict_error", + * "message": "Repository has already been imported.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + health_health_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The API process is running. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "status": "ok", + * "environment": "development" + * } + */ + "application/json": { + [key: string]: string; + }; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + get_snapshot_intelligence_v1_snapshots__snapshot_id__get: { + parameters: { + query?: never; + header?: never; + path: { + snapshot_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Sealed ri.v1 snapshot metadata. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "schemaVersion": "ri.v1" + * } + */ + "application/json": components["schemas"]["RiSnapshotMetadataResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + list_assertions_intelligence_v1_snapshots__snapshot_id__assertions_get: { + parameters: { + query?: { + /** @description Maximum 100 results per page. */ + limit?: number; + /** @description Zero-based deterministic result offset. */ + offset?: number; + }; + header?: never; + path: { + snapshot_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Stored inferred assertions with their derivations. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "schemaVersion": "ri.v1", + * "data": [], + * "pagination": { + * "offset": 0, + * "limit": 50, + * "total": 0 + * } + * } + */ + "application/json": components["schemas"]["RiAssertionsResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + list_evidence_intelligence_v1_snapshots__snapshot_id__evidence_get: { + parameters: { + query?: { + /** @description Maximum 100 results per page. */ + limit?: number; + /** @description Zero-based deterministic result offset. */ + offset?: number; + }; + header?: never; + path: { + snapshot_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Stored evidence spans only. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "schemaVersion": "ri.v1", + * "data": [], + * "pagination": { + * "offset": 0, + * "limit": 50, + * "total": 0 + * } + * } + */ + "application/json": components["schemas"]["RiEvidenceResponsePage"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + list_neighbours_intelligence_v1_snapshots__snapshot_id__neighbours_get: { + parameters: { + query: { + /** @description Maximum 100 results per page. */ + limit?: number; + nodeKey: string; + /** @description Zero-based deterministic result offset. */ + offset?: number; + }; + header?: never; + path: { + snapshot_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Stored resolved edges incident to a node. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "schemaVersion": "ri.v1", + * "nodeKey": "repo:root", + * "data": [], + * "pagination": { + * "offset": 0, + * "limit": 50, + * "total": 0 + * } + * } + */ + "application/json": components["schemas"]["RiNeighboursResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + list_paths_intelligence_v1_snapshots__snapshot_id__paths_get: { + parameters: { + query?: { + /** @description Maximum 100 results per page. */ + limit?: number; + /** @description Zero-based deterministic result offset. */ + offset?: number; + }; + header?: never; + path: { + snapshot_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Stored file facts with provenance. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "schemaVersion": "ri.v1", + * "data": [], + * "pagination": { + * "offset": 0, + * "limit": 50, + * "total": 0 + * } + * } + */ + "application/json": components["schemas"]["RiPathsResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + list_references_intelligence_v1_snapshots__snapshot_id__references_get: { + parameters: { + query?: { + /** @description Maximum 100 results per page. */ + limit?: number; + /** @description Zero-based deterministic result offset. */ + offset?: number; + }; + header?: never; + path: { + snapshot_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Stored resolved relationship facts only. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "schemaVersion": "ri.v1", + * "data": [], + * "pagination": { + * "offset": 0, + * "limit": 50, + * "total": 0 + * } + * } + */ + "application/json": components["schemas"]["RiReferencesResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + list_symbols_intelligence_v1_snapshots__snapshot_id__symbols_get: { + parameters: { + query?: { + /** @description Maximum 100 results per page. */ + limit?: number; + /** @description Zero-based deterministic result offset. */ + offset?: number; + }; + header?: never; + path: { + snapshot_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Observed symbol nodes with stored evidence. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "schemaVersion": "ri.v1", + * "data": [], + * "pagination": { + * "offset": 0, + * "limit": 50, + * "total": 0 + * } + * } + */ + "application/json": components["schemas"]["RiSymbolsResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + metrics_metrics_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Prometheus-compatible runtime metrics. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** @example partha_http_requests_total 1\n */ + "text/plain": string; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + "text/plain": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + "text/plain": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + readiness_ready_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The API can reach its database and write to repository storage. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "status": "ready", + * "environment": "development", + * "checks": { + * "database": "ok", + * "storage": "ok" + * } + * } + */ + "application/json": { + checks: { + [key: string]: "ok" | "error"; + }; + environment: string; + /** @enum {string} */ + status: "ready" | "not_ready"; + }; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The API is running but one or more readiness checks failed. */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "status": "not_ready", + * "environment": "development", + * "checks": { + * "database": "error", + * "storage": "ok" + * } + * } + */ + "application/json": { + checks: { + [key: string]: "ok" | "error"; + }; + environment: string; + /** @enum {string} */ + status: "ready" | "not_ready"; + }; + }; + }; + }; + }; + list_repositories_repositories_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Repositories owned by the authenticated user. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "data": [ + * { + * "id": "11111111-1111-1111-1111-111111111111", + * "name": "example-service", + * "source": "github", + * "sourceUrl": "https://github.com/example/example-service", + * "branch": "main", + * "size": 2048, + * "fileCount": 12, + * "status": "completed", + * "analysisStage": "completed", + * "analysisProgress": 100, + * "uploadedAt": "2026-07-17T00:00:00Z", + * "analysedAt": "2026-07-17T00:00:02Z", + * "revision": { + * "kind": "git", + * "value": "0123456789abcdef0123456789abcdef01234567", + * "ref": "refs/heads/main" + * }, + * "commitSha": "0123456789abcdef0123456789abcdef01234567", + * "fileTree": [] + * } + * ], + * "total": 1 + * } + */ + "application/json": components["schemas"]["RepositoryListResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + get_repository_repositories__repository_id__get: { + parameters: { + query?: never; + header?: never; + path: { + repository_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Repository metadata and file tree. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "id": "11111111-1111-1111-1111-111111111111", + * "name": "example-service", + * "source": "github", + * "sourceUrl": "https://github.com/example/example-service", + * "branch": "main", + * "size": 2048, + * "fileCount": 12, + * "status": "completed", + * "analysisStage": "completed", + * "analysisProgress": 100, + * "uploadedAt": "2026-07-17T00:00:00Z", + * "analysedAt": "2026-07-17T00:00:02Z", + * "revision": { + * "kind": "git", + * "value": "0123456789abcdef0123456789abcdef01234567", + * "ref": "refs/heads/main" + * }, + * "commitSha": "0123456789abcdef0123456789abcdef01234567", + * "fileTree": [] + * } + */ + "application/json": components["schemas"]["RepositoryResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + delete_repository_repositories__repository_id__delete: { + parameters: { + query?: never; + header?: never; + path: { + repository_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + get_repository_file_repositories__repository_id__file_get: { + parameters: { + query: { + /** @description Repository-relative file path. */ + path: string; + }; + header?: never; + path: { + repository_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Preview of a repository-relative file. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "path": "/README.md", + * "content": "# Example service\\n", + * "size": 18, + * "truncated": false, + * "isBinary": false, + * "isImage": false + * } + */ + "application/json": components["schemas"]["RepositoryFileResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + import_github_repository_repositories_github_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GitHubImportRequest"]; + }; + }; + responses: { + /** @description Public GitHub repository cloned and parsed. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "id": "11111111-1111-1111-1111-111111111111", + * "name": "example-service", + * "source": "github", + * "sourceUrl": "https://github.com/example/example-service", + * "branch": "main", + * "size": 2048, + * "fileCount": 12, + * "status": "completed", + * "analysisStage": "completed", + * "analysisProgress": 100, + * "uploadedAt": "2026-07-17T00:00:00Z", + * "analysedAt": "2026-07-17T00:00:02Z", + * "revision": { + * "kind": "git", + * "value": "0123456789abcdef0123456789abcdef01234567", + * "ref": "refs/heads/main" + * }, + * "commitSha": "0123456789abcdef0123456789abcdef01234567", + * "fileTree": [] + * } + */ + "application/json": components["schemas"]["RepositoryResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request conflicts with existing state. */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "conflict_error", + * "message": "Repository has already been imported.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An upstream service could not complete the request. */ + 502: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "external_service_error", + * "message": "AI provider request failed.", + * "details": { + * "provider": "openai" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An upstream service did not respond before the timeout. */ + 504: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "timeout_error", + * "message": "GitHub repository clone timed out.", + * "details": { + * "timeoutSeconds": 120 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + upload_repository_repositories_upload_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_upload_repository_repositories_upload_post"]; + }; + }; + responses: { + /** @description Repository archive accepted and parsed. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "id": "11111111-1111-1111-1111-111111111111", + * "name": "example-service", + * "source": "github", + * "sourceUrl": "https://github.com/example/example-service", + * "branch": "main", + * "size": 2048, + * "fileCount": 12, + * "status": "completed", + * "analysisStage": "completed", + * "analysisProgress": 100, + * "uploadedAt": "2026-07-17T00:00:00Z", + * "analysedAt": "2026-07-17T00:00:02Z", + * "revision": { + * "kind": "git", + * "value": "0123456789abcdef0123456789abcdef01234567", + * "ref": "refs/heads/main" + * }, + * "commitSha": "0123456789abcdef0123456789abcdef01234567", + * "fileTree": [] + * } + */ + "application/json": components["schemas"]["RepositoryResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request conflicts with existing state. */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "conflict_error", + * "message": "Repository has already been imported.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; +} diff --git a/apps/frontend/src/shared/services/api/index.ts b/apps/frontend/src/shared/services/api/index.ts index 4258d2c..df0b266 100644 --- a/apps/frontend/src/shared/services/api/index.ts +++ b/apps/frontend/src/shared/services/api/index.ts @@ -1,7 +1,7 @@ export { api, configureApiClient, getApiConfig, uploadFile, requestSharedRefresh } from './client'; export type { RequestConfig, ApiClientConfig, HttpMethod } from './client'; -export { ApiError, NetworkError, TimeoutError, CancelledError, isApiError, isNetworkError, isTimeoutError, isCancelledError, getErrorMessage, getErrorDetail } from './errors'; +export { ApiError, NetworkError, TimeoutError, CancelledError, isApiError, isNetworkError, isTimeoutError, isCancelledError, isErrorResponse, getErrorMessage, getErrorDetail } from './errors'; export { authService } from './auth'; export { repositoryService, repositoryIntelligenceService } from './repositories'; @@ -14,3 +14,4 @@ export { aiService } from './ai'; export { documentationService, exportService } from './documentation'; export type * from './types'; +export type * from './generated'; diff --git a/apps/frontend/src/shared/services/api/insights.ts b/apps/frontend/src/shared/services/api/insights.ts index 53cbd31..bc7c047 100644 --- a/apps/frontend/src/shared/services/api/insights.ts +++ b/apps/frontend/src/shared/services/api/insights.ts @@ -1,9 +1,20 @@ import { api } from './client'; import type { RequestConfig } from './client'; +import type { components } from './generated'; import type { RepositoryInsights } from '@/shared/types/insights'; export const insightsService = { getInsights(repositoryId: string, config?: RequestConfig): Promise { - return api.get(`/analysis/${repositoryId}/insights`, config); + return api.get(`/analysis/${repositoryId}/insights`, config) + .then((response) => ({ + ...response, + changeOverTime: response.changeOverTime ?? { assessmentState: 'not_assessed', message: 'Change-over-time insights are not available yet.' }, + extractorSet: response.extractorSet ?? [], + metrics: response.metrics ?? [], + relationshipsByPredicate: response.relationshipsByPredicate ?? [], + diagnosticsBySeverity: response.diagnosticsBySeverity ?? [], + diagnosticsByCode: response.diagnosticsByCode ?? [], + languages: response.languages ?? [], + })); }, }; diff --git a/apps/frontend/src/shared/services/api/repositories.ts b/apps/frontend/src/shared/services/api/repositories.ts index 3edaac5..5e0c3bf 100644 --- a/apps/frontend/src/shared/services/api/repositories.ts +++ b/apps/frontend/src/shared/services/api/repositories.ts @@ -1,17 +1,12 @@ import { api } from './client'; import type { RequestConfig } from './client'; +import type { components } from './generated'; import type { RepositoryResponse, RepositoryListResponse, RepositoryFileResponse, ImportGithubRequest, - RiCollectionResponse, - RiAssertion, - RiEdge, - RiEvidence, RiNeighboursResponse, - RiNode, - RiPath, RiSnapshotMetadata, } from './types'; @@ -42,7 +37,7 @@ export const repositoryIntelligenceService = { return api.get(`/intelligence/v1/snapshots/${encodeURIComponent(snapshotId)}`, config); }, - listSymbols(snapshotId: string, offset = 0, limit = 50, config?: RequestConfig): Promise> { + listSymbols(snapshotId: string, offset = 0, limit = 50, config?: RequestConfig): Promise { return api.get(`/intelligence/v1/snapshots/${encodeURIComponent(snapshotId)}/symbols?offset=${offset}&limit=${limit}`, config); }, @@ -50,19 +45,19 @@ export const repositoryIntelligenceService = { return api.get(`/intelligence/v1/snapshots/${encodeURIComponent(snapshotId)}/neighbours?nodeKey=${encodeURIComponent(nodeKey)}&offset=${offset}&limit=${limit}`, config); }, - listReferences(snapshotId: string, offset = 0, limit = 50, config?: RequestConfig): Promise> { + listReferences(snapshotId: string, offset = 0, limit = 50, config?: RequestConfig): Promise { return api.get(`/intelligence/v1/snapshots/${encodeURIComponent(snapshotId)}/references?offset=${offset}&limit=${limit}`, config); }, - listAssertions(snapshotId: string, offset = 0, limit = 50, config?: RequestConfig): Promise> { + listAssertions(snapshotId: string, offset = 0, limit = 50, config?: RequestConfig): Promise { return api.get(`/intelligence/v1/snapshots/${encodeURIComponent(snapshotId)}/assertions?offset=${offset}&limit=${limit}`, config); }, - listPaths(snapshotId: string, offset = 0, limit = 50, config?: RequestConfig): Promise> { + listPaths(snapshotId: string, offset = 0, limit = 50, config?: RequestConfig): Promise { return api.get(`/intelligence/v1/snapshots/${encodeURIComponent(snapshotId)}/paths?offset=${offset}&limit=${limit}`, config); }, - listEvidence(snapshotId: string, offset = 0, limit = 50, config?: RequestConfig): Promise> { + listEvidence(snapshotId: string, offset = 0, limit = 50, config?: RequestConfig): Promise { return api.get(`/intelligence/v1/snapshots/${encodeURIComponent(snapshotId)}/evidence?offset=${offset}&limit=${limit}`, config); }, }; diff --git a/apps/frontend/src/shared/services/api/review.ts b/apps/frontend/src/shared/services/api/review.ts index 8e0abac..35680e6 100644 --- a/apps/frontend/src/shared/services/api/review.ts +++ b/apps/frontend/src/shared/services/api/review.ts @@ -1,9 +1,11 @@ import { api } from './client'; import type { RequestConfig } from './client'; +import type { components } from './generated'; import type { ReviewResponse } from './types'; export const reviewService = { getReview(repositoryId: string, config?: RequestConfig): Promise { - return api.get(`/analysis/${repositoryId}/review`, config); + return api.get(`/analysis/${repositoryId}/review`, config) + .then((response) => ({ ...response, categories: response.categories ?? [], findings: response.findings ?? [] })); }, }; diff --git a/apps/frontend/src/shared/services/api/types.ts b/apps/frontend/src/shared/services/api/types.ts index 12f8824..1cec089 100644 --- a/apps/frontend/src/shared/services/api/types.ts +++ b/apps/frontend/src/shared/services/api/types.ts @@ -1,7 +1,8 @@ -import type { FileTreeNode, RepositoryMeta, AnalysisStage } from '@/shared/types'; -import type { ArchitectureModel } from '@/shared/types/architecture'; -import type { EngineeringReview } from '@/shared/types/review'; +import type { components } from './generated'; +type WithGeneratedDefaults = Omit & Required>; + +/** Transport helpers retained for non-schema-wrapped legacy endpoints. */ export interface ApiResponse { data: T; meta?: { @@ -21,487 +22,87 @@ export interface PaginatedResponse { }; } -// Repository - -// First-class revision identity (#87). `value` is the immutable identity: a -// 40-char lowercase git commit SHA for GitHub imports, or a `sha256:` -// archive content hash for uploads. `ref` (e.g. `refs/heads/main`) is a moving -// pointer — descriptive metadata only, never identity, and null for uploads. -export interface RepositoryRevision { - kind: 'git' | 'upload'; - value: string; - ref: string | null; -} - -export interface RepositoryResponse { - id: string; - name: string; - description: string | null; - source: 'upload' | 'github'; - sourceUrl: string | null; - branch?: string | null; - size: number; - fileCount: number; - status: 'uploading' | 'analysing' | 'completed' | 'cancelled' | 'error'; - analysisStage: AnalysisStage | null; - analysisProgress: number; - uploadedAt: string; - analysedAt: string | null; - errorMessage: string | null; - revision: RepositoryRevision | null; - // Backward-compatible alias of `revision.value`. - commitSha: string | null; - meta: RepositoryMeta | null; - fileTree: FileTreeNode[]; -} - -export interface CreateRepositoryRequest { - name: string; - description?: string; - source: 'upload' | 'github'; - sourceUrl?: string; -} - -export interface ImportGithubRequest { - url: string; - branch?: string; -} - -export interface RepositoryListResponse { - data: RepositoryResponse[]; - total: number; -} - -export interface RepositoryFileResponse { - path: string; - content: string; - size: number; - truncated: boolean; - isBinary: boolean; - isImage: boolean; - mediaType: string | null; -} - -// Repository Intelligence snapshot queries (`/intelligence/v1`). These are -// read-only views of sealed, owner-scoped normalized facts, not legacy metadata. -export type RiSchemaVersion = 'ri.v1'; - -export interface RiPagination { - offset: number; - limit: number; - total: number; -} - -export interface RiEvidence { - schemaVersion: RiSchemaVersion; - factKind: 'node' | 'edge' | 'observation'; - factId: string; - path: string; - startLine: number; - endLine: number; - granularity: 'span' | 'file'; - extractor: string; - extractorVersion: string; -} - -export interface RiNode { - stableKey: string; - nodeKind: string; - name: string | null; - language: string | null; - truthClass: 'observed'; - properties: Record | null; - evidence: RiEvidence[]; -} - -export interface RiEdge { - edgeId: string; - subjectKind: string; - subjectKey: string; - predicate: string; - objectKind: string; - objectKey: string; - truthClass: 'resolved'; - producer: string; - producerVersion: string; - evidence: RiEvidence[]; - derivedFrom: Array<{ kind: string; identity: string }>; -} - -export interface RiAssertion { - assertionId: string; - subjectKind: string; - subjectKey: string; - predicate: string; - value: Record; - truthClass: 'inferred'; - producer: string; - producerVersion: string; - derivedFrom: Array<{ kind: string; identity: string }>; -} - -export interface RiSnapshotMetadata { - schemaVersion: RiSchemaVersion; - snapshotId: string; - repositoryId: string; - revisionKind: 'git' | 'upload'; - revisionValue: string; - revisionRef: string | null; - state: 'completed'; - producerVersionSet: string[]; - producerSetHash: string; - configHash: string; - canonicalGraphHash: string; - createdAt: string; - updatedAt: string; - sealedAt: string; -} - -export interface RiCollectionResponse { - schemaVersion: RiSchemaVersion; - data: T[]; - pagination: RiPagination; -} - -export interface RiPath { - path: string; - node: RiNode; -} - -export interface RiNeighboursResponse extends RiCollectionResponse { - nodeKey: string; -} - -// Analysis -export type AnalysisJobStatus = 'queued' | 'running' | 'completed' | 'failed' | 'cancelled'; - -export interface AnalysisStartResponse { - repositoryId: string; - status: AnalysisJobStatus; - jobId: string | null; -} - -export interface AnalysisStatusResponse { - repositoryId: string; - status: AnalysisJobStatus; - jobId: string | null; - stage: AnalysisStage | null; - progress: number; - startedAt: string | null; - completedAt: string | null; - error: string | null; -} - -// Architecture -export type ArchitectureResponse = ArchitectureModel; - -// Authentication explanation (evidence-backed, snapshot-only) -export type AuthSchemaVersion = 'auth-explanation.v1'; -export type AuthClaimKind = 'route' | 'middleware' | 'service' | 'model' | 'dependency'; -export type AuthRelationshipNodeKind = 'route' | 'handler' | 'middleware' | 'service' | 'model' | 'dependency'; -export type AuthConfidence = 'observed' | 'heuristic'; -export type AuthStatus = 'ready' | 'missing_snapshot'; - -export interface AuthEvidenceRef { - snapshotId: string; - factId: string; - path: string; - startLine: number; - endLine: number; -} - -export interface AuthClaim { - kind: AuthClaimKind; - name: string; - confidence: AuthConfidence; - evidence: AuthEvidenceRef[]; -} - -export interface AuthRelationship { - subject: string; - subjectKind: AuthRelationshipNodeKind; - predicate: string; - object: string; - objectKind: AuthRelationshipNodeKind; - evidence: AuthEvidenceRef[]; -} - -export interface AuthChain { - route: string; - hops: AuthRelationship[]; -} - -export interface AuthenticationDiagnostic { - code: string; - category: string; - severity: 'fatal' | 'error' | 'warning' | 'info'; - message: string; - path: string | null; - startLine: number | null; - endLine: number | null; -} - -export interface AuthenticationExplanationResponse { - schemaVersion: AuthSchemaVersion; - repositoryId: string; - repositoryName: string; - revisionKind: string | null; - revisionValue: string | null; - snapshotId: string | null; - status: AuthStatus; - summary: string; - claims: AuthClaim[]; - relationships: AuthRelationship[]; - chains: AuthChain[]; - diagnostics: AuthenticationDiagnostic[]; -} - -// Evidence-source navigation (snapshot-and-revision-verified, #95) -export type EvidenceSchemaVersion = 'evidence-source.v1'; -export type EvidenceSourceStatus = 'ready' | 'unavailable'; - -export interface EvidenceSourceResponse { - schemaVersion: EvidenceSchemaVersion; - repositoryId: string; - snapshotId: string; - factId: string; - revisionKind: string | null; - revisionValue: string | null; - path: string; - startLine: number; - endLine: number; - status: EvidenceSourceStatus; - reason: string | null; - content: string | null; - truncated: boolean; - size: number; -} - -// Dependency Graph (#158) — built exclusively from a sealed ri.v1 snapshot, -// the same contract shape as Review/Insights: no legacy fallback. -export interface DependencyNode { - id: string; - name: string; - version: string | null; - type: 'production' | 'development' | 'peer' | 'optional' | 'multiple'; - ecosystem: string; - declarations: DependencyDeclaration[]; -} - -export interface DependencyDeclaration { - name: string; - manifestPath: string; - workspacePath: string; - startLine: number; - endLine: number; - extractor: string; - extractorVersion: string; - ecosystem: string; - version: string | null; - type: 'production' | 'development' | 'peer' | 'optional'; -} - -export interface DependencyDiagnostic { - code: string; - category: string; - severity: 'fatal' | 'error' | 'warning' | 'info'; - message: string; - path: string | null; - producer: string; - details: Record | null; -} - -export interface DependencyEdge { - id: string; - source: string; - target: string; - type: 'depends-on'; -} - -export interface DependencyAssessment { - status: 'not_computed'; -} - -export interface DependencyProvenance { - source: 'ri.v1'; - snapshotId: string; - snapshotSchemaVersion: string; - canonicalGraphHash: string; -} - -export interface DependencyGraphResponse { - schemaVersion: 'dependency-graph.v2'; - repositoryId: string; - repositoryName: string; - revisionKind: 'git' | 'upload'; - revisionValue: string; - snapshotId: string; - snapshotSchemaVersion: string; - canonicalGraphHash: string; - manifestDigest: string; - provenance: DependencyProvenance; - generatedAt: string; - nodes: DependencyNode[]; - edges: DependencyEdge[]; - totalDependencies: number; - manifestCount: number; - diagnostics: DependencyDiagnostic[]; - vulnerabilityAssessment: DependencyAssessment; - outdatedAssessment: DependencyAssessment; -} - -// Review -export type ReviewResponse = EngineeringReview; - -// AI -export interface AiQueryRequest { - repositoryId: string; - query: string; - context?: { - selectedNodeId?: string; - selectedFile?: string; - conversationHistory?: AiMessage[]; - }; -} - -export interface AiMessage { - role: 'user' | 'assistant'; - content: string; - timestamp: string; - citations?: AiCitation[]; -} - -export interface AiCitation { - file: string; - startLine: number; - endLine: number; - content: string; -} - -export interface AiQueryResponse { - message: AiMessage; - suggestions?: string[]; -} - -export type AiProvider = 'openai' | 'anthropic' | 'gemini' | 'openrouter' | 'ollama'; - -export interface AiProviderConfig { - provider: AiProvider; - apiKey?: string; - model?: string; - baseUrl?: string; -} - -export interface AiProviderPublicConfig { - provider: AiProvider | null; - model: string | null; - baseUrl: string | null; - hasApiKey: boolean; - // Write-only key contract: the backend returns only the last four characters, - // never the full key. Null when no key is stored. - apiKeyLast4: string | null; -} - -export interface AiProviderTestRequest { - provider?: AiProvider; - apiKey?: string; - model?: string; - baseUrl?: string; -} - -export interface AiProviderTestResponse { - ok: boolean; - message: string; - checkedAt: string; -} - -// Documentation -export interface GenerateDocRequest { - repositoryId: string; - format: 'markdown' | 'html'; - sections?: string[]; -} - -export interface GenerateDocResponse { - content: string; - format: 'markdown' | 'html'; - generatedAt: string; - source: 'ri.v1'; - snapshotId: string; - snapshotSchemaVersion: 'ri.v1'; - revisionKind: 'git' | 'upload'; - revisionValue: string; - revisionRef?: string | null; -} - -// Auth -export interface UserResponse { - id: string; - email: string; - createdAt: string; -} - -export interface AuthResponse { - accessToken: string; - tokenType: 'bearer'; - user: UserResponse; -} - -export interface LoginRequest { - email: string; - password: string; -} - -export interface RegisterRequest { - email: string; - password: string; -} - -// Export -export type ExportFormat = 'json' | 'markdown' | 'html' | 'pdf'; -export type ExportTarget = 'review' | 'documentation' | 'architecture' | 'dependencies'; - -export interface ExportRequest { - repositoryId: string; - target: ExportTarget; - format: ExportFormat; -} - -export interface ExportResponse { - filename: string; - mediaType: string; - encoding: 'utf-8' | 'base64'; - content: string; -} - -// Revision manifest (#113): the verifiable identity of the sealed snapshot an -// answer was derived from. `manifestDigest` is a canonical content hash, not a -// digital signature. -export interface ManifestExtractor { - name: string; - version: string; -} - -export interface RevisionManifest { - schemaVersion: 'revision-manifest.v1'; - repositoryId: string; - revisionKind: 'git' | 'upload'; - revisionValue: string; - revisionRef: string | null; - snapshotId: string; - snapshotSchemaVersion: string; - extractors: ManifestExtractor[]; - producerSetHash: string; - configHash: string; - canonicalGraphHash: string | null; - createdAt: string; - sealedAt: string | null; -} - -export interface RevisionManifestResponse { - manifest: RevisionManifest; - manifestDigest: string; - verificationMethod: string; - verificationState: 'verified' | 'superseded' | 'mismatch' | 'unverifiable'; - verificationNote: string; -} +// The aliases below are compatibility names for the existing service/UI +// modules. Their shapes are owned by the generated OpenAPI components; this +// file intentionally contains no hand-maintained DTO fields. +export type RepositoryRevision = components['schemas']['RepositoryRevision']; +export type RepositoryResponse = components['schemas']['RepositoryResponse']; +export type ImportGithubRequest = components['schemas']['GitHubImportRequest']; +export type RepositoryListResponse = components['schemas']['RepositoryListResponse']; +export type RepositoryFileResponse = components['schemas']['RepositoryFileResponse']; + +export type RiSchemaVersion = components['schemas']['RiEvidenceResponse']['schemaVersion']; +export type RiPagination = components['schemas']['RiPagination']; +export type RiEvidence = components['schemas']['RiEvidenceResponse']; +export type RiNode = components['schemas']['RiNodeResponse']; +export type RiEdge = components['schemas']['RiEdgeResponse']; +export type RiAssertion = components['schemas']['RiAssertionResponse']; +export type RiSnapshotMetadata = components['schemas']['RiSnapshotMetadataResponse']; +export type RiCollectionResponse = Omit & { data: T[] }; +export type RiPath = components['schemas']['RiPathResponse']; +export type RiNeighboursResponse = components['schemas']['RiNeighboursResponse']; + +export type AnalysisJobStatus = components['schemas']['AnalysisStartResponse']['status']; +export type AnalysisStartResponse = components['schemas']['AnalysisStartResponse']; +export type AnalysisStatusResponse = components['schemas']['AnalysisStatusResponse']; + +export type ArchitectureResponse = WithGeneratedDefaults; + +export type AuthSchemaVersion = components['schemas']['AuthenticationExplanationResponse']['schemaVersion']; +export type AuthClaimKind = components['schemas']['AuthClaim']['kind']; +export type AuthRelationshipNodeKind = components['schemas']['AuthRelationship']['subjectKind']; +export type AuthConfidence = components['schemas']['AuthClaim']['confidence']; +export type AuthStatus = components['schemas']['AuthenticationExplanationResponse']['status']; +export type AuthEvidenceRef = components['schemas']['AuthEvidenceRef']; +export type AuthClaim = components['schemas']['AuthClaim']; +export type AuthRelationship = components['schemas']['AuthRelationship']; +export type AuthChain = components['schemas']['AuthChain']; +export type AuthenticationDiagnostic = components['schemas']['AuthenticationDiagnostic']; +export type AuthenticationExplanationResponse = WithGeneratedDefaults< + components['schemas']['AuthenticationExplanationResponse'], + 'claims' | 'relationships' | 'chains' | 'diagnostics' +>; + +export type EvidenceSchemaVersion = components['schemas']['EvidenceSourceResponse']['schemaVersion']; +export type EvidenceSourceStatus = components['schemas']['EvidenceSourceResponse']['status']; +export type EvidenceSourceResponse = components['schemas']['EvidenceSourceResponse']; + +export type DependencyNode = components['schemas']['DependencyNode']; +export type DependencyDeclaration = components['schemas']['DependencyDeclaration']; +export type DependencyDiagnostic = components['schemas']['DependencyDiagnostic']; +export type DependencyEdge = components['schemas']['DependencyEdge']; +export type DependencyAssessment = components['schemas']['DependencyAssessment']; +export type DependencyProvenance = components['schemas']['DependencyProvenance']; +export type DependencyGraphResponse = WithGeneratedDefaults; + +export type ReviewResponse = WithGeneratedDefaults< + components['schemas']['EngineeringReviewResponse'], + 'categories' | 'findings' +>; + +export type AiQueryRequest = components['schemas']['AiQueryRequest']; +export type AiMessage = components['schemas']['AiMessage']; +export type AiCitation = components['schemas']['AiCitation']; +export type AiQueryResponse = components['schemas']['AiQueryResponse']; +export type AiProvider = components['schemas']['AiProviderConfig']['provider']; +export type AiProviderConfig = components['schemas']['AiProviderConfig']; +export type AiProviderPublicConfig = components['schemas']['AiProviderPublicConfig']; +export type AiProviderTestRequest = components['schemas']['AiProviderTestRequest']; +export type AiProviderTestResponse = components['schemas']['AiProviderTestResponse']; + +export type GenerateDocRequest = components['schemas']['GenerateDocRequest']; +export type GenerateDocResponse = components['schemas']['GenerateDocResponse']; + +export type UserResponse = components['schemas']['UserResponse']; +export type AuthResponse = components['schemas']['AuthResponse']; +export type LoginRequest = components['schemas']['LoginRequest']; +export type RegisterRequest = components['schemas']['RegisterRequest']; + +export type ExportFormat = components['schemas']['ExportRequest']['format']; +export type ExportTarget = components['schemas']['ExportRequest']['target']; +export type ExportRequest = components['schemas']['ExportRequest']; +export type ExportResponse = components['schemas']['ExportResponse']; + +export type ManifestExtractor = components['schemas']['ManifestExtractor']; +export type RevisionManifest = components['schemas']['RevisionManifest']; +export type RevisionManifestResponse = components['schemas']['RevisionManifestResponse']; diff --git a/apps/frontend/src/shared/services/backend.ts b/apps/frontend/src/shared/services/backend.ts index 7f6047e..471db85 100644 --- a/apps/frontend/src/shared/services/backend.ts +++ b/apps/frontend/src/shared/services/backend.ts @@ -117,14 +117,14 @@ function mapRepositoryResponse(response: RepositoryResponse): Repository { size: response.size, fileCount: response.fileCount, status: response.status, - analysisStage: response.analysisStage, + analysisStage: response.analysisStage ?? null, analysisProgress: response.analysisProgress, uploadedAt: response.uploadedAt, analysedAt: response.analysedAt || undefined, errorMessage: response.errorMessage || undefined, - revision: response.revision, - commitSha: response.commitSha, - meta: response.meta, - fileTree: response.fileTree, + revision: response.revision ? { ...response.revision, ref: response.revision.ref ?? null } : null, + commitSha: response.commitSha ?? null, + meta: response.meta ?? null, + fileTree: response.fileTree ?? [], }; } diff --git a/apps/frontend/src/shared/types/architecture.ts b/apps/frontend/src/shared/types/architecture.ts index 739ee86..5bf1ece 100644 --- a/apps/frontend/src/shared/types/architecture.ts +++ b/apps/frontend/src/shared/types/architecture.ts @@ -1,126 +1,25 @@ -export type ArchNodeType = - | 'frontend' - | 'backend' - | 'controller' - | 'route' - | 'service' - | 'repository' - | 'database' - | 'configuration' - | 'authentication' - | 'middleware' - | 'utilities' - | 'models' - | 'external-api' - | 'shared-library' - | 'environment' - | 'queue' - | 'cache'; - -export type ArchEdgeType = 'dependency' | 'import' | 'api-call' | 'data-flow' | 'event' | 'reads' | 'writes' | 'calls' | 'config-usage'; -export type RelationshipState = 'connected' | 'no-observed-relationships' | 'unresolved' | 'not-extracted'; -export type TruthClass = 'resolved' | 'inferred'; +import type { components } from '@/shared/services/api/generated'; + +export type ArchNodeType = components['schemas']['ArchNode']['type']; +export type ArchEdgeType = components['schemas']['ArchEdge']['type']; +export type RelationshipState = components['schemas']['ArchNode']['relationshipState']; +export type TruthClass = components['schemas']['ArchEdge']['truthClass']; +export type ArchNode = components['schemas']['ArchNode']; +export type ArchEvidence = components['schemas']['ArchEvidence']; +export type ArchEdge = components['schemas']['ArchEdge']; +export type ArchitectureDiagnostic = components['schemas']['ArchitectureDiagnostic']; +export type ArchLayer = components['schemas']['ArchLayer']; +export type ArchModule = components['schemas']['ArchModule']; +export type RequestFlowStep = components['schemas']['RequestFlowStep']; +export type ArchitectureSummary = components['schemas']['ArchitectureSummary']; + +// These fields have backend defaults, so the generated OpenAPI schema marks +// them optional even though every successful response includes them. +export type ArchitectureModel = components['schemas']['ArchitectureResponse'] & + Required>; // 'complexity' and 'size' heatmap modes were removed with #217: no repository- // intelligence producer measures either today, so there was no real signal to // render. 'usage' (dependents count) and 'critical' are computed only from // real graph facts. export type HeatmapMode = 'none' | 'usage' | 'critical'; - -export interface ArchNode { - id: string; - name: string; - type: ArchNodeType; - description: string; - responsibilities: string[]; - files: string[]; - dependencies: string[]; - dependents: string[]; - // No producer measures complexity or line counts today (#217); 'not_computed' - // is explicit and must never be synthesized from an unrelated real value - // (e.g. file count). - estimatedComplexity: 'low' | 'medium' | 'high' | 'not_computed'; - estimatedLines: number | 'not_computed'; - tags: string[]; - layer: string; - parentModule?: string; - relationshipState: RelationshipState; -} - -export interface ArchEvidence { - snapshotId: string; - factId: string; - path: string; - startLine: number; - endLine: number; -} - -export interface ArchEdge { - id: string; - source: string; - target: string; - label?: string; - type: ArchEdgeType; - predicate: string; - truthClass: TruthClass; - evidence: ArchEvidence[]; -} - -export interface ArchitectureDiagnostic { - code: string; - category: string; - severity: 'fatal' | 'error' | 'warning' | 'info'; - message: string; - path?: string; - startLine?: number; - endLine?: number; - subjectKey?: string; - objectKey?: string; - details?: Record; - nodeIds?: string[]; -} - -export interface ArchLayer { - id: string; - name: string; - order: number; - nodes: string[]; -} - -export interface ArchModule { - id: string; - name: string; - layer: string; - nodeIds: string[]; - description: string; - fileCount: number; -} - -export interface RequestFlowStep { - id: string; - name: string; - type: ArchNodeType; - description: string; - details: string[]; -} - -export interface ArchitectureModel { - repositoryId: string; - repositoryName: string; - architectureType: string; - detectedLayers: ArchLayer[]; - nodes: ArchNode[]; - edges: ArchEdge[]; - modules: ArchModule[]; - requestFlow: RequestFlowStep[]; - relationshipSnapshotId?: string; - diagnostics: ArchitectureDiagnostic[]; - summary: { - language: string; - framework: string; - totalModules: number; - totalNodes: number; - entryPoint: string; - architecturePattern: string; - }; -} diff --git a/apps/frontend/src/shared/types/index.ts b/apps/frontend/src/shared/types/index.ts index cb64bd5..17fd724 100644 --- a/apps/frontend/src/shared/types/index.ts +++ b/apps/frontend/src/shared/types/index.ts @@ -1,19 +1,11 @@ +import type { components } from '@/shared/services/api/generated'; + export type AppStatus = 'empty' | 'repository-selected' | 'uploading' | 'analysing' | 'completed' | 'cancelled' | 'error'; export type RepositorySource = 'upload' | 'github'; export type FeatureStatus = 'idle' | 'loading' | 'success' | 'error' | 'empty'; -export type AnalysisStage = - | 'uploading' - | 'extracting' - | 'reading-structure' - | 'detecting-languages' - | 'detecting-framework' - | 'building-file-tree' - | 'extracting-modules' - | 'building-dependency-graph' - | 'preparing-architecture' - | 'completed'; +export type AnalysisStage = NonNullable; export const ANALYSIS_STAGES: { key: AnalysisStage; label: string }[] = [ { key: 'uploading', label: 'Uploading Repository' }, @@ -28,35 +20,9 @@ export const ANALYSIS_STAGES: { key: AnalysisStage; label: string }[] = [ { key: 'completed', label: 'Analysis Complete' }, ]; -export interface FileTreeNode { - id: string; - name: string; - type: 'file' | 'folder'; - path: string; - children?: FileTreeNode[]; - size?: number; - extension?: string; - language?: string; -} - -export interface RepositoryMeta { - language: string; - framework: string; - totalFiles: number; - totalFolders: number; - entryPoint: string | null; - configFiles: string[]; - packageManager: string | null; - hasReadme: boolean; - hasLicense: boolean; - licenseName: string | null; -} - -export interface RepositoryRevision { - kind: 'git' | 'upload'; - value: string; - ref: string | null; -} +export type FileTreeNode = components['schemas']['FileTreeNode']; +export type RepositoryMeta = components['schemas']['RepositoryMeta']; +export type RepositoryRevision = components['schemas']['RepositoryRevision']; export interface Repository { id: string; diff --git a/apps/frontend/src/shared/types/insights.ts b/apps/frontend/src/shared/types/insights.ts index 3c8d230..74c8b8c 100644 --- a/apps/frontend/src/shared/types/insights.ts +++ b/apps/frontend/src/shared/types/insights.ts @@ -1,59 +1,15 @@ -export type InsightAssessmentState = 'assessed' | 'not_assessed' | 'insufficient_evidence'; - -export interface InsightProvenance { - source: 'ri.v1'; - snapshotId: string; - snapshotSchemaVersion: string; - canonicalGraphHash: string; -} - -export interface InsightExtractor { - name: string; - version: string; - evidenceRecordCount: number; -} - -export interface InsightMetric { - id: string; - label: string; - value: number | string | null; - unit: string; - definition: string; - provenance: InsightProvenance; - assessmentState: InsightAssessmentState; - snapshotId: string; - numerator: number | null; - denominator: number | null; -} - -export interface InsightBreakdown { - key: string; - label: string; - value: number; -} - -export interface RepositoryInsights { - schemaVersion: 'repository-insights.v1'; - repositoryId: string; - repositoryName: string; - revisionKind: 'git' | 'upload'; - revisionValue: string; - snapshotId: string; - snapshotSchemaVersion: string; - canonicalGraphHash: string; - manifestDigest: string; - provenance: InsightProvenance; - computedAt: string; - snapshotCreatedAt: string; - snapshotSealedAt: string; - extractorSet: InsightExtractor[]; - metrics: InsightMetric[]; - relationshipsByPredicate: InsightBreakdown[]; - diagnosticsBySeverity: InsightBreakdown[]; - diagnosticsByCode: InsightBreakdown[]; - languages: InsightBreakdown[]; - changeOverTime: { - assessmentState: 'not_assessed'; - message: string; - }; -} +import type { components } from '@/shared/services/api/generated'; + +export type InsightAssessmentState = components['schemas']['InsightMetric']['assessmentState']; +export type InsightProvenance = components['schemas']['InsightProvenance']; +export type InsightExtractor = components['schemas']['InsightExtractor']; +export type InsightMetric = components['schemas']['InsightMetric']; +export type InsightBreakdown = components['schemas']['InsightBreakdown']; + +export type RepositoryInsights = components['schemas']['RepositoryInsightsResponse'] & Required< + Pick< + components['schemas']['RepositoryInsightsResponse'], + 'changeOverTime' | 'extractorSet' | 'metrics' | 'relationshipsByPredicate' | + 'diagnosticsBySeverity' | 'diagnosticsByCode' | 'languages' + > +>; diff --git a/apps/frontend/src/shared/types/review.ts b/apps/frontend/src/shared/types/review.ts index 66006aa..e4f127f 100644 --- a/apps/frontend/src/shared/types/review.ts +++ b/apps/frontend/src/shared/types/review.ts @@ -1,113 +1,15 @@ -export type ReviewSeverity = 'info' | 'low' | 'medium' | 'high' | 'critical'; -export type ReviewAssessmentState = - | 'assessed' - | 'partially_assessed' - | 'not_assessed' - | 'insufficient_evidence'; - -export type ReviewCategory = - | 'architecture_boundaries' - | 'relationship_resolution' - | 'source_extraction' - | 'dependency_declarations' - | 'security_vulnerability_scanning' - | 'authentication_evidence' - | 'repository_structure' - | 'analysis_integrity'; - -/** - * How precisely a finding's evidence addresses its diagnostic. - * - * `supported` the reported lines are the diagnostic's own recorded span. - * `file_scoped` the diagnostic named a file but no span, so the reported lines - * cover the whole file rather than the exact defect. - */ -export type ReviewSupportStatus = 'supported' | 'file_scoped'; - -export interface ReviewProvenance { - source: 'ri.v1'; - snapshotId: string; - snapshotSchemaVersion: string; - canonicalGraphHash: string; -} - -export interface ReviewEvidenceReference { - evidenceId: string; - snapshotId: string; - factId: string; - path: string; - startLine: number; - endLine: number; - extractorName: string; - extractorVersion: string; -} - -export interface ReviewFinding { - id: string; - category: ReviewCategory; - severity: ReviewSeverity; - title: string; - explanation: string; - path: string; - startLine: number; - endLine: number; - snapshotId: string; - factId: string; - evidenceId: string; - extractorName: string; - extractorVersion: string; - diagnosticCode: string; - ruleId: string; - remediationGuidance: string; - supportStatus: ReviewSupportStatus; - provenance: ReviewProvenance; - evidence: ReviewEvidenceReference; -} - -export interface ReviewCategoryAssessment { - id: ReviewCategory; - label: string; - state: ReviewAssessmentState; - explanation: string; - findingCount: number; -} - -export interface ReviewSeverityCounts { - info: number; - low: number; - medium: number; - high: number; - critical: number; -} - -export interface ReviewSummary { - message: string; - findingsBySeverity: ReviewSeverityCounts; - assessedCategories: number; - partiallyAssessedCategories: number; - notAssessedCategories: number; - insufficientEvidenceCategories: number; - evidenceBackedFindingCount: number; - /** Subset of evidenceBackedFindingCount scoped to a whole file, not a span. */ - fileScopedFindingCount: number; - omittedUnsupportedDiagnosticCount: number; - vulnerabilityScanning: 'not_assessed'; -} - -export interface EngineeringReview { - schemaVersion: 'engineering-review.v2'; - repositoryId: string; - repositoryName: string; - revisionKind: 'git' | 'upload'; - revisionValue: string; - snapshotId: string; - snapshotSchemaVersion: string; - canonicalGraphHash: string; - manifestDigest: string; - provenance: ReviewProvenance; - generatedAt: string; - assessmentStatus: ReviewAssessmentState; - categories: ReviewCategoryAssessment[]; - findings: ReviewFinding[]; - summary: ReviewSummary; -} +import type { components } from '@/shared/services/api/generated'; + +export type ReviewSeverity = components['schemas']['ReviewFinding']['severity']; +export type ReviewAssessmentState = components['schemas']['ReviewCategoryAssessment']['state']; +export type ReviewCategory = components['schemas']['ReviewCategoryAssessment']['id']; +export type ReviewSupportStatus = components['schemas']['ReviewFinding']['supportStatus']; +export type ReviewProvenance = components['schemas']['ReviewProvenance']; +export type ReviewEvidenceReference = components['schemas']['ReviewEvidenceReference']; +export type ReviewFinding = components['schemas']['ReviewFinding']; +export type ReviewCategoryAssessment = components['schemas']['ReviewCategoryAssessment']; +export type ReviewSeverityCounts = components['schemas']['ReviewSeverityCounts']; +export type ReviewSummary = components['schemas']['ReviewSummary']; + +export type EngineeringReview = components['schemas']['EngineeringReviewResponse'] & + Required>; diff --git a/package.json b/package.json index cde1976..d62db84 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "partha:down": "docker compose down", "dev:frontend": "npm --prefix apps/frontend run dev", "build:frontend": "npm --prefix apps/frontend run build", + "generate:api-contract": "npm --prefix apps/frontend run generate:api-contract", "preview:frontend": "npm --prefix apps/frontend run preview", "lint:frontend": "npm --prefix apps/frontend run lint", "test:backend": "node scripts/backend-python.mjs -m pytest", diff --git a/scripts/generate-api-contract.mjs b/scripts/generate-api-contract.mjs new file mode 100644 index 0000000..227f3c6 --- /dev/null +++ b/scripts/generate-api-contract.mjs @@ -0,0 +1,79 @@ +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import openapiTS, { COMMENT_HEADER, astToString } from 'openapi-typescript'; + +const repoRoot = resolve(fileURLToPath(new URL('..', import.meta.url))); +const outputPath = resolve(repoRoot, 'apps/frontend/src/shared/services/api/generated.ts'); +const checkOnly = process.argv.includes('--check'); + +function pythonExecutable() { + if (process.env.PARTHA_PYTHON) return process.env.PARTHA_PYTHON; + const venvPython = process.platform === 'win32' + ? resolve(repoRoot, 'apps/backend/.venv/Scripts/python.exe') + : resolve(repoRoot, 'apps/backend/.venv/bin/python'); + return existsSync(venvPython) ? venvPython : (process.platform === 'win32' ? 'python' : 'python3'); +} + +function loadOpenApiDocument() { + const result = spawnSync( + pythonExecutable(), + ['-c', 'import json; from app.main import app; print(json.dumps(app.openapi(), separators=(",", ":")))'], + { cwd: resolve(repoRoot, 'apps/backend'), encoding: 'utf8' }, + ); + + if (result.error) throw result.error; + if (result.status !== 0) { + throw new Error(`Backend OpenAPI generation failed:\n${result.stderr || result.stdout}`); + } + const document = JSON.parse(result.stdout); + stripDynamicDefaults(document); + return document; +} + +// Pydantic evaluates datetime defaults while importing the backend module. +// Those values are runtime metadata, not part of the TypeScript contract, and +// must not make two generations from the same source differ. +function stripDynamicDefaults(value) { + if (!value || typeof value !== 'object') return; + if (Array.isArray(value)) { + value.forEach(stripDynamicDefaults); + return; + } + for (const [key, child] of Object.entries(value)) { + if (key === 'default' && typeof child === 'string' && /^\d{4}-\d{2}-\d{2}T.*Z$/.test(child)) { + delete value[key]; + } else { + stripDynamicDefaults(child); + } + } +} + +function assertSafeGeneratedOutput(content) { + const environmentUrl = /https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|host\.docker\.internal)(?::\d+)?/i; + const embeddedSecret = /(?:api[_-]?key|password|secret|token)\s*[:=]\s*["'`][^"'`]+["'`]/i; + if (environmentUrl.test(content)) { + throw new Error('Generated API contract contains an environment-specific URL.'); + } + if (embeddedSecret.test(content)) { + throw new Error('Generated API contract contains an embedded secret-like value.'); + } +} + +const document = loadOpenApiDocument(); +const generated = `${COMMENT_HEADER}// Source: apps/backend/app.main:app.openapi()\n// Generator: openapi-typescript@7.13.0\n\n${astToString(await openapiTS(document, { alphabetize: true }))}\n`; +assertSafeGeneratedOutput(generated); + +if (checkOnly) { + if (!existsSync(outputPath)) throw new Error(`Generated contract is missing: ${outputPath}`); + const current = readFileSync(outputPath, 'utf8'); + if (current !== generated) { + const firstDifference = [...generated].findIndex((character, index) => current[index] !== character); + throw new Error(`Generated API contract is stale at character ${firstDifference}. Run \`npm run generate:api-contract\`.`); + } + console.log(`API contract is up to date: ${outputPath}`); +} else { + writeFileSync(outputPath, generated, 'utf8'); + console.log(`Generated API contract: ${outputPath}`); +} From 39b3d597271063bb3eff675b60a7954b74dbe396 Mon Sep 17 00:00:00 2001 From: Shaurya K Sharma Date: Mon, 27 Jul 2026 22:28:29 +0100 Subject: [PATCH 2/4] fix(ci): secure generated API contract checks --- .github/workflows/ci.yml | 33 +++++++++++++++++++++++++++++++ apps/frontend/package-lock.json | 6 +++--- apps/frontend/package.json | 3 ++- scripts/generate-api-contract.mjs | 10 ++++++++-- 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6731e0b..c0cc25c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,39 @@ jobs: echo "Repository hygiene checks passed." + api-contract: + name: API Contract Drift + needs: repository-hygiene + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: apps/frontend/package-lock.json + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + cache: pip + + - name: Install frontend dependencies + run: npm ci --prefix apps/frontend + + - name: Install backend dependencies + run: | + python -m pip install -r apps/backend/requirements-dev.txt + python -m pip install -e apps/backend --no-deps + + - name: Check generated frontend API contract + run: npm --prefix apps/frontend run generate:api-contract -- --check + frontend: name: Frontend needs: repository-hygiene diff --git a/apps/frontend/package-lock.json b/apps/frontend/package-lock.json index 1d1c2b5..dcb1f06 100644 --- a/apps/frontend/package-lock.json +++ b/apps/frontend/package-lock.json @@ -3438,9 +3438,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "dev": true, "funding": [ { diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 399a27d..1f5fef2 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -56,6 +56,7 @@ }, "overrides": { "dompurify": "3.4.12", - "brace-expansion": "^5.0.8" + "brace-expansion": "^5.0.8", + "js-yaml": "4.3.0" } } diff --git a/scripts/generate-api-contract.mjs b/scripts/generate-api-contract.mjs index 227f3c6..8bf468a 100644 --- a/scripts/generate-api-contract.mjs +++ b/scripts/generate-api-contract.mjs @@ -1,10 +1,15 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; import { resolve } from 'node:path'; import { spawnSync } from 'node:child_process'; import { fileURLToPath } from 'node:url'; -import openapiTS, { COMMENT_HEADER, astToString } from 'openapi-typescript'; const repoRoot = resolve(fileURLToPath(new URL('..', import.meta.url))); +// The generator is kept at the repository root, while its tool dependency +// belongs to the frontend workspace. Resolve from that workspace so this works +// after `npm ci --prefix apps/frontend` in a clean CI checkout as well. +const frontendRequire = createRequire(resolve(repoRoot, 'apps/frontend/package.json')); +const { default: openapiTS, COMMENT_HEADER, astToString } = frontendRequire('openapi-typescript'); const outputPath = resolve(repoRoot, 'apps/frontend/src/shared/services/api/generated.ts'); const checkOnly = process.argv.includes('--check'); @@ -62,7 +67,8 @@ function assertSafeGeneratedOutput(content) { } const document = loadOpenApiDocument(); -const generated = `${COMMENT_HEADER}// Source: apps/backend/app.main:app.openapi()\n// Generator: openapi-typescript@7.13.0\n\n${astToString(await openapiTS(document, { alphabetize: true }))}\n`; +const contractBody = astToString(await openapiTS(document, { alphabetize: true })).replace(/\r?\n+$/, ''); +const generated = `${COMMENT_HEADER}// Source: apps/backend/app.main:app.openapi()\n// Generator: openapi-typescript@7.13.0\n\n${contractBody}\n`; assertSafeGeneratedOutput(generated); if (checkOnly) { From b1086ce1937aafe97a08a3619d786d52cb22199c Mon Sep 17 00:00:00 2001 From: Shaurya K Sharma Date: Mon, 27 Jul 2026 22:52:03 +0100 Subject: [PATCH 3/4] chore(api): refresh generated contract after rebase --- .../src/shared/services/api/generated.ts | 209 +++++++++++++++++- 1 file changed, 198 insertions(+), 11 deletions(-) diff --git a/apps/frontend/src/shared/services/api/generated.ts b/apps/frontend/src/shared/services/api/generated.ts index 9c6e48b..322292e 100644 --- a/apps/frontend/src/shared/services/api/generated.ts +++ b/apps/frontend/src/shared/services/api/generated.ts @@ -433,6 +433,23 @@ export interface paths { patch?: never; trace?: never; }; + "/intelligence/v1/snapshots/{snapshot_id}/impact": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Impact */ + get: operations["get_impact_intelligence_v1_snapshots__snapshot_id__impact_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/intelligence/v1/snapshots/{snapshot_id}/neighbours": { parameters: { query?: never; @@ -917,9 +934,9 @@ export interface components { * Estimatedcomplexity * @enum {string} */ - estimatedComplexity: "low" | "medium" | "high"; + estimatedComplexity: "low" | "medium" | "high" | "not_computed"; /** Estimatedlines */ - estimatedLines: number; + estimatedLines: number | "not_computed"; /** Files */ files: string[]; /** Id */ @@ -2120,6 +2137,44 @@ export interface components { */ schemaVersion: "ri.v1"; }; + /** + * RiImpactDirectionResponse + * @description One bounded traversal direction. + * + * ``limit_reached`` means the response omitted further reachable nodes at the + * requested depth rather than claiming that the listed nodes are exhaustive. + */ + RiImpactDirectionResponse: { + /** Data */ + data: components["schemas"]["RiImpactStepResponse"][]; + /** Limitreached */ + limitReached: boolean; + }; + /** RiImpactResponse */ + RiImpactResponse: { + dependencies: components["schemas"]["RiImpactDirectionResponse"]; + dependents: components["schemas"]["RiImpactDirectionResponse"]; + /** Depth */ + depth: number; + /** Nodekey */ + nodeKey: string; + /** + * Schemaversion + * @constant + */ + schemaVersion: "ri.v1"; + }; + /** + * RiImpactStepResponse + * @description A reached node and the stored edge that proves the traversal hop. + */ + RiImpactStepResponse: { + /** Depth */ + depth: number; + /** Nodekey */ + nodeKey: string; + via: components["schemas"]["RiEdgeResponse"]; + }; /** RiNeighboursResponse */ RiNeighboursResponse: { /** Data */ @@ -2765,7 +2820,7 @@ export interface operations { }; requestBody?: never; responses: { - /** @description Architecture model derived from repository intelligence. */ + /** @description Architecture model derived from the sealed repository-intelligence snapshot bound to the repository's current revision. A repository with no sealed snapshot yet returns 404, never a fallback graph (#217). */ 200: { headers: { [name: string]: unknown; @@ -2781,14 +2836,8 @@ export interface operations { * "edges": [], * "modules": [], * "requestFlow": [], - * "diagnostics": [ - * { - * "code": "ARCH-REL-NOT-EXTRACTED", - * "category": "relationship extraction", - * "severity": "info", - * "message": "No sealed repository-intelligence snapshot is available for relationship analysis." - * } - * ], + * "relationshipSnapshotId": "snap_example", + * "diagnostics": [], * "summary": { * "language": "Python", * "framework": "FastAPI", @@ -5398,6 +5447,144 @@ export interface operations { }; }; }; + get_impact_intelligence_v1_snapshots__snapshot_id__impact_get: { + parameters: { + query: { + /** @description Directed import/dependency traversal depth (1-10). */ + depth?: number; + nodeKey: string; + }; + header?: never; + path: { + snapshot_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Bounded, provenance-backed dependents and dependencies over stored import/dependency edges. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "schemaVersion": "ri.v1", + * "nodeKey": "file:src/api.py", + * "depth": 1, + * "dependents": { + * "data": [], + * "limitReached": false + * }, + * "dependencies": { + * "data": [], + * "limitReached": false + * } + * } + */ + "application/json": components["schemas"]["RiImpactResponse"]; + }; + }; + /** @description Authentication is required or the access token is invalid. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "unauthorized", + * "message": "Not authenticated.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The requested resource does not exist or is not accessible to this user. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "not_found", + * "message": "Repository not found.", + * "details": { + * "repositoryId": "11111111-1111-1111-1111-111111111111" + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request could not be validated. */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "request_validation_error", + * "message": "Request validation failed.", + * "details": { + * "errors": [ + * { + * "loc": [ + * "body", + * "url" + * ], + * "msg": "Field required" + * } + * ] + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description The request-rate limit has been exceeded. */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "rate_limited", + * "message": "Too many requests. Try again shortly.", + * "details": { + * "retryAfterSeconds": 30 + * }, + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description An unexpected server error occurred. */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "code": "internal_server_error", + * "message": "An unexpected error occurred.", + * "request_id": "req_01HXYZEXAMPLE" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; list_neighbours_intelligence_v1_snapshots__snapshot_id__neighbours_get: { parameters: { query: { From 987ce27b8964b9caf7fd49d310ff112277e1b426 Mon Sep 17 00:00:00 2001 From: Shaurya K Sharma Date: Tue, 28 Jul 2026 11:53:45 +0100 Subject: [PATCH 4/4] fix(api): address contract review feedback --- .../src/shared/services/api/errors.test.ts | 24 +++++++++++++++++++ .../src/shared/services/api/errors.ts | 4 ++-- scripts/generate-api-contract.mjs | 22 +---------------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/apps/frontend/src/shared/services/api/errors.test.ts b/apps/frontend/src/shared/services/api/errors.test.ts index 3310bfb..dfb9a32 100644 --- a/apps/frontend/src/shared/services/api/errors.test.ts +++ b/apps/frontend/src/shared/services/api/errors.test.ts @@ -8,6 +8,7 @@ import { isErrorResponse, isNetworkError, isTimeoutError, + getErrorDetail, parseRetryAfter, } from './errors'; @@ -104,3 +105,26 @@ describe('type guards', () => { expect(isErrorResponse(null)).toBe(false); }); }); + +describe('getErrorDetail', () => { + it('only renders details from a valid backend error envelope', () => { + const valid = new ApiError( + 422, + 'Unprocessable Entity', + { code: 'validation_error', message: 'Invalid request', details: ['name is required'] }, + '/x', + ); + const invalid = new ApiError( + 422, + 'Unprocessable Entity', + { details: ['must not render without code and message'] }, + '/x', + ); + + expect(getErrorDetail(valid)).toEqual({ + message: 'Invalid request', + details: ['name is required'], + }); + expect(getErrorDetail(invalid).details).toEqual([]); + }); +}); diff --git a/apps/frontend/src/shared/services/api/errors.ts b/apps/frontend/src/shared/services/api/errors.ts index 9723a76..5fca9e2 100644 --- a/apps/frontend/src/shared/services/api/errors.ts +++ b/apps/frontend/src/shared/services/api/errors.ts @@ -155,8 +155,8 @@ function getBackendMessage(body: unknown): string | null { } function getBackendDetails(body: unknown): string[] { - if (!body || typeof body !== 'object') return []; - const raw = (body as { details?: unknown }).details; + if (!isErrorResponse(body)) return []; + const raw = body.details; if (Array.isArray(raw)) { return raw .map((item) => { diff --git a/scripts/generate-api-contract.mjs b/scripts/generate-api-contract.mjs index 8bf468a..994cdc4 100644 --- a/scripts/generate-api-contract.mjs +++ b/scripts/generate-api-contract.mjs @@ -32,27 +32,7 @@ function loadOpenApiDocument() { if (result.status !== 0) { throw new Error(`Backend OpenAPI generation failed:\n${result.stderr || result.stdout}`); } - const document = JSON.parse(result.stdout); - stripDynamicDefaults(document); - return document; -} - -// Pydantic evaluates datetime defaults while importing the backend module. -// Those values are runtime metadata, not part of the TypeScript contract, and -// must not make two generations from the same source differ. -function stripDynamicDefaults(value) { - if (!value || typeof value !== 'object') return; - if (Array.isArray(value)) { - value.forEach(stripDynamicDefaults); - return; - } - for (const [key, child] of Object.entries(value)) { - if (key === 'default' && typeof child === 'string' && /^\d{4}-\d{2}-\d{2}T.*Z$/.test(child)) { - delete value[key]; - } else { - stripDynamicDefaults(child); - } - } + return JSON.parse(result.stdout); } function assertSafeGeneratedOutput(content) {